package main

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"regexp"
	"strings"
	"sync"
	"time"
)

const qweatherHost = "https://pb4nmv4qnu.re.qweatherapi.com"

type City struct {
	ID   string `json:"id"`
	Name string `json:"name"`
	Adm2 string `json:"adm2"`
	Adm1 string `json:"adm1"`
}

var cities = []City{
	{"101010100", "北京", "北京", "北京"},
	{"101020100", "上海", "上海", "上海"},
	{"101280101", "广州", "广州", "广东"},
	{"101280601", "深圳", "深圳", "广东"},
	{"101200101", "武汉", "武汉", "湖北"},
	{"101110101", "西安", "西安", "陕西"},
	{"101210101", "杭州", "杭州", "浙江"},
	{"101190101", "南京", "南京", "江苏"},
	{"101070101", "沈阳", "沈阳", "辽宁"},
	{"101050101", "哈尔滨", "哈尔滨", "黑龙江"},
	{"101250101", "长沙", "长沙", "湖南"},
	{"101270101", "成都", "成都", "四川"},
	{"101090101", "石家庄", "石家庄", "河北"},
	{"101090206", "任丘", "任丘", "河北"},
	{"101090301", "邯郸", "邯郸", "河北"},
	{"101290106", "宣威", "宣威", "云南"},
	{"101290101", "昆明", "昆明", "云南"},
	{"101260101", "贵阳", "贵阳", "贵州"},
}

var defaultCity = City{"101200101", "武汉", "武汉", "湖北"}

var httpClient = &http.Client{Timeout: 60 * time.Second}

var weatherIcons = map[string]string{
	"晴": "☀️", "多云": "⛅", "阴": "☁️",
	"雨": "🌧️", "雪": "❄️", "雷": "⛈️",
	"雾": "🌫️", "霾": "😷", "风": "💨",
}

func getWeatherIcon(text string) string {
	for k, v := range weatherIcons {
		if strings.Contains(text, k) {
			return v
		}
	}
	return "🌤️"
}

func httpGet(url string) ([]byte, error) {
	resp, err := httpClient.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	return io.ReadAll(resp.Body)
}

// ── 定位 ──

func getLocation() City {
	if city := locateByIPIP(); city != nil {
		return *city
	}
	if city := locateByGeoAPI(); city != nil {
		return *city
	}
	log.Println("所有定位失败，使用默认城市:", defaultCity.Name)
	return defaultCity
}

var (
	reIPIP    = regexp.MustCompile(`来自于[：:]\s*(.+?)\s*$`)
	reIPAddr  = regexp.MustCompile(`(\d+\.\d+\.\d+\.\d+)`)
)

func locateByIPIP() *City {
	data, err := httpGet("https://myip.ipip.net")
	if err != nil {
		return nil
	}
	text := string(data)
	m := reIPIP.FindStringSubmatch(text)
	if m == nil {
		return nil
	}
	parts := strings.Fields(m[1])
	cityName := parts[0]
	if len(parts) >= 3 {
		cityName = parts[2]
	} else if len(parts) >= 2 {
		cityName = parts[1]
	}
	for _, c := range cities {
		if c.Name == cityName {
			log.Println("ipip.net 匹配:", c.Name)
			return &c
		}
	}
	return nil
}

func locateByGeoAPI() *City {
	data, err := httpGet("https://myip.ipip.net")
	if err == nil {
		if m := reIPAddr.FindStringSubmatch(string(data)); m != nil {
			return geoLookup(m[1])
		}
	}
	return nil
}

func geoLookup(ip string) *City {
	key := loadConfig().qweatherKey()
	if key == "" {
		log.Println("未配置和风天气 API Key")
		return nil
	}
	url := fmt.Sprintf(qweatherHost+"/v2/city/lookup?location=%s&key=%s", ip, key)
	data, err := httpGet(url)
	if err != nil {
		return nil
	}
	var resp struct {
		Code     string `json:"code"`
		Location []struct {
			ID   string `json:"id"`
			Name string `json:"name"`
			Adm2 string `json:"adm2"`
			Adm1 string `json:"adm1"`
		} `json:"location"`
	}
	if json.Unmarshal(data, &resp) != nil || resp.Code != "200" || len(resp.Location) == 0 {
		return nil
	}
	loc := resp.Location[0]
	for _, c := range cities {
		if c.Name == loc.Name {
			return &c
		}
	}
	return &City{loc.ID, loc.Name, loc.Adm2, loc.Adm1}
}

func getCurrentCity() City {
	cfg := loadConfig()
	if cfg.City != "" {
		for _, c := range cities {
			if c.ID == cfg.City {
				return c
			}
		}
	}
	return getLocation()
}

// ── 天气数据 ──

type hourlyItem struct {
	Time string `json:"time"`
	Temp string `json:"temp"`
	Icon string `json:"icon"`
	Pop  string `json:"pop,omitempty"`
}

type dailyItem struct {
	Date    string `json:"date"`
	Icon    string `json:"icon"`
	TempMin string `json:"tempMin"`
	TempMax string `json:"tempMax"`
}

type currentWeather struct {
	Text string `json:"text"`
	Temp string `json:"temp"`
}

var (
	weatherCacheMu   sync.Mutex
	weatherCache     map[string]time.Time
	weatherCacheData map[string]string
)

func init() {
	weatherCache = make(map[string]time.Time)
	weatherCacheData = make(map[string]string)
}

func fetchAndPushWeather(city City) {
	// 5 分钟内同一城市不重复请求
	weatherCacheMu.Lock()
	if last, ok := weatherCache[city.ID]; ok && time.Since(last) < 5*time.Minute {
		if cached, ok := weatherCacheData[city.ID]; ok {
			weatherCacheMu.Unlock()
			evalJS(fmt.Sprintf(`if(window.updateWeatherFromGo) window.updateWeatherFromGo(%s)`, cached))
			return
		}
	}
	weatherCacheMu.Unlock()

	type weatherData struct {
		Current string       `json:"current"`
		Hourly  []hourlyItem `json:"hourly"`
		Daily   []dailyItem  `json:"daily"`
		City    string       `json:"city"`
	}

	wd := weatherData{}
	cityName := city.Name
	if city.Adm2 != city.Name {
		cityName = city.Adm2 + " " + city.Name
	}
	wd.City = cityName

	if now := fetchCurrentWeather(city.ID); now != nil {
		icon := getWeatherIcon(now.Text)
		wd.Current = fmt.Sprintf("📍 %s %s %s %s°C", cityName, icon, now.Text, now.Temp)
	} else {
		wd.Current = fmt.Sprintf("📍 %s ☀️ 晴 24°C", cityName)
	}

	wd.Hourly = fetchHourlyForecast(city.ID)
	wd.Daily = fetchDailyForecast(city.ID)

	jsonData, _ := json.Marshal(wd)
	weatherCacheMu.Lock()
	weatherCache[city.ID] = time.Now()
	weatherCacheData[city.ID] = string(jsonData)
	weatherCacheMu.Unlock()
	evalJS(fmt.Sprintf(`if(window.updateWeatherFromGo) window.updateWeatherFromGo(%s)`, string(jsonData)))
	log.Println("天气已推送:", wd.Current)
}

func fetchCurrentWeather(cityID string) *currentWeather {
	key := loadConfig().qweatherKey()
	if key == "" {
		log.Println("未配置和风天气 API Key")
		return nil
	}
	url := fmt.Sprintf(qweatherHost+"/v7/weather/now?location=%s&key=%s", cityID, key)
	data, err := httpGet(url)
	if err != nil {
		return nil
	}
	var resp struct {
		Code string `json:"code"`
		Now  struct {
			Text string `json:"text"`
			Temp string `json:"temp"`
		} `json:"now"`
	}
	if json.Unmarshal(data, &resp) != nil || resp.Code != "200" {
		return nil
	}
	return &currentWeather{resp.Now.Text, resp.Now.Temp}
}

func fetchHourlyForecast(cityID string) []hourlyItem {
	key := loadConfig().qweatherKey()
	if key == "" {
		return nil
	}
	url := fmt.Sprintf(qweatherHost+"/v7/weather/24h?location=%s&key=%s", cityID, key)
	data, err := httpGet(url)
	if err != nil {
		return nil
	}
	var resp struct {
		Code   string `json:"code"`
		Hourly []struct {
			FxTime string `json:"fxTime"`
			Temp   string `json:"temp"`
			Pop    string `json:"pop"`
			Text   string `json:"text"`
		} `json:"hourly"`
	}
	if json.Unmarshal(data, &resp) != nil || resp.Code != "200" {
		return nil
	}
	var result []hourlyItem
	for i, h := range resp.Hourly {
		if i >= 8 {
			break
		}
		t := h.FxTime
		if idx := strings.Index(t, "T"); idx >= 0 {
			t = t[idx+1:]
			if len(t) >= 5 {
				t = t[:5]
			}
		}
		result = append(result, hourlyItem{t, h.Temp + "°", getWeatherIcon(h.Text), h.Pop})
	}
	return result
}

func fetchDailyForecast(cityID string) []dailyItem {
	key := loadConfig().qweatherKey()
	if key == "" {
		return nil
	}
	url := fmt.Sprintf(qweatherHost+"/v7/weather/7d?location=%s&key=%s", cityID, key)
	data, err := httpGet(url)
	if err != nil {
		return nil
	}
	var resp struct {
		Code  string `json:"code"`
		Daily []struct {
			FxDate  string `json:"fxDate"`
			TextDay string `json:"textDay"`
			TempMin string `json:"tempMin"`
			TempMax string `json:"tempMax"`
		} `json:"daily"`
	}
	if json.Unmarshal(data, &resp) != nil || resp.Code != "200" {
		return nil
	}
	weekdays := []string{"周日", "周一", "周二", "周三", "周四", "周五", "周六"}
	today := time.Now().Format("2006-01-02")
	var result []dailyItem
	for _, d := range resp.Daily {
		date := d.FxDate
		if date == today {
			date = "今天"
		} else {
			if t, err := time.Parse("2006-01-02", date); err == nil {
				date = weekdays[t.Weekday()]
			}
		}
		result = append(result, dailyItem{date, getWeatherIcon(d.TextDay), d.TempMin, d.TempMax})
	}
	return result
}

func weatherLoop() {
	time.Sleep(3 * time.Second)
	city := getCurrentCity()
	log.Println("初始城市:", city.Name)
	fetchAndPushWeather(city)

	ticker := time.NewTicker(10 * time.Minute)
	for range ticker.C {
		city := getCurrentCity()
		fetchAndPushWeather(city)
	}
}
