package setting

import (
	_ "embed"
	"encoding/json"
	"fmt"
	"net/http"
	"runtime"
	"strings"
	"sync"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/yaoapp/yao/commercial"
	"github.com/yaoapp/yao/config"
	"github.com/yaoapp/yao/openapi/response"
	"github.com/yaoapp/yao/share"
	"gopkg.in/yaml.v3"
)

const cdnBase = "https://get.yaoapps.com/yao"

// update check cache (package-level, protected by mutex)
var (
	updateCache     *CheckUpdateResult
	updateCacheTime time.Time
	updateMu        sync.Mutex
	cacheTTL        = 10 * time.Minute
)

// handleSystemInfo returns aggregated system information.
// GET /setting/system?locale=zh-cn
func handleSystemInfo(c *gin.Context) {
	locale := strings.ToLower(c.DefaultQuery("locale", "en-us"))

	env := share.App.Option["env"]
	environment, _ := env.(string)
	if environment == "" {
		environment = config.Conf.Mode
	}
	if environment == "" {
		environment = "production"
	}

	listen := fmt.Sprintf("%s:%d", config.Conf.Host, config.Conf.Port)
	sessionStore := config.Conf.Session.Store
	if sessionStore == "" {
		sessionStore = "file"
	}

	lang := langFromLocale(locale)
	lic := commercial.License
	deployment := lic.Edition
	if deployment == "" {
		deployment = "community"
	}

	var licenseKey string
	if lic.Valid && lic.SerialNumber != "" {
		licenseKey = lic.SerialNumber
	}

	data := SystemInfoData{
		App: AppInfo{
			Name:        share.App.Name,
			Short:       share.App.Short,
			Description: share.App.Description,
			Logo:        "/api/__yao/app/icons/app.png",
			Version:     share.App.Version,
		},
		Deployment:       deployment,
		DeploymentLabel:  resolveLabel(promFile.Labels.Deployment, deployment, lang, deployment),
		LicenseKey:       licenseKey,
		Environment:      environment,
		EnvironmentLabel: resolveLabel(promFile.Labels.Environment, environment, lang, environment),
		Server: VersionInfo{
			Version:   share.VERSION,
			BuildDate: share.PRVERSION,
			CommitSHA: share.PRVERSION,
		},
		Client: VersionInfo{
			Version:   share.CUI,
			BuildDate: share.PRCUI,
			CommitSHA: share.PRCUI,
		},
		Technical: TechnicalInfo{
			Listen:       listen,
			DBDriver:     config.Conf.DB.Driver,
			SessionStore: sessionStore,
		},
		Promotions: buildPromotions(deployment, locale),
	}

	response.RespondWithSuccess(c, http.StatusOK, data)
}

//go:embed promotions.yml
var promotionsYML []byte

type promotionEntry struct {
	ID   string                     `yaml:"id"`
	Link string                     `yaml:"link"`
	I18n map[string]promotionLocale `yaml:"i18n"`
}

type promotionLocale struct {
	Title string `yaml:"title"`
	Desc  string `yaml:"desc"`
	Label string `yaml:"label"`
}

type promotionsFile struct {
	Labels struct {
		Deployment  map[string]map[string]string `yaml:"deployment"`
		Environment map[string]map[string]string `yaml:"environment"`
	} `yaml:"labels"`
	Community  []promotionEntry `yaml:"community"`
	Enterprise []promotionEntry `yaml:"enterprise"`
	Cloud      []promotionEntry `yaml:"cloud"`
}

var promFile promotionsFile

func init() {
	yaml.Unmarshal(promotionsYML, &promFile)
}

func resolveLabel(m map[string]map[string]string, key, lang, fallback string) string {
	if langs, ok := m[key]; ok {
		if v, ok := langs[lang]; ok {
			return v
		}
		if v, ok := langs["en"]; ok {
			return v
		}
	}
	return fallback
}

func langFromLocale(locale string) string {
	if strings.HasPrefix(locale, "zh") {
		return "zh"
	}
	return "en"
}

func buildPromotions(deployment, locale string) []Promotion {
	lang := langFromLocale(locale)

	var entries []promotionEntry
	switch deployment {
	case "community":
		entries = promFile.Community
	case "enterprise":
		entries = promFile.Enterprise
	case "cloud":
		entries = promFile.Cloud
	}
	if len(entries) == 0 {
		return nil
	}

	promos := make([]Promotion, 0, len(entries))
	for _, e := range entries {
		loc, ok := e.I18n[lang]
		if !ok {
			loc = e.I18n["en"]
		}
		promos = append(promos, Promotion{
			ID:    e.ID,
			Title: loc.Title,
			Desc:  loc.Desc,
			Link:  e.Link,
			Label: loc.Label,
		})
	}
	return promos
}

// handleSystemCheckUpdate checks for a newer engine release.
// Uses the same CDN source as `yao upgrade` and yao-desktop:
//
//	GET https://get.yaoapps.com/yao/latest.json
//
// POST /setting/system/check-update
func handleSystemCheckUpdate(c *gin.Context) {
	updateMu.Lock()
	if updateCache != nil && time.Since(updateCacheTime) < cacheTTL {
		result := *updateCache
		updateMu.Unlock()
		response.RespondWithSuccess(c, http.StatusOK, result)
		return
	}
	updateMu.Unlock()

	result := fetchLatestVersion()

	updateMu.Lock()
	updateCache = &result
	updateCacheTime = time.Now()
	updateMu.Unlock()

	response.RespondWithSuccess(c, http.StatusOK, result)
}

// cdnLatest mirrors the JSON structure of get.yaoapps.com/yao/latest.json
// (same format used by cmd/upgrade.go and yao-desktop updater.rs).
type cdnLatest struct {
	Version    string            `json:"version"`
	ReleasedAt string            `json:"released_at"`
	Assets     map[string]string `json:"assets"`
}

func fetchLatestVersion() CheckUpdateResult {
	current := strings.TrimPrefix(share.VERSION, "v")
	base := CheckUpdateResult{HasUpdate: false, CurrentVersion: current}

	url := cdnBase + "/latest.json"
	client := &http.Client{Timeout: 15 * time.Second}
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return base
	}
	req.Header.Set("User-Agent", fmt.Sprintf("yao/%s", share.VERSION))

	resp, err := client.Do(req)
	if err != nil {
		return base
	}
	defer resp.Body.Close()

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

	var data cdnLatest
	if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
		return base
	}

	latest := strings.TrimPrefix(data.Version, "v")
	if latest == "" {
		return base
	}

	platformKey := fmt.Sprintf("%s-%s", runtime.GOOS, runtime.GOARCH)
	downloadURL := data.Assets[platformKey]

	return CheckUpdateResult{
		HasUpdate:      latest != current,
		CurrentVersion: current,
		LatestVersion:  latest,
		DownloadURL:    downloadURL,
	}
}
