package proxyio

import (
	"encoding/json"
	"fmt"
	"net/url"
	"strings"

	"github.com/prism/proxy/internal/country"
	"github.com/prism/proxy/internal/store"
	"github.com/prism/proxy/internal/subscription"
)

const ExportVersion = 1

type ExportDoc struct {
	Version int          `json:"version"`
	Nodes   []ExportNode `json:"nodes"`
}

type ExportNode struct {
	Name      string          `json:"name"`
	Type      string          `json:"type"`
	RawConfig json.RawMessage `json:"raw_config"`
	Country   string          `json:"country,omitempty"`
	Enabled   *bool           `json:"enabled,omitempty"`
}

func nodeEnabled(n ExportNode) bool {
	if n.Enabled == nil {
		return true
	}
	return *n.Enabled
}

func ToExportNodes(nodes []store.ProxyNode) []ExportNode {
	out := make([]ExportNode, 0, len(nodes))
	for _, n := range nodes {
		enabled := n.Enabled
		raw := json.RawMessage(n.RawConfig)
		if !json.Valid(raw) {
			raw = json.RawMessage("{}")
		}
		out = append(out, ExportNode{
			Name:      n.Name,
			Type:      n.Type,
			RawConfig: raw,
			Country:   n.Country,
			Enabled:   &enabled,
		})
	}
	return out
}

func ExportJSON(nodes []store.ProxyNode) ([]byte, error) {
	doc := ExportDoc{Version: ExportVersion, Nodes: ToExportNodes(nodes)}
	return json.MarshalIndent(doc, "", "  ")
}

func ExportLinks(nodes []store.ProxyNode) string {
	var b strings.Builder
	for _, n := range nodes {
		if !n.Enabled {
			continue
		}
		link, err := EncodeLink(n)
		if err != nil {
			b.WriteString("# ")
			b.WriteString(n.Name)
			b.WriteString(": ")
			b.WriteString(err.Error())
			b.WriteByte('\n')
			continue
		}
		b.WriteString(link)
		b.WriteByte('\n')
	}
	return b.String()
}

func ParseJSON(data []byte) ([]ExportNode, error) {
	var doc ExportDoc
	if err := json.Unmarshal(data, &doc); err == nil && (len(doc.Nodes) > 0 || doc.Version > 0) {
		return doc.Nodes, nil
	}
	var nodes []ExportNode
	if err := json.Unmarshal(data, &nodes); err != nil {
		return nil, fmt.Errorf("invalid JSON: %w", err)
	}
	return nodes, nil
}

func ParseText(text string) ([]store.ProxyNode, []string) {
	text = strings.TrimSpace(text)
	if text == "" {
		return nil, nil
	}
	if strings.HasPrefix(text, "{") || strings.HasPrefix(text, "[") {
		items, err := ParseJSON([]byte(text))
		if err == nil && len(items) > 0 {
			nodes, errs := ToStoreNodes(items)
			return nodes, errs
		}
	}
	return subscription.ParseLinkLines(text)
}

func ToStoreNodes(items []ExportNode) ([]store.ProxyNode, []string) {
	out := make([]store.ProxyNode, 0, len(items))
	var errs []string
	for i, item := range items {
		name := strings.TrimSpace(item.Name)
		typ := strings.TrimSpace(strings.ToLower(item.Type))
		raw := normalizeRawConfig(item.RawConfig)
		if name == "" {
			errs = append(errs, fmt.Sprintf("节点 %d: 缺少名称", i+1))
			continue
		}
		if typ == "" {
			errs = append(errs, fmt.Sprintf("节点 %d: 缺少类型", i+1))
			continue
		}
		if raw == "" || raw == "{}" {
			errs = append(errs, fmt.Sprintf("节点 %d: 缺少配置", i+1))
			continue
		}
		code := strings.TrimSpace(item.Country)
		if code == "" {
			code = country.Detect(name)
		}
		out = append(out, store.ProxyNode{
			Name:        name,
			RuntimeName: store.RuntimeName("manual", 0, name),
			Type:        typ,
			RawConfig:   raw,
			Source:      "manual",
			Country:     code,
			Enabled:     nodeEnabled(item),
		})
	}
	return out, errs
}

func normalizeRawConfig(raw json.RawMessage) string {
	if len(raw) == 0 {
		return ""
	}
	if raw[0] == '{' || raw[0] == '[' {
		if !json.Valid(raw) {
			return ""
		}
		return string(raw)
	}
	var s string
	if err := json.Unmarshal(raw, &s); err == nil {
		return strings.TrimSpace(s)
	}
	return strings.TrimSpace(string(raw))
}

func AssignUniqueNames(nodes []store.ProxyNode, reserved map[string]bool) {
	if reserved == nil {
		reserved = map[string]bool{}
	}
	for i := range nodes {
		name := strings.TrimSpace(nodes[i].Name)
		if name == "" {
			name = "node"
		}
		base := name
		candidate := base
		for n := 2; reserved[candidate]; n++ {
			candidate = fmt.Sprintf("%s_%d", base, n)
		}
		reserved[candidate] = true
		nodes[i].Name = candidate
		nodes[i].RuntimeName = store.RuntimeName("manual", 0, candidate)
		if nodes[i].Country == "" {
			nodes[i].Country = country.Detect(candidate)
		}
	}
}

func ReservedNames(nodes []store.ProxyNode) map[string]bool {
	m := make(map[string]bool, len(nodes))
	for _, n := range nodes {
		m[n.Name] = true
	}
	return m
}

func EncodeLink(n store.ProxyNode) (string, error) {
	var raw map[string]any
	if err := json.Unmarshal([]byte(n.RawConfig), &raw); err != nil {
		return "", err
	}
	fragment := url.QueryEscape(n.Name)
	typ := strings.ToLower(n.Type)
	switch typ {
	case "socks5":
		return encodeSocks5(raw, fragment)
	case "http", "https":
		return encodeHTTP(raw, fragment, typ == "https")
	case "trojan":
		return encodeTrojan(raw, fragment)
	default:
		return "", fmt.Errorf("类型 %s 暂不支持导出为分享链接", n.Type)
	}
}

func encodeSocks5(raw map[string]any, fragment string) (string, error) {
	server, _ := raw["server"].(string)
	port := intFromAny(raw["port"])
	if server == "" || port <= 0 {
		return "", fmt.Errorf("socks5 配置不完整")
	}
	host := fmt.Sprintf("%s:%d", server, port)
	user, _ := raw["username"].(string)
	pass, _ := raw["password"].(string)
	if user != "" {
		return fmt.Sprintf("socks5://%s:%s@%s#%s", url.QueryEscape(user), url.QueryEscape(pass), host, fragment), nil
	}
	return fmt.Sprintf("socks5://%s#%s", host, fragment), nil
}

func encodeHTTP(raw map[string]any, fragment string, tls bool) (string, error) {
	server, _ := raw["server"].(string)
	port := intFromAny(raw["port"])
	if server == "" || port <= 0 {
		return "", fmt.Errorf("http 配置不完整")
	}
	scheme := "http"
	if tls {
		scheme = "https"
	}
	host := fmt.Sprintf("%s:%d", server, port)
	user, _ := raw["username"].(string)
	pass, _ := raw["password"].(string)
	if user != "" {
		return fmt.Sprintf("%s://%s:%s@%s#%s", scheme, url.QueryEscape(user), url.QueryEscape(pass), host, fragment), nil
	}
	return fmt.Sprintf("%s://%s#%s", scheme, host, fragment), nil
}

func encodeTrojan(raw map[string]any, fragment string) (string, error) {
	server, _ := raw["server"].(string)
	port := intFromAny(raw["port"])
	pass, _ := raw["password"].(string)
	if server == "" || port <= 0 || pass == "" {
		return "", fmt.Errorf("trojan 配置不完整")
	}
	return fmt.Sprintf("trojan://%s@%s:%d#%s", url.QueryEscape(pass), server, port, fragment), nil
}

func intFromAny(v any) int {
	switch n := v.(type) {
	case float64:
		return int(n)
	case int:
		return n
	case int64:
		return int(n)
	case json.Number:
		i, _ := n.Int64()
		return int(i)
	default:
		return 0
	}
}
