// service/singleton/domain.go
package singleton

import (
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net"
	"net/http"
	"slices"
	"strconv"
	"strings"
	"time"

	"github.com/nezhahq/nezha/model"
	"gorm.io/datatypes"

	whois "github.com/likexian/whois"
	whoisparser "github.com/likexian/whois-parser"
)


// SyncDomainPrice 从 哪煮米(nazhumi.com) 获取域名续费价格
func SyncDomainPrice(billing *model.BillingDataMod, domainName string) {
	// 获取 TLD
	parts := strings.Split(domainName, ".")
	if len(parts) < 2 {
		return
	}
	tld := parts[len(parts)-1]

	// 匹配注册商代码 (简单启示式匹配)
	registrarCode := ""
	regNameLower := strings.ToLower(billing.Registrar)
	
	// 这里可以扩展更多的映射关系
	mapping := map[string]string{
		"aliyun": "aliyun", "tencent": "tencent", "cloudflare": "cloudflare",
		"namesilo": "namesilo", "porkbun": "porkbun", "dynadot": "dynadot",
		"google": "google", "namecheap": "namecheap", "godaddy": "godaddy",
		"spaceship": "spaceship", "huawei": "huawei", "baidu": "baidu",
		"volcengine": "volcengine", "juming": "juming", "quyu": "quyu",
		"west": "west", "xinnet": "xinnet", "ename": "ename",
	}

	for key, code := range mapping {
		if strings.Contains(regNameLower, key) {
			registrarCode = code
			break
		}
	}

	if registrarCode == "" {
		// 备选方案：去除常用后缀和空格
		registrarCode = strings.ReplaceAll(regNameLower, " ", "")
		registrarCode = strings.ReplaceAll(registrarCode, "inc", "")
		registrarCode = strings.ReplaceAll(registrarCode, "llc", "")
		registrarCode = strings.ReplaceAll(registrarCode, ".", "")
		registrarCode = strings.ReplaceAll(registrarCode, ",", "")
	}

	apiURL := fmt.Sprintf("https://www.nazhumi.com/api/v1?registrar=%s&domain=%s", registrarCode, tld)
	client := &http.Client{Timeout: 5 * time.Second}
	resp, err := client.Get(apiURL)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	var results []struct {
		Renew    interface{} `json:"renew"`
		Currency string      `json:"currency"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&results); err != nil || len(results) == 0 {
		return
	}

	// 转换价格
	res := results[0]
	priceStr := ""
	switch v := res.Renew.(type) {
	case float64:
		priceStr = fmt.Sprintf("%.2f", v)
	case string:
		if v != "n/a" {
			priceStr = v
		}
	}

	if priceStr != "" {
		billing.RenewalPrice = fmt.Sprintf("%s %s", priceStr, res.Currency)
	}
}

// RDAPResponse 简化的 RDAP 响应结构
type RDAPResponse struct {
	Events []struct {
		EventAction string `json:"eventAction"`
		EventDate   string `json:"eventDate"`
	} `json:"events"`
	Entities []struct {
		Roles      []string      `json:"roles"`
		VcardArray []interface{} `json:"vcardArray"`
	} `json:"entities"`
}

// SyncDomainWHOIS 使用 RDAP (主要) 和 Whois (备用) 同步域名信息
func SyncDomainWHOIS(d *model.Domain) error {
	var billing model.BillingDataMod
	if d.BillingData != nil && len(d.BillingData) > 0 {
		json.Unmarshal(d.BillingData, &billing)
	}

	// 1. 尝试使用官方 RDAP 协议 (JSON格式，更可靠，无需解析正则)
	rdapSuccess := false
	apiURL := fmt.Sprintf("https://rdap.org/domain/%s", d.Domain)
	client := &http.Client{Timeout: 10 * time.Second}
	resp, err := client.Get(apiURL)
	if err == nil && resp.StatusCode == http.StatusOK {
		var rdap RDAPResponse
		if err := json.NewDecoder(resp.Body).Decode(&rdap); err == nil {
			rdapSuccess = true
			for _, event := range rdap.Events {
				switch event.EventAction {
				case "expiration":
					billing.EndDate = event.EventDate
				case "registration":
					billing.RegisteredDate = event.EventDate
				}
			}
			// 提取注册商
			for _, entity := range rdap.Entities {
				isRegistrar := false
				for _, role := range entity.Roles {
					if role == "registrar" {
						isRegistrar = true
						break
					}
				}
				if isRegistrar && len(entity.VcardArray) > 1 {
					if vcard, ok := entity.VcardArray[1].([]interface{}); ok {
						for _, field := range vcard {
							if f, ok := field.([]interface{}); ok && len(f) > 3 {
								if f[0] == "fn" {
									billing.Registrar = fmt.Sprint(f[3])
									break
								}
							}
						}
					}
				}
			}
		}
		resp.Body.Close()
	}

	// 2. 如果 RDAP 失败，回退到传统的 Whois 查询
	if !rdapSuccess {
		raw, err := whois.Whois(d.Domain)
		if err == nil {
			result, err := whoisparser.Parse(raw)
			if err == nil {
				if result.Registrar.Name != "" {
					billing.Registrar = result.Registrar.Name
				}
				if result.Domain.ExpirationDate != "" {
					billing.EndDate = result.Domain.ExpirationDate
				}
				if result.Domain.CreatedDate != "" {
					billing.RegisteredDate = result.Domain.CreatedDate
				}
			}
		}
	}

	// 3. 补充价格同步
	SyncDomainPrice(&billing, d.Domain)

	newBillingData, err := json.Marshal(billing)
	if err != nil {
		return err
	}

	d.BillingData = newBillingData
	saveErr := DB.Save(d).Error
	if saveErr != nil {
		return fmt.Errorf("数据库保存失败: %w", saveErr)
	}

	if !rdapSuccess && billing.EndDate == "" {
		return fmt.Errorf("RDAP 和 Whois 同步均失败，请检查网络或手动输入")
	}

	return nil
}

// SyncAllDomains 异步批量同步所有已验证域名的 Whois 和价格信息
func SyncAllDomains() {
	go func() {
		log.Println("NEZHA>> 开始批量同步所有域名的 Whois 和价格信息...")
		domains, err := GetDomains("admin")
		if err != nil {
			log.Printf("NEZHA>> 批量同步域名失败: %v", err)
			return
		}

		successCount := 0
		for _, d := range domains {
			if d.Status == "verified" {
				if err := SyncDomainWHOIS(&d); err != nil {
					log.Printf("NEZHA>> 域名 %s 同步失败: %v", d.Domain, err)
				} else {
					successCount++
				}
				// 避免并发过高被 API 限制
				time.Sleep(2 * time.Second)
			}
		}
		log.Printf("NEZHA>> 批量同步域名结束，成功 %d/%d", successCount, len(domains))
	}()
}

// GetDomains 获取所有域名记录
func GetDomains(scope string) ([]model.Domain, error) {
	var domains []model.Domain
	query := DB

	if scope == "public" {
		// 如果是公开访问，只返回已验证且公开的域名
		query = query.Where("status IN (?, ?) AND is_public = ?", "verified", "expired", true)
	}

	if err := query.Find(&domains).Error; err != nil {
		return nil, err
	}
	return domains, nil
}

// GetDomainByID 根据ID获取单个域名记录
func GetDomainByID(id uint64) (*model.Domain, error) {
	var domain model.Domain
	if err := DB.First(&domain, id).Error; err != nil {
		return nil, err
	}
	return &domain, nil
}

// AddDomain 添加一个新的域名，并自动生成验证Token
func AddDomain(domainName string) (*model.Domain, error) {
	b := make([]byte, 16)
	if _, err := rand.Read(b); err != nil {
		return nil, fmt.Errorf("无法生成随机Token: %w", err)
	}
	token := "nezha-verify-" + hex.EncodeToString(b)

	newDomain := &model.Domain{
		Domain:      strings.ToLower(domainName),
		VerifyToken: token,
		Status:      "pending",
	}

	if err := DB.Create(newDomain).Error; err != nil {
		return nil, err
	}

	return newDomain, nil
}

// VerifyDomain 验证域名的 TXT 记录是否正确
func VerifyDomain(id uint64) (bool, error) {
	domain, err := GetDomainByID(id) // 直接调用 GetDomainByID
	if err != nil {
		return false, err
	}
	if domain.Status == "verified" {
		return true, nil
	}

	txtRecords, err := net.LookupTXT(domain.Domain)
	if err != nil {
		var dnsErr *net.DNSError
		if errors.As(err, &dnsErr) && dnsErr.IsNotFound {
			return false, nil
		}
		return false, fmt.Errorf("DNS查询失败: %w", err)
	}

	found := false
	for _, record := range txtRecords {
		if record == domain.VerifyToken {
			domain.Status = "verified"
			found = true
			break
		}
	}

	if found {
		// 自动同步 Whois 信息
		if err := SyncDomainWHOIS(domain); err != nil {
			log.Printf("NEZHA>> 域名 %s 验证成功但 Whois 同步失败: %v", domain.Domain, err)
		}
		return true, DB.Save(domain).Error
	}

	return false, nil
}

// UpdateDomainConfig 更新指定域名的配置信息
func UpdateDomainConfig(id uint64, billingData datatypes.JSON) (*model.Domain, error) {
	domain, err := GetDomainByID(id) // 直接调用 GetDomainByID
	if err != nil {
		return nil, err
	}

	domain.BillingData = billingData
	if err := DB.Save(domain).Error; err != nil {
		return nil, err
	}
	return domain, nil
}

// UpdateDomain 更新域名信息 (重命名并增强)
func UpdateDomain(id uint64, req model.DomainUpdateRequest) (*model.Domain, error) { // 使用新的请求体
	domain, err := GetDomainByID(id)
	if err != nil {
		return nil, err
	}

	domain.IsPublic = req.IsPublic
	domain.BillingData = req.BillingData
	if err := DB.Save(domain).Error; err != nil {
		return nil, err
	}
	return domain, nil
}

// DeleteDomain 删除一个域名记录
func DeleteDomain(id uint64) error {
	return DB.Delete(&model.Domain{}, id).Error
}

func isDomainNotificationDay(daysLeft int) bool {
	daysStr := Conf.DomainExpiryNotificationDays
	if daysStr == "" {
		return slices.Contains([]int{60, 30, 15, 7, 3, 1, 0}, daysLeft+1)
	}
	parts := strings.Split(daysStr, ",")
	for _, p := range parts {
		d, err := strconv.Atoi(strings.TrimSpace(p))
		if err == nil && d == daysLeft+1 {
			return true
		}
	}
	return false
}

func isServerNotificationDay(daysLeft int) bool {
	daysStr := Conf.ServerExpiryNotificationDays
	if daysStr == "" {
		return slices.Contains([]int{30, 15, 7, 3, 1, 0}, daysLeft+1)
	}
	parts := strings.Split(daysStr, ",")
	for _, p := range parts {
		d, err := strconv.Atoi(strings.TrimSpace(p))
		if err == nil && d == daysLeft+1 {
			return true
		}
	}
	return false
}

// CronJobForDomainStatus 检查域名到期和自动续费的定时任务
func CronJobForDomainStatus() {
	log.Println("NEZHA>> Cron::开始执行域名状态检查任务")
	var domains []model.Domain
	if err := DB.Where("status = ?", "verified").Find(&domains).Error; err != nil {
		log.Printf("NEZHA>> Cron::Error fetching domains: %v", err)
		return
	}

	now := time.Now()

	for i := range domains {
		d := domains[i]
		if d.BillingData == nil {
			continue
		}

		var billing model.BillingDataMod
		if err := json.Unmarshal(d.BillingData, &billing); err != nil {
			log.Printf("NEZHA>> Cron::Error parsing billing data for domain %s: %v", d.Domain, err)
			continue
		}

		if billing.EndDate == "" {
			continue
		}

		// 处理类似 2026-10-10 甚至其他不带时区的格式
		endDateStr := billing.EndDate
		var endDate time.Time
		var err error
		if len(endDateStr) == 10 { // YYYY-MM-DD
			endDate, err = time.Parse("2006-01-02", endDateStr)
		} else if len(endDateStr) == 19 && endDateStr[10] == ' ' { // YYYY-MM-DD HH:MM:SS
			endDate, err = time.Parse("2006-01-02 15:04:05", endDateStr)
		} else {
			endDate, err = time.Parse(time.RFC3339, endDateStr)
		}

		if err != nil {
			log.Printf("NEZHA>> Cron::Error parsing end date for domain %s: %v", d.Domain, err)
			continue
		}

		daysLeft := int(endDate.Sub(now).Hours() / 24)

		// 只有在到期前一定天数通知，且避开重复通知 (简单逻辑：每天通知一次)
		if Conf.ExpiryNotificationGroupID != 0 && isDomainNotificationDay(daysLeft) {
			msg := ""
			if daysLeft+1 > 0 {
				msg = fmt.Sprintf("域名 [%s] 即将到期，剩余 %d 天。到期时间: %s", d.Domain, daysLeft+1, endDate.Format("2006-01-02"))
			} else {
				msg = fmt.Sprintf("域名 [%s] 已到期！到期时间: %s", d.Domain, endDate.Format("2006-01-02"))
			}
			NotificationShared.SendNotification(Conf.ExpiryNotificationGroupID, msg, fmt.Sprintf("expiry-domain-%d-%d", d.ID, daysLeft))
		}

		if now.After(endDate) {
			if billing.AutoRenewal == "1" {
				var newEndDate time.Time
				renewalYears := 0
				renewalMonths := 0
				switch billing.Cycle {
				case "年":
					renewalYears = 1
				case "月":
					renewalMonths = 1
				default:
					log.Printf("NEZHA>> Cron::未知续费周期 '%s' for domain %s", billing.Cycle, d.Domain)
					continue
				}

				newEndDate = endDate.AddDate(renewalYears, renewalMonths, 0)
				billing.EndDate = newEndDate.Format(time.RFC3339)
				newBillingData, _ := json.Marshal(billing)
				d.BillingData = newBillingData
				log.Printf("NEZHA>> Cron::域名 %s 已自动续费至 %s", d.Domain, billing.EndDate)
				if err := DB.Save(&d).Error; err != nil {
					log.Printf("NEZHA>> Cron::Error saving auto-renewed domain %s: %v", d.Domain, err)
				}
			} else {
				d.Status = "expired"
				log.Printf("NEZHA>> Cron::域名 %s 已过期", d.Domain)
				if err := DB.Save(&d).Error; err != nil {
					log.Printf("NEZHA>> Cron::Error marking domain %s as expired: %v", d.Domain, err)
				}
			}
		}
	}
	log.Println("NEZHA>> Cron::域名状态检查任务执行完毕")
}

// CronJobForServerStatus 检查服务器/VPS 到期通知
func CronJobForServerStatus() {
	log.Println("NEZHA>> Cron::开始执行服务器到期检查任务")
	var servers []model.Server
	if err := DB.Find(&servers).Error; err != nil {
		log.Printf("NEZHA>> Cron::Error fetching servers: %v", err)
		return
	}

	now := time.Now()

	for i := range servers {
		s := servers[i]
		var pn struct {
			BillingDataMod struct {
				EndDate string `json:"endDate"`
			} `json:"billingDataMod"`
		}
		if s.PublicNote != "" {
			_ = json.Unmarshal([]byte(s.PublicNote), &pn)
		}
		if pn.BillingDataMod.EndDate == "" {
			continue
		}

		// 忽略前端默认生成的空日期
		if strings.HasPrefix(pn.BillingDataMod.EndDate, "0000-00-00") {
			continue
		}

		// 处理类似 2026-10-10 甚至其他不带时区的格式
		endDateStr := pn.BillingDataMod.EndDate
		var endDate time.Time
		var err error
		if len(endDateStr) == 10 { // YYYY-MM-DD
			endDate, err = time.Parse("2006-01-02", endDateStr)
		} else if len(endDateStr) == 19 && endDateStr[10] == ' ' { // YYYY-MM-DD HH:MM:SS
			endDate, err = time.Parse("2006-01-02 15:04:05", endDateStr)
		} else {
			endDate, err = time.Parse(time.RFC3339, endDateStr)
		}

		if err != nil {
			log.Printf("NEZHA>> Cron::Error parsing end date for VPS %s: %v", s.Name, err)
			continue
		}

		daysLeft := int(endDate.Sub(now).Hours() / 24)

		if Conf.ExpiryNotificationGroupID != 0 && isServerNotificationDay(daysLeft) {
			msg := ""
			if daysLeft+1 > 0 {
				msg = fmt.Sprintf("VPS [%s] 即将到期，剩余 %d 天。到期时间: %s", s.Name, daysLeft+1, endDate.Format("2006-01-02"))
			} else {
				msg = fmt.Sprintf("VPS [%s] 已到期！到期时间: %s", s.Name, endDate.Format("2006-01-02"))
			}
			NotificationShared.SendNotification(Conf.ExpiryNotificationGroupID, msg, fmt.Sprintf("expiry-server-%d-%d", s.ID, daysLeft), &s)
		}
	}
	log.Println("NEZHA>> Cron::服务器到期检查任务执行完毕")
}
