package controller

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"one-api/setting/console_setting"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"golang.org/x/sync/errgroup"
)

type UptimeKumaMonitor struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
	Type string `json:"type"`
}

type UptimeKumaGroup struct {
	ID          int                  `json:"id"`
	Name        string               `json:"name"`
	Weight      int                  `json:"weight"`
	MonitorList []UptimeKumaMonitor  `json:"monitorList"`
}

type UptimeKumaHeartbeat struct {
	Status int      `json:"status"`
	Time   string   `json:"time"`
	Msg    string   `json:"msg"`
	Ping   *float64 `json:"ping"`
}

type UptimeKumaStatusResponse struct {
	PublicGroupList []UptimeKumaGroup `json:"publicGroupList"`
}

type UptimeKumaHeartbeatResponse struct {
	HeartbeatList map[string][]UptimeKumaHeartbeat `json:"heartbeatList"`
	UptimeList    map[string]float64               `json:"uptimeList"`
}

type MonitorStatus struct {
	Name   string  `json:"name"`
	Uptime float64 `json:"uptime"`
	Status int     `json:"status"`
}

var (
	ErrUpstreamNon200 = errors.New("upstream non-200")
	ErrTimeout        = errors.New("context deadline exceeded")
)

func getAndDecode(ctx context.Context, client *http.Client, url string, dest interface{}) error {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return err
	}

	resp, err := client.Do(req)
	if err != nil {
		if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
			return ErrTimeout
		}
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return ErrUpstreamNon200
	}

	return json.NewDecoder(resp.Body).Decode(dest)
}

func GetUptimeKumaStatus(c *gin.Context) {
	cs := console_setting.GetConsoleSetting()
	uptimeKumaUrl := cs.UptimeKumaUrl
	slug := cs.UptimeKumaSlug

	if uptimeKumaUrl == "" || slug == "" {
		c.JSON(http.StatusOK, gin.H{
			"success": true,
			"message": "",
			"data":    []MonitorStatus{},
		})
		return
	}

	uptimeKumaUrl = strings.TrimSuffix(uptimeKumaUrl, "/")

	ctx, cancel := context.WithTimeout(c.Request.Context(), 10*time.Second)
	defer cancel()

	client := &http.Client{}

	statusPageUrl := fmt.Sprintf("%s/api/status-page/%s", uptimeKumaUrl, slug)
	heartbeatUrl := fmt.Sprintf("%s/api/status-page/heartbeat/%s", uptimeKumaUrl, slug)

	var (
		statusData    UptimeKumaStatusResponse
		heartbeatData UptimeKumaHeartbeatResponse
	)

	g, gCtx := errgroup.WithContext(ctx)

	g.Go(func() error {
		return getAndDecode(gCtx, client, statusPageUrl, &statusData)
	})

	g.Go(func() error {
		return getAndDecode(gCtx, client, heartbeatUrl, &heartbeatData)
	})

	if err := g.Wait(); err != nil {
		switch err {
		case ErrUpstreamNon200:
			c.JSON(http.StatusBadRequest, gin.H{
				"success": false,
				"message": "上游接口出现问题",
			})
		case ErrTimeout:
			c.JSON(http.StatusRequestTimeout, gin.H{
				"success": false,
				"message": "请求上游接口超时",
			})
		default:
			c.JSON(http.StatusBadRequest, gin.H{
				"success": false,
				"message": err.Error(),
			})
		}
		return
	}

	var monitors []MonitorStatus
	for _, group := range statusData.PublicGroupList {
		for _, monitor := range group.MonitorList {
			monitorStatus := MonitorStatus{
				Name:   monitor.Name,
				Uptime: 0.0,
				Status: 0,
			}

			uptimeKey := fmt.Sprintf("%d_24", monitor.ID)
			if uptime, exists := heartbeatData.UptimeList[uptimeKey]; exists {
				monitorStatus.Uptime = uptime
			}

			heartbeatKey := fmt.Sprintf("%d", monitor.ID)
			if heartbeats, exists := heartbeatData.HeartbeatList[heartbeatKey]; exists && len(heartbeats) > 0 {
				latestHeartbeat := heartbeats[0]
				monitorStatus.Status = latestHeartbeat.Status
			}

			monitors = append(monitors, monitorStatus)
		}
	}

	c.JSON(http.StatusOK, gin.H{
		"success": true,
		"message": "",
		"data":    monitors,
	})
} 