package storage

import (
	"log"
	"time"

	"gorm.io/gorm"

	"mengyaping-backend/config"
	"mengyaping-backend/models"
)

func latestToRecord(p *MonitorProbeLatest) models.MonitorRecord {
	return models.MonitorRecord{
		WebsiteID:  p.WebsiteID,
		URLID:      p.URLID,
		URL:        p.URL,
		StatusCode: p.StatusCode,
		Latency:    p.LatencyMs,
		IsUp:       p.IsUp,
		Error:      p.ErrorText,
		CheckedAt:  p.CheckedAt,
	}
}

// AddRecord 写入最新探测 + 按小时/按天汇总（不插逐条历史）
func (s *Storage) AddRecord(record models.MonitorRecord) error {
	hourAt := record.CheckedAt.Truncate(time.Hour)
	statDate := time.Date(record.CheckedAt.Year(), record.CheckedAt.Month(), record.CheckedAt.Day(),
		0, 0, 0, 0, record.CheckedAt.Location())
	upDelta := 0
	if record.IsUp {
		upDelta = 1
	}

	return s.db.Transaction(func(tx *gorm.DB) error {
		lp := MonitorProbeLatest{
			WebsiteID:  record.WebsiteID,
			URLID:      record.URLID,
			URL:        record.URL,
			StatusCode: record.StatusCode,
			LatencyMs:  record.Latency,
			IsUp:       record.IsUp,
			ErrorText:  record.Error,
			CheckedAt:  record.CheckedAt,
		}
		if err := tx.Save(&lp).Error; err != nil {
			return err
		}
		if err := tx.Exec(`
INSERT INTO monitor_probe_hour (website_id, url_id, hour_at, probe_count, up_count, latency_sum)
VALUES (?, ?, ?, 1, ?, ?)
ON DUPLICATE KEY UPDATE
  probe_count = probe_count + 1,
  up_count = up_count + VALUES(up_count),
  latency_sum = latency_sum + VALUES(latency_sum)
`, record.WebsiteID, record.URLID, hourAt, upDelta, record.Latency).Error; err != nil {
			return err
		}
		return tx.Exec(`
INSERT INTO monitor_probe_day (website_id, url_id, stat_date, probe_count, up_count, latency_sum)
VALUES (?, ?, ?, 1, ?, ?)
ON DUPLICATE KEY UPDATE
  probe_count = probe_count + 1,
  up_count = up_count + VALUES(up_count),
  latency_sum = latency_sum + VALUES(latency_sum)
`, record.WebsiteID, record.URLID, statDate.Format("2006-01-02"), upDelta, record.Latency).Error
	})
}

// GetLatestRecord 最新一条（无记录时返回 nil；用 Find 而非 First，避免 GORM 将「未找到」打成 record not found 日志）
func (s *Storage) GetLatestRecord(websiteID, urlID string) *models.MonitorRecord {
	var p MonitorProbeLatest
	if err := s.db.Where("website_id = ? AND url_id = ?", websiteID, urlID).Limit(1).Find(&p).Error; err != nil {
		return nil
	}
	if p.WebsiteID == "" {
		return nil
	}
	m := latestToRecord(&p)
	return &m
}

// GetRecords 兼容旧接口：按小时聚合展开为「代表点」（每探测窗口 1 条），仅用于仍依赖 slice 的逻辑；新逻辑请用汇总 API
func (s *Storage) GetRecords(websiteID, urlID string, since time.Time) []models.MonitorRecord {
	rows := s.getProbeHourRows(websiteID, urlID, since)
	var out []models.MonitorRecord
	for _, h := range rows {
		if h.ProbeCount <= 0 {
			continue
		}
		avgLat := h.LatencySum / int64(h.ProbeCount)
		isUp := h.UpCount*2 >= h.ProbeCount
		out = append(out, models.MonitorRecord{
			WebsiteID:  websiteID,
			URLID:      urlID,
			StatusCode: 0,
			Latency:    avgLat,
			IsUp:       isUp,
			CheckedAt:  h.HourAt,
		})
	}
	return out
}

func (s *Storage) getProbeHourRows(websiteID, urlID string, since time.Time) []MonitorProbeHour {
	var rows []MonitorProbeHour
	s.db.Where("website_id = ? AND url_id = ? AND hour_at >= ?", websiteID, urlID, since).
		Order("hour_at ASC").Find(&rows)
	return rows
}

// GetProbeHourRowsForURL 某 URL 自 since 起的小时汇总行（升序）
func (s *Storage) GetProbeHourRowsForURL(websiteID, urlID string, since time.Time) []MonitorProbeHour {
	return s.getProbeHourRows(websiteID, urlID, since)
}

// GetProbeLatestKeyMap 全表 latest（每 URL 一行），key = website_id + "\x00" + url_id
func (s *Storage) GetProbeLatestKeyMap() map[string]models.MonitorRecord {
	var rows []MonitorProbeLatest
	if err := s.db.Find(&rows).Error; err != nil {
		log.Printf("GetProbeLatestKeyMap: %v", err)
		return map[string]models.MonitorRecord{}
	}
	m := make(map[string]models.MonitorRecord, len(rows))
	for i := range rows {
		k := rows[i].WebsiteID + "\x00" + rows[i].URLID
		m[k] = latestToRecord(&rows[i])
	}
	return m
}

// GroupProbeHoursSince 近窗口内全部小时汇总，按站点、URL 分组（列表接口一次查出）
func (s *Storage) GroupProbeHoursSince(since time.Time) map[string]map[string][]MonitorProbeHour {
	var rows []MonitorProbeHour
	if err := s.db.Where("hour_at >= ?", since).Order("website_id, url_id, hour_at").Find(&rows).Error; err != nil {
		log.Printf("GroupProbeHoursSince: %v", err)
		return map[string]map[string][]MonitorProbeHour{}
	}
	out := make(map[string]map[string][]MonitorProbeHour)
	for i := range rows {
		h := rows[i]
		if out[h.WebsiteID] == nil {
			out[h.WebsiteID] = make(map[string][]MonitorProbeHour)
		}
		out[h.WebsiteID][h.URLID] = append(out[h.WebsiteID][h.URLID], h)
	}
	return out
}

// LoadAllWebsiteDayRollups 自 since 日起各站每日汇总 + 90d 整体可用率（单条 SQL 聚合）
func (s *Storage) LoadAllWebsiteDayRollups(since time.Time) (map[string][]models.DailyStats, map[string]float64) {
	sinceDay := time.Date(since.Year(), since.Month(), since.Day(), 0, 0, 0, 0, since.Location())
	sinceStr := sinceDay.Format("2006-01-02")
	type aggRow struct {
		Wid string    `gorm:"column:wid"`
		D   time.Time `gorm:"column:d"`
		Pc  int64     `gorm:"column:pc"`
		Uc  int64     `gorm:"column:uc"`
		Ls  int64     `gorm:"column:ls"`
	}
	var agg []aggRow
	if err := s.db.Raw(`
SELECT website_id AS wid, stat_date AS d, SUM(probe_count) AS pc, SUM(up_count) AS uc, SUM(latency_sum) AS ls
FROM monitor_probe_day
WHERE stat_date >= ?
GROUP BY website_id, stat_date
ORDER BY website_id, stat_date
`, sinceStr).Scan(&agg).Error; err != nil {
		log.Printf("LoadAllWebsiteDayRollups: %v", err)
		return map[string][]models.DailyStats{}, map[string]float64{}
	}

	daily := make(map[string][]models.DailyStats)
	totP := make(map[string]int64)
	totU := make(map[string]int64)
	for _, r := range agg {
		st := models.DailyStats{
			Date:       r.D,
			TotalCount: int(r.Pc),
			UpCount:    int(r.Uc),
		}
		if r.Pc > 0 {
			st.AvgLatency = r.Ls / r.Pc
			st.Uptime = float64(r.Uc) / float64(r.Pc) * 100
		}
		daily[r.Wid] = append(daily[r.Wid], st)
		totP[r.Wid] += r.Pc
		totU[r.Wid] += r.Uc
	}
	uptime := make(map[string]float64)
	for wid, p := range totP {
		if p > 0 {
			uptime[wid] = float64(totU[wid]) / float64(p) * 100
		}
	}
	return daily, uptime
}

// GetProbeHourTotals 某 URL 在时间窗口内汇总（来自小时表）
func (s *Storage) GetProbeHourTotals(websiteID, urlID string, since time.Time) (probeTotal int64, upTotal int64, latencySum int64) {
	type row struct {
		P int64
		U int64
		L int64
	}
	var r row
	s.db.Model(&MonitorProbeHour{}).
		Select("COALESCE(SUM(probe_count),0), COALESCE(SUM(up_count),0), COALESCE(SUM(latency_sum),0)").
		Where("website_id = ? AND url_id = ? AND hour_at >= ?", websiteID, urlID, since).
		Scan(&r)
	return r.P, r.U, r.L
}

// GetHourlyStatsForURL 7 天折线用（已聚合）
func (s *Storage) GetHourlyStatsForURL(websiteID, urlID string, since time.Time) []models.HourlyStats {
	rows := s.getProbeHourRows(websiteID, urlID, since)
	out := make([]models.HourlyStats, 0, len(rows))
	for _, h := range rows {
		st := models.HourlyStats{
			Hour:       h.HourAt,
			TotalCount: h.ProbeCount,
			UpCount:    h.UpCount,
		}
		if h.ProbeCount > 0 {
			st.AvgLatency = h.LatencySum / int64(h.ProbeCount)
			st.Uptime = float64(h.UpCount) / float64(h.ProbeCount) * 100
		}
		out = append(out, st)
	}
	return out
}

// GetWebsiteDailyAggregates 站点维度按日合并（多 URL 汇总到同一天）
func (s *Storage) GetWebsiteDailyAggregates(websiteID string, since time.Time) []models.DailyStats {
	type aggRow struct {
		D  time.Time `gorm:"column:d"`
		Pc int64     `gorm:"column:pc"`
		Uc int64     `gorm:"column:uc"`
		Ls int64     `gorm:"column:ls"`
	}
	var rows []aggRow
	sinceDay := time.Date(since.Year(), since.Month(), since.Day(), 0, 0, 0, 0, since.Location())
	s.db.Raw(`
SELECT stat_date AS d, SUM(probe_count) AS pc, SUM(up_count) AS uc, SUM(latency_sum) AS ls
FROM monitor_probe_day
WHERE website_id = ? AND stat_date >= ?
GROUP BY stat_date
ORDER BY stat_date ASC
`, websiteID, sinceDay.Format("2006-01-02")).Scan(&rows)

	out := make([]models.DailyStats, 0, len(rows))
	for _, r := range rows {
		st := models.DailyStats{
			Date:       r.D,
			TotalCount: int(r.Pc),
			UpCount:    int(r.Uc),
		}
		if r.Pc > 0 {
			st.AvgLatency = r.Ls / r.Pc
			st.Uptime = float64(r.Uc) / float64(r.Pc) * 100
		}
		out = append(out, st)
	}
	return out
}

// GetWebsiteDayProbeTotals 整站在日期范围内探测次数汇总（用于 90 天可用率）
func (s *Storage) GetWebsiteDayProbeTotals(websiteID string, since time.Time) (probeTotal int64, upTotal int64) {
	sinceDay := time.Date(since.Year(), since.Month(), since.Day(), 0, 0, 0, 0, since.Location())
	type row struct {
		P int64
		U int64
	}
	var r row
	s.db.Model(&MonitorProbeDay{}).
		Select("COALESCE(SUM(probe_count),0), COALESCE(SUM(up_count),0)").
		Where("website_id = ? AND stat_date >= ?", websiteID, sinceDay.Format("2006-01-02")).
		Scan(&r)
	return r.P, r.U
}

func (s *Storage) purgeProbeRollups() {
	cfg := config.GetConfig()
	now := time.Now()
	dayCutoff := now.AddDate(0, 0, -cfg.Monitor.HistoryDays).Truncate(24 * time.Hour)
	if err := s.db.Where("stat_date < ?", dayCutoff.Format("2006-01-02")).Delete(&MonitorProbeDay{}).Error; err != nil {
		log.Printf("清理 monitor_probe_day: %v", err)
	}
	// 小时图保留约 10 天（覆盖 7 天曲线 + 余量）
	hourCutoff := now.Add(-10 * 24 * time.Hour).Truncate(time.Hour)
	if err := s.db.Where("hour_at < ?", hourCutoff).Delete(&MonitorProbeHour{}).Error; err != nil {
		log.Printf("清理 monitor_probe_hour: %v", err)
	}
}
