package handler

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"
	"xboard-go/internal/model"
	"xboard-go/internal/protocol"
	"xboard-go/internal/service"

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

func ClientSubscribe(c *gin.Context) {
	user, ok := currentUser(c)
	if !ok {
		Fail(c, http.StatusForbidden, "invalid token")
		return
	}

	FulfillSubscription(c, user)
}

func FulfillSubscription(c *gin.Context, user *model.User) {
	servers, err := service.AvailableServersForUser(user)
	if err != nil {
		Fail(c, 500, "failed to fetch servers")
		return
	}

	// 1. Filter servers
	types := c.Query("types")
	filter := c.Query("filter")
	servers = filterServers(servers, types, filter)

	ua := c.GetHeader("User-Agent")
	flag := c.Query("flag")
	uaLower := strings.ToLower(ua)

	// 2. Handle specialized configs
	if strings.Contains(uaLower, "stash") || flag == "stash" {
		config, _ := protocol.GenerateClashWithTemplate("stash", servers, *user)
		c.Header("Content-Type", "application/octet-stream")
		c.String(http.StatusOK, config)
		return
	}

	if strings.Contains(uaLower, "clashmeta") ||
		strings.Contains(uaLower, "clash meta") ||
		strings.Contains(uaLower, "metacubex") ||
		strings.Contains(uaLower, "verge") ||
		strings.Contains(uaLower, "flclash") ||
		strings.Contains(uaLower, "nekobox") ||
		flag == "clashmeta" || flag == "clash-meta" {
		config, _ := protocol.GenerateClashWithTemplate("clashmeta", servers, *user)
		c.Header("Content-Type", "application/octet-stream")
		c.String(http.StatusOK, config)
		return
	}

	if strings.Contains(ua, "Clash") || flag == "clash" {
		config, _ := protocol.GenerateClashWithTemplate("clash", servers, *user)
		c.Header("Content-Type", "application/octet-stream")
		c.String(http.StatusOK, config)
		return
	}

	if strings.Contains(uaLower, "sing-box") || flag == "sing-box" {
		config, _ := protocol.GenerateSingBox(servers, *user)
		c.Header("Content-Type", "application/json; charset=utf-8")
		c.String(http.StatusOK, config)
		return
	}

	// 3. General Subscription (Links)
	var finalLinks []string

	// 3a. Add Info Nodes (match reference behavior)
	if service.MustGetBool("show_info_to_server_enable", true) {
		finalLinks = append(finalLinks, generateInfoNodes(user)...)
	}

	// 3b. Add Actual Node Links
	if service.MustGetBool("subscribe_protocol_prefix_enable", true) {
		for i := range servers {
			prefix := strings.ToUpper(servers[i].Type)
			if !strings.HasPrefix(servers[i].Name, "["+prefix+"]") {
				servers[i].Name = "[" + prefix + "] " + servers[i].Name
			}
		}
	}

	nodeLinks := protocol.GenerateGeneralLinks(servers, *user)
	if nodeLinks != "" {
		finalLinks = append(finalLinks, strings.Split(nodeLinks, "\n")...)
	}

	c.Header("Content-Type", "text/plain; charset=utf-8")
	c.Header("Subscription-Userinfo", subscriptionUserInfo(*user))
	c.String(http.StatusOK, base64.StdEncoding.EncodeToString([]byte(strings.Join(finalLinks, "\n"))))
}

func filterServers(servers []model.Server, types, filter string) []model.Server {
	var filtered []model.Server
	allowed := make(map[string]bool)
	if types != "" && types != "all" {
		for _, t := range strings.Split(types, ",") {
			allowed[strings.TrimSpace(t)] = true
		}
	}

	for _, s := range servers {
		if len(allowed) > 0 && !allowed[s.Type] {
			continue
		}
		if filter != "" {
			if !strings.Contains(strings.ToLower(s.Name), strings.ToLower(filter)) {
				continue
			}
		}
		filtered = append(filtered, s)
	}
	return filtered
}

func generateInfoNodes(user *model.User) []string {
	var nodes []string

	// Expire Info
	expireDate := "长期有效"
	if user.ExpiredAt != nil {
		expireDate = time.Unix(*user.ExpiredAt, 0).Format("2006-01-02")
	}
	nodes = append(nodes, buildInfoLink("套餐到期："+expireDate))

	// Reset Day Info
	if days := service.GetResetDay(user); days != nil {
		nodes = append(nodes, buildInfoLink(fmt.Sprintf("距离重置： %d 天", *days)))
	}

	// Traffic Info
	remaining := int64(user.TransferEnable) - (int64(user.U) + int64(user.D))
	if remaining < 0 {
		remaining = 0
	}
	nodes = append(nodes, buildInfoLink("剩余流量："+formatTraffic(remaining)))

	return nodes
}

func buildInfoLink(name string) string {
	m := map[string]string{
		"v":    "2",
		"ps":   name,
		"add":  "1.2.3.4",
		"port": "443",
		"id":   "00000000-0000-0000-0000-000000000000",
		"aid":  "0",
		"scy":  "auto",
		"net":  "tcp",
		"type": "none",
		"host": "",
		"path": "",
		"tls":  "",
	}
	b, _ := json.Marshal(m)
	return "vmess://" + base64.StdEncoding.EncodeToString(b)
}

func formatTraffic(bytes int64) string {
	const unit = 1024
	if bytes < unit {
		return fmt.Sprintf("%d B", bytes)
	}
	div, exp := int64(unit), 0
	for n := bytes / unit; n >= unit; n /= unit {
		div *= unit
		exp++
	}
	return fmt.Sprintf("%.2f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
}

func ClientAppConfigV1(c *gin.Context) {
	user, ok := currentUser(c)
	if !ok {
		Fail(c, http.StatusForbidden, "invalid token")
		return
	}

	servers, err := service.AvailableServersForUser(user)
	if err != nil {
		Fail(c, 500, "failed to fetch servers")
		return
	}

	config, _ := protocol.GenerateClash(servers, *user)
	c.Header("Content-Type", "text/yaml; charset=utf-8")
	c.String(http.StatusOK, config)
}

func ClientAppConfigV2(c *gin.Context) {
	config := gin.H{
		"app_info": gin.H{
			"app_name":        service.MustGetString("app_name", "XBoard"),
			"app_description": service.MustGetString("app_description", ""),
			"app_url":         service.GetAppURL(),
			"logo":            service.MustGetString("logo", ""),
			"version":         service.MustGetString("app_version", "1.0.0"),
		},
		"ui_config": gin.H{
			"app_primary_color":      service.MustGetString("app_primary_color", "#000000"),
			"app_home_screen_style":  service.MustGetString("app_home_screen_style", "default"),
			"app_server_list_style":  service.MustGetString("app_server_list_style", "default"),
			"app_theme_mode":         service.MustGetString("app_theme_mode", "light"),
			"app_show_traffic_chart": service.MustGetBool("app_show_traffic_chart", true),
		},
		"business_rules": gin.H{
			"enable_register":               service.MustGetBool("app_enable_register", true),
			"enable_invite_system":          service.MustGetBool("app_enable_invite_system", true),
			"enable_telegram_bot":           service.MustGetBool("telegram_bot_enable", false),
			"enable_ticket_system":          service.MustGetBool("app_enable_ticket_system", true),
			"enable_commission_system":      service.MustGetBool("app_enable_commission_system", true),
			"enable_traffic_log":            service.MustGetBool("app_enable_traffic_log", true),
			"enable_knowledge_base":         service.MustGetBool("app_enable_knowledge_base", true),
			"enable_announcements":          service.MustGetBool("app_enable_announcements", true),
			"enable_speed_test":             service.MustGetBool("app_enable_speed_test", true),
			"enable_global_search":          service.MustGetBool("app_enable_global_search", true),
			"enable_real_name_verification": service.MustGetBool("app_enable_real_name_verification", false),
		},
		"security_config": gin.H{
			"tos_url":                service.MustGetString("tos_url", ""),
			"privacy_policy_url":     service.MustGetString("app_privacy_policy_url", ""),
			"is_email_verify":        service.MustGetInt("email_verify", 0),
			"is_invite_force":        service.MustGetInt("invite_force", 0),
			"email_whitelist_suffix": service.MustGetString("email_whitelist_suffix", ""),
			"is_captcha":             service.MustGetInt("captcha_enable", 0),
			"captcha_type":           service.MustGetString("captcha_type", "recaptcha"),
			"recaptcha_site_key":     service.MustGetString("recaptcha_site_key", ""),
			"turnstile_site_key":     service.MustGetString("turnstile_site_key", ""),
		},
		"server_config": gin.H{
			"default_kernel":   service.MustGetString("default_kernel", "sing-box"),
			"default_protocol": service.MustGetString("default_protocol", "vless"),
		},
	}

	Success(c, config)
}

func ClientAppVersion(c *gin.Context) {
	ua := strings.ToLower(c.GetHeader("User-Agent"))
	if strings.Contains(ua, "tidalab/4.0.0") || strings.Contains(ua, "tunnelab/4.0.0") {
		if strings.Contains(ua, "win64") {
			Success(c, gin.H{
				"version":      service.MustGetString("windows_version", ""),
				"download_url": service.MustGetString("windows_download_url", ""),
			})
			return
		}

		Success(c, gin.H{
			"version":      service.MustGetString("macos_version", ""),
			"download_url": service.MustGetString("macos_download_url", ""),
		})
		return
	}

	Success(c, gin.H{
		"windows_version":      service.MustGetString("windows_version", ""),
		"windows_download_url": service.MustGetString("windows_download_url", ""),
		"macos_version":        service.MustGetString("macos_version", ""),
		"macos_download_url":   service.MustGetString("macos_download_url", ""),
		"android_version":      service.MustGetString("android_version", ""),
		"android_download_url": service.MustGetString("android_download_url", ""),
	})
}

func subscriptionUserInfo(user model.User) string {
	expire := int64(0)
	if user.ExpiredAt != nil {
		expire = *user.ExpiredAt
	}
	return "upload=" + strconv.FormatInt(int64(user.U), 10) +
		"; download=" + strconv.FormatInt(int64(user.D), 10) +
		"; total=" + strconv.FormatInt(int64(user.TransferEnable), 10) +
		"; expire=" + strconv.FormatInt(expire, 10)
}
