package singleton

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"strings"
)

type DDNSDomainConfig struct {
	EnableIPv4 bool
	EnableIpv6 bool
	FullDomain string
	Ipv4Addr   string
	Ipv6Addr   string
}

type DDNSProvider interface {
	// UpdateDomain Return is updated
	UpdateDomain(domainConfig *DDNSDomainConfig) bool
}

type DDNSProviderWebHook struct {
	URL           string
	RequestMethod string
	RequestBody   string
	RequestHeader string
}

func (provider DDNSProviderWebHook) UpdateDomain(domainConfig *DDNSDomainConfig) bool {
	if domainConfig == nil {
		return false
	}

	if domainConfig.FullDomain == "" {
		log.Println("NEZHA>> Failed to update an empty domain")
		return false
	}
	updated := false
	client := &http.Client{}
	if domainConfig.EnableIPv4 && domainConfig.Ipv4Addr != "" {
		url := provider.FormatWebhookString(provider.URL, domainConfig, "ipv4")
		body := provider.FormatWebhookString(provider.RequestBody, domainConfig, "ipv4")
		header := provider.FormatWebhookString(provider.RequestHeader, domainConfig, "ipv4")
		headers := strings.Split(header, "\n")
		req, err := http.NewRequest(provider.RequestMethod, url, bytes.NewBufferString(body))
		if err == nil && req != nil {
			SetStringHeadersToRequest(req, headers)
			if _, err := client.Do(req); err != nil {
				log.Printf("NEZHA>> Failed to update a domain: %s. Cause by: %s\n", domainConfig.FullDomain, err.Error())
			}
			updated = true
		}
	}
	if domainConfig.EnableIpv6 && domainConfig.Ipv6Addr != "" {
		url := provider.FormatWebhookString(provider.URL, domainConfig, "ipv6")
		body := provider.FormatWebhookString(provider.RequestBody, domainConfig, "ipv6")
		header := provider.FormatWebhookString(provider.RequestHeader, domainConfig, "ipv6")
		headers := strings.Split(header, "\n")
		req, err := http.NewRequest(provider.RequestMethod, url, bytes.NewBufferString(body))
		if err == nil && req != nil {
			SetStringHeadersToRequest(req, headers)
			if _, err := client.Do(req); err != nil {
				log.Printf("NEZHA>> Failed to update a domain: %s. Cause by: %s\n", domainConfig.FullDomain, err.Error())
			}
			updated = true
		}
	}
	return updated
}

type DDNSProviderDummy struct{}

func (provider DDNSProviderDummy) UpdateDomain(domainConfig *DDNSDomainConfig) bool {
	return false
}

type DDNSProviderCloudflare struct {
	Secret string
}

func (provider DDNSProviderCloudflare) UpdateDomain(domainConfig *DDNSDomainConfig) bool {
	if domainConfig == nil {
		return false
	}

	zoneID, err := provider.getZoneID(domainConfig.FullDomain)
	if err != nil {
		log.Printf("无法获取 zone ID: %s\n", err)
		return false
	}

	record, err := provider.findDNSRecord(zoneID, domainConfig.FullDomain)
	if err != nil {
		log.Printf("查找 DNS 记录时出错: %s\n", err)
		return false
	}

	if record == nil {
		// 添加 DNS 记录
		return provider.createDNSRecord(zoneID, domainConfig)
	} else {
		// 更新 DNS 记录
		return provider.updateDNSRecord(zoneID, record["id"].(string), domainConfig)
	}
}

func (provider DDNSProviderCloudflare) getZoneID(domain string) (string, error) {
	_, realDomain := SplitDomain(domain)
	url := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones?name=%s", realDomain)
	body, err := provider.sendRequest("GET", url, nil)
	if err != nil {
		return "", err
	}

	var res map[string]interface{}
	err = json.Unmarshal(body, &res)
	if err != nil {
		return "", err
	}

	result := res["result"].([]interface{})
	if len(result) > 0 {
		zoneID := result[0].(map[string]interface{})["id"].(string)
		return zoneID, nil
	}

	return "", fmt.Errorf("找不到 Zone ID")
}

func (provider DDNSProviderCloudflare) findDNSRecord(zoneID string, domain string) (map[string]interface{}, error) {
	url := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records?type=A&name=%s", zoneID, domain)
	body, err := provider.sendRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}

	var res map[string]interface{}
	err = json.Unmarshal(body, &res)
	if err != nil {
		return nil, err
	}

	result := res["result"].([]interface{})
	if len(result) > 0 {
		return result[0].(map[string]interface{}), nil
	}

	return nil, nil // 没有找到 DNS 记录
}

func (provider DDNSProviderCloudflare) createDNSRecord(zoneID string, domainConfig *DDNSDomainConfig) bool {
	url := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records", zoneID)
	data := map[string]interface{}{
		"type":    "A",
		"name":    domainConfig.FullDomain,
		"content": domainConfig.Ipv4Addr,
		"ttl":     3600,
		"proxied": false,
	}
	jsonData, _ := json.Marshal(data)
	_, err := provider.sendRequest("POST", url, jsonData)
	return err == nil
}

func (provider DDNSProviderCloudflare) updateDNSRecord(zoneID string, recordID string, domainConfig *DDNSDomainConfig) bool {
	url := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records/%s", zoneID, recordID)
	data := map[string]interface{}{
		"type":    "A",
		"name":    domainConfig.FullDomain,
		"content": domainConfig.Ipv4Addr,
		"ttl":     3600,
		"proxied": false,
	}
	jsonData, _ := json.Marshal(data)
	_, err := provider.sendRequest("PATCH", url, jsonData)
	return err == nil
}

// 以下为辅助方法,如发送 HTTP 请求等
func (provider DDNSProviderCloudflare) sendRequest(method string, url string, data []byte) ([]byte, error) {
	client := &http.Client{}
	req, err := http.NewRequest(method, url, bytes.NewBuffer(data))
	if err != nil {
		return nil, err
	}

	req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", provider.Secret))
	req.Header.Add("Content-Type", "application/json")

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer func(Body io.ReadCloser) {
		err := Body.Close()
		if err != nil {
			log.Printf("NEZHA>> 无法关闭HTTP响应体流: %s\n", err.Error())
		}
	}(resp.Body)

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	return body, nil
}

func (provider DDNSProviderWebHook) FormatWebhookString(s string, config *DDNSDomainConfig, ipType string) string {
	if config == nil {
		return s
	}

	result := strings.TrimSpace(s)
	result = strings.Replace(s, "{ip}", config.Ipv4Addr, -1)
	result = strings.Replace(result, "{domain}", config.FullDomain, -1)
	result = strings.Replace(result, "{type}", ipType, -1)
	result = strings.Replace(result, "{access_id}", Conf.DDNS.AccessID, -1)
	result = strings.Replace(result, "{access_secret}", Conf.DDNS.AccessSecret, -1)
	// remove \r
	result = strings.Replace(result, "\r", "", -1)
	return result
}

func SetStringHeadersToRequest(req *http.Request, headers []string) {
	if req == nil {
		return
	}
	for _, element := range headers {
		kv := strings.SplitN(element, ":", 2)
		if len(kv) == 2 {
			req.Header.Add(kv[0], kv[1])
		}
	}
}

// SplitDomain 分割域名为前缀和一级域名
func SplitDomain(domain string) (prefix string, topLevelDomain string) {
	// 带有二级TLD的一些常见例子,需要特别处理
	secondLevelTLDs := map[string]bool{
		".co.uk": true, ".com.cn": true, ".gov.cn": true, ".net.cn": true, ".org.cn": true,
	}

	// 分割域名为"."的各部分
	parts := strings.Split(domain, ".")

	// 处理特殊情况,例如 ".co.uk"
	for i := len(parts) - 2; i > 0; i-- {
		potentialTLD := fmt.Sprintf(".%s.%s", parts[i], parts[i+1])
		if secondLevelTLDs[potentialTLD] {
			if i > 1 {
				return strings.Join(parts[:i-1], "."), strings.Join(parts[i-1:], ".")
			}
			return "", domain // 当域名仅为二级TLD时,无前缀
		}
	}

	// 常规处理,查找最后一个"."前的所有内容作为前缀
	if len(parts) > 2 {
		return strings.Join(parts[:len(parts)-2], "."), strings.Join(parts[len(parts)-2:], ".")
	}
	return "", domain // 当域名不包含子域名时,无前缀
}

func RetryableUpdateDomain(provider DDNSProvider, config *DDNSDomainConfig, maxRetries int) bool {
	if nil == config {
		return false
	}
	for retries := 0; retries < maxRetries; retries++ {
		log.Printf("NEZHA>> 正在尝试更新域名(%s)DDNS(%d/%d)\n", config.FullDomain, retries+1, maxRetries)
		if provider.UpdateDomain(config) {
			log.Printf("NEZHA>> 尝试更新域名(%s)DDNS成功\n", config.FullDomain)
			return true
		}
	}
	log.Printf("NEZHA>> 尝试更新域名(%s)DDNS失败\n", config.FullDomain)
	return false
}

func GetDDNSProviderFromString(provider string) (DDNSProvider, error) {
	switch provider {
	case "webhook":
		return DDNSProviderWebHook{
			URL:           Conf.DDNS.WebhookURL,
			RequestMethod: Conf.DDNS.WebhookMethod,
			RequestBody:   Conf.DDNS.WebhookRequestBody,
			RequestHeader: Conf.DDNS.WebhookHeaders,
		}, nil
	case "dummy":
		return DDNSProviderDummy{}, nil
	case "cloudflare":
		return DDNSProviderCloudflare{
			Secret: Conf.DDNS.AccessSecret,
		}, nil
	}
	return DDNSProviderDummy{}, errors.New(fmt.Sprintf("无法找到配置的DDNS提供者%s", Conf.DDNS.Provider))
}