package forecast

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"math"
	"net/http"
	"time"

	"weatherstation/internal/database"
)

// 彩云返回结构（仅取用需要的字段）
type caiyunHourly struct {
	Status string `json:"status"`
	Result struct {
		Hourly struct {
			Status      string `json:"status"`
			Temperature []struct {
				Datetime string  `json:"datetime"`
				Value    float64 `json:"value"`
			} `json:"temperature"`
			Humidity []struct {
				Datetime string  `json:"datetime"`
				Value    float64 `json:"value"`
			} `json:"humidity"`
			Wind []struct {
				Datetime  string  `json:"datetime"`
				Speed     float64 `json:"speed"`
				Direction float64 `json:"direction"`
			} `json:"wind"`
			Precipitation []struct {
				Datetime    string  `json:"datetime"`
				Value       float64 `json:"value"`
				Probability float64 `json:"probability"`
			} `json:"precipitation"`
			Pressure []struct {
				Datetime string  `json:"datetime"`
				Value    float64 `json:"value"`
			} `json:"pressure"`
		} `json:"hourly"`
	} `json:"result"`
}

// RunCaiyunFetch 拉取各站点未来三小时并写入 forecast_hourly（provider=caiyun）
func RunCaiyunFetch(ctx context.Context, token string) error {
	log.Printf("彩云抓取开始，token=%s", token)
	db := database.GetDB()
	stations, err := loadStationsWithLatLon(ctx, db)
	if err != nil {
		log.Printf("加载站点失败: %v", err)
		return err
	}
	log.Printf("找到 %d 个有经纬度的站点", len(stations))

	client := &http.Client{Timeout: 15 * time.Second}
	loc, _ := time.LoadLocation("Asia/Shanghai")
	if loc == nil {
		loc = time.FixedZone("CST", 8*3600)
	}

	issuedAt := time.Now().In(loc)
	startHour := issuedAt.Truncate(time.Hour)
	// 彩云小时接口返回的是“左端点”时刻（例如 13:00 表示 13:00-14:00 区间）。
	// 我们将左端点列表保留为 startHour, startHour+1h, startHour+2h，并在写库时统一 +1h
	// 使得 forecast_time 表示区间右端，与实测聚合对齐。
	leftEdges := []time.Time{startHour, startHour.Add(1 * time.Hour), startHour.Add(2 * time.Hour)}

	for _, s := range stations {
		if !s.lat.Valid || !s.lon.Valid {
			continue
		}
		url := fmt.Sprintf("https://api.caiyunapp.com/v2.6/%s/%f,%f/hourly?hourlysteps=4&unit=metric:v2", token, s.lon.Float64, s.lat.Float64)
		log.Printf("请求彩云 API: %s", url)
		req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
		resp, err := client.Do(req)
		if err != nil {
			log.Printf("caiyun 请求失败 station=%s err=%v", s.id, err)
			continue
		}
		log.Printf("彩云响应状态码: %d", resp.StatusCode)
		var data caiyunHourly
		body, _ := io.ReadAll(resp.Body)
		log.Printf("彩云响应内容: %s", string(body))
		resp.Body.Close()

		if err := json.Unmarshal(body, &data); err != nil {
			log.Printf("caiyun 解码失败 station=%s err=%v", s.id, err)
			continue
		}

		log.Printf("彩云响应解析: status=%s", data.Status)

		// 彩云时间戳形式例如 2022-05-26T16:00+08:00，需按CST解析
		// 建立 time->vals 映射
		table := map[time.Time]struct {
			rain float64
			temp float64
			rh   float64
			ws   float64
			wdir float64
			prob float64
			pres float64
		}{}

		// 温度 ℃
		for _, t := range data.Result.Hourly.Temperature {
			log.Printf("解析时间: %s", t.Datetime)
			if ft, err := time.ParseInLocation("2006-01-02T15:04-07:00", t.Datetime, loc); err == nil {
				log.Printf("解析结果: %v", ft)
				v := table[ft]
				v.temp = t.Value
				table[ft] = v
			} else {
				log.Printf("时间解析失败: %v", err)
			}
		}
		// 湿度 比例(0..1) 转换为 %
		for _, h := range data.Result.Hourly.Humidity {
			if ft, err := time.ParseInLocation("2006-01-02T15:04-07:00", h.Datetime, loc); err == nil {
				v := table[ft]
				v.rh = h.Value * 100.0
				table[ft] = v
			}
		}
		// 风：metric:v2速度为km/h，这里转换为m/s；方向为度
		for _, w := range data.Result.Hourly.Wind {
			if ft, err := time.ParseInLocation("2006-01-02T15:04-07:00", w.Datetime, loc); err == nil {
				v := table[ft]
				v.ws = w.Speed / 3.6
				v.wdir = w.Direction
				table[ft] = v
			}
		}
		// 降水 该小时量 mm，概率 0..1 → %
		for _, p := range data.Result.Hourly.Precipitation {
			if ft, err := time.ParseInLocation("2006-01-02T15:04-07:00", p.Datetime, loc); err == nil {
				v := table[ft]
				v.rain = p.Value
				// 直接使用API返回的概率值，只进行范围限制
				prob := p.Probability
				// 四舍五入并确保在0-100范围内
				prob = math.Round(prob)
				if prob < 0 {
					prob = 0
				}
				if prob > 100 {
					prob = 100
				}
				v.prob = prob
				table[ft] = v
			}
		}
		// 气压：单位为 Pa，转换为 hPa（Pa/100）
		for _, pr := range data.Result.Hourly.Pressure {
			if ft, err := time.ParseInLocation("2006-01-02T15:04-07:00", pr.Datetime, loc); err == nil {
				v := table[ft]
				v.pres = pr.Value / 100.0
				table[ft] = v
			}
		}

		log.Printf("处理时间点(彩云左端): %v", leftEdges)
		for _, left := range leftEdges {
			v, ok := table[left]
			if !ok {
				log.Printf("时间点无数据: %s", left.Format(time.RFC3339))
				continue
			}
			ft := left.Add(1 * time.Hour)
			log.Printf("写入预报点: station=%s forecast_time=%s (source=%s) rain=%.3f temp=%.2f rh=%.1f ws=%.3f wdir=%.1f prob=%.1f pres=%.2f",
				s.id, ft.Format(time.RFC3339), left.Format(time.RFC3339), v.rain, v.temp, v.rh, v.ws, v.wdir, v.prob, v.pres)
			err := upsertForecastCaiyun(ctx, db, s.id, issuedAt, ft,
				int64(v.rain*1000.0), // mm → x1000
				int64(v.temp*100.0),  // °C → x100
				int64(v.rh),          // %
				int64(v.ws*1000.0),   // m/s → x1000
				int64(0),             // gust: 彩云小时接口无阵风，置0
				int64(v.wdir),        // 度
				int64(v.prob),        // %
				int64(v.pres*100.0),  // hPa → x100
			)
			if err != nil {
				log.Printf("写入forecast失败(caiyun) station=%s time=%s err=%v", s.id, ft.Format(time.RFC3339), err)
			} else {
				log.Printf("写入forecast成功(caiyun) station=%s time=%s", s.id, ft.Format(time.RFC3339))
			}
		}
	}
	return nil
}

func upsertForecastCaiyun(ctx context.Context, db *sql.DB, stationID string, issuedAt, forecastTime time.Time,
	rainMmX1000, tempCx100, humidityPct, wsMsX1000, gustMsX1000, wdirDeg, probPct, pressureHpaX100 int64,
) error {
	_, err := db.ExecContext(ctx, `
        INSERT INTO forecast_hourly (
            station_id, provider, issued_at, forecast_time,
            rain_mm_x1000, temp_c_x100, humidity_pct, wind_speed_ms_x1000,
            wind_gust_ms_x1000, wind_dir_deg, precip_prob_pct, pressure_hpa_x100
        ) VALUES ($1, 'caiyun', $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
        ON CONFLICT (station_id, provider, issued_at, forecast_time)
        DO UPDATE SET 
            rain_mm_x1000 = EXCLUDED.rain_mm_x1000,
            temp_c_x100 = EXCLUDED.temp_c_x100,
            humidity_pct = EXCLUDED.humidity_pct,
            wind_speed_ms_x1000 = EXCLUDED.wind_speed_ms_x1000,
            wind_gust_ms_x1000 = EXCLUDED.wind_gust_ms_x1000,
            wind_dir_deg = EXCLUDED.wind_dir_deg,
            precip_prob_pct = EXCLUDED.precip_prob_pct,
            pressure_hpa_x100 = EXCLUDED.pressure_hpa_x100
    `, stationID, issuedAt, forecastTime,
		rainMmX1000, tempCx100, humidityPct, wsMsX1000, gustMsX1000, wdirDeg, probPct, pressureHpaX100)
	return err
}
