package server

import (
	"database/sql"
	"encoding/binary"
	"net/http"
	"time"
	"weatherstation/core/internal/data"

	"github.com/gin-gonic/gin"
)

type rainTileRecord struct {
	DT     time.Time
	Z      int
	Y      int
	X      int
	Width  int
	Height int
	West   float64
	South  float64
	East   float64
	North  float64
	ResDeg float64
	Data   []byte
}

type rainTileResponse struct {
	DT     string       `json:"dt"`
	Z      int          `json:"z"`
	Y      int          `json:"y"`
	X      int          `json:"x"`
	Width  int          `json:"width"`
	Height int          `json:"height"`
	West   float64      `json:"west"`
	South  float64      `json:"south"`
	East   float64      `json:"east"`
	North  float64      `json:"north"`
	ResDeg float64      `json:"res_deg"`
	Values [][]*float64 `json:"values"`
}

func handleRainTimes(c *gin.Context) {
	z := parseInt(c.Query("z"), 7)
	y := parseInt(c.Query("y"), 40)
	x := parseInt(c.Query("x"), 102)
	fromStr := c.Query("from")
	toStr := c.Query("to")
	loc, _ := time.LoadLocation("Asia/Shanghai")
	if loc == nil {
		loc = time.FixedZone("CST", 8*3600)
	}

	var rows *sql.Rows
	var err error
	if fromStr != "" && toStr != "" {
		from, err1 := time.ParseInLocation("2006-01-02 15:04:05", fromStr, loc)
		to, err2 := time.ParseInLocation("2006-01-02 15:04:05", toStr, loc)
		if err1 != nil || err2 != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "invalid time range"})
			return
		}
		const qRange = `SELECT dt FROM rain_tiles WHERE z=$1 AND y=$2 AND x=$3 AND dt BETWEEN $4 AND $5 ORDER BY dt DESC`
		rows, err = data.DB().Query(qRange, z, y, x, from, to)
	} else {
		limit := parseInt(c.Query("limit"), 48)
		const q = `SELECT dt FROM rain_tiles WHERE z=$1 AND y=$2 AND x=$3 ORDER BY dt DESC LIMIT $4`
		rows, err = data.DB().Query(q, z, y, x, limit)
	}
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "query failed"})
		return
	}
	defer rows.Close()
	var times []string
	for rows.Next() {
		var dt time.Time
		if err := rows.Scan(&dt); err == nil {
			times = append(times, dt.In(loc).Format("2006-01-02 15:04:05"))
		}
	}
	c.JSON(http.StatusOK, gin.H{"times": times})
}

func handleRainTilesAt(c *gin.Context) {
	z := parseInt(c.Query("z"), 7)
	dtStr := c.Query("dt")
	if dtStr == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "missing dt"})
		return
	}
	loc, _ := time.LoadLocation("Asia/Shanghai")
	if loc == nil {
		loc = time.FixedZone("CST", 8*3600)
	}
	dt, err := time.ParseInLocation("2006-01-02 15:04:05", dtStr, loc)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid dt"})
		return
	}

	const q = `SELECT dt,z,y,x,width,height,west,south,east,north,res_deg,data FROM rain_tiles WHERE z=$1 AND dt=$2 ORDER BY y,x`
	rows, qerr := data.DB().Query(q, z, dt)
	if qerr != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "db failed"})
		return
	}
	defer rows.Close()

	var tiles []rainTileResponse
	for rows.Next() {
		var r rainTileRecord
		if err := rows.Scan(&r.DT, &r.Z, &r.Y, &r.X, &r.Width, &r.Height, &r.West, &r.South, &r.East, &r.North, &r.ResDeg, &r.Data); err != nil {
			continue
		}
		w, h := r.Width, r.Height
		if w <= 0 || h <= 0 || len(r.Data) < w*h*2 {
			continue
		}
		vals := decodeRain(r.Data, w, h)
		tiles = append(tiles, rainTileResponse{DT: r.DT.In(loc).Format("2006-01-02 15:04:05"), Z: r.Z, Y: r.Y, X: r.X, Width: w, Height: h, West: r.West, South: r.South, East: r.East, North: r.North, ResDeg: r.ResDeg, Values: vals})
	}
	if len(tiles) == 0 {
		c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
		return
	}
	c.JSON(http.StatusOK, gin.H{"tiles": tiles})
}

func decodeRain(buf []byte, w, h int) [][]*float64 {
	vals := make([][]*float64, h)
	off := 0
	for row := 0; row < h; row++ {
		rowVals := make([]*float64, w)
		for col := 0; col < w; col++ {
			v := int16(binary.BigEndian.Uint16(buf[off : off+2]))
			off += 2
			if v >= 32766 {
				rowVals[col] = nil
				continue
			}
			mm := float64(v) / 10.0
			if mm < 0 {
				mm = 0
			}
			vv := mm
			rowVals[col] = &vv
		}
		vals[row] = rowVals
	}
	return vals
}
