package app

import (
	"crypto/tls"
	"crypto/x509"
	"errors"
	"fmt"
	"net"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"sync"
	"time"

	"GoNavi-Wails/internal/connection"
	"GoNavi-Wails/internal/logger"
	proxytunnel "GoNavi-Wails/internal/proxy"
)

type globalProxySnapshot struct {
	Enabled bool                   `json:"enabled"`
	Proxy   connection.ProxyConfig `json:"proxy"`
}

var globalProxyRuntime = struct {
	mu      sync.RWMutex
	enabled bool
	proxy   connection.ProxyConfig
}{}

type localProxyTLSFallbackTransport struct {
	primary       *http.Transport
	fallback      *http.Transport
	proxyEndpoint string
}

func currentGlobalProxyConfig() globalProxySnapshot {
	globalProxyRuntime.mu.RLock()
	defer globalProxyRuntime.mu.RUnlock()
	if !globalProxyRuntime.enabled {
		return globalProxySnapshot{
			Enabled: false,
			Proxy:   connection.ProxyConfig{},
		}
	}
	return globalProxySnapshot{
		Enabled: true,
		Proxy:   globalProxyRuntime.proxy,
	}
}

func setGlobalProxyConfig(enabled bool, proxyConfig connection.ProxyConfig) (globalProxySnapshot, error) {
	if !enabled {
		globalProxyRuntime.mu.Lock()
		globalProxyRuntime.enabled = false
		globalProxyRuntime.proxy = connection.ProxyConfig{}
		globalProxyRuntime.mu.Unlock()
		return currentGlobalProxyConfig(), nil
	}

	normalizedProxy, err := proxytunnel.NormalizeConfig(proxyConfig)
	if err != nil {
		return globalProxySnapshot{}, err
	}

	globalProxyRuntime.mu.Lock()
	globalProxyRuntime.enabled = true
	globalProxyRuntime.proxy = normalizedProxy
	globalProxyRuntime.mu.Unlock()
	return currentGlobalProxyConfig(), nil
}

func (a *App) ConfigureGlobalProxy(enabled bool, proxyConfig connection.ProxyConfig) connection.QueryResult {
	before := currentGlobalProxyConfig()
	snapshot, err := setGlobalProxyConfig(enabled, proxyConfig)
	if err != nil {
		return connection.QueryResult{Success: false, Message: err.Error()}
	}

	// 前端可能在同一配置下重复触发同步（例如严格模式或状态回放），
	// 这里做幂等日志，避免重复刷屏。
	if !globalProxySnapshotEqual(before, snapshot) {
		if snapshot.Enabled {
			authState := ""
			if strings.TrimSpace(snapshot.Proxy.User) != "" {
				authState = "（认证：已配置）"
			}
			logger.Infof(
				"全局代理已启用：%s://%s:%d%s",
				strings.ToLower(strings.TrimSpace(snapshot.Proxy.Type)),
				strings.TrimSpace(snapshot.Proxy.Host),
				snapshot.Proxy.Port,
				authState,
			)
		} else {
			logger.Infof("全局代理已关闭")
		}
	}

	return connection.QueryResult{
		Success: true,
		Message: "全局代理配置已生效",
		Data:    snapshot,
	}
}

func globalProxySnapshotEqual(a, b globalProxySnapshot) bool {
	if a.Enabled != b.Enabled {
		return false
	}
	if !a.Enabled {
		return true
	}
	return proxyConfigEqual(a.Proxy, b.Proxy)
}

func proxyConfigEqual(a, b connection.ProxyConfig) bool {
	return strings.EqualFold(strings.TrimSpace(a.Type), strings.TrimSpace(b.Type)) &&
		strings.TrimSpace(a.Host) == strings.TrimSpace(b.Host) &&
		a.Port == b.Port &&
		strings.TrimSpace(a.User) == strings.TrimSpace(b.User) &&
		a.Password == b.Password
}

func currentGlobalProxyView() connection.GlobalProxyView {
	snapshot := currentGlobalProxyConfig()
	if !snapshot.Enabled {
		return connection.GlobalProxyView{Enabled: false}
	}
	return connection.GlobalProxyView{
		Enabled:     true,
		Type:        snapshot.Proxy.Type,
		Host:        snapshot.Proxy.Host,
		Port:        snapshot.Proxy.Port,
		User:        snapshot.Proxy.User,
		HasPassword: strings.TrimSpace(snapshot.Proxy.Password) != "",
	}
}

func (a *App) GetGlobalProxyConfig() connection.QueryResult {
	return connection.QueryResult{
		Success: true,
		Message: "OK",
		Data:    currentGlobalProxyView(),
	}
}

func applyGlobalProxyToConnection(config connection.ConnectionConfig) connection.ConnectionConfig {
	effective := config
	if effective.UseProxy || effective.UseHTTPTunnel {
		return effective
	}
	if isFileDatabaseType(effective.Type) {
		effective.Proxy = connection.ProxyConfig{}
		return effective
	}

	snapshot := currentGlobalProxyConfig()
	if !snapshot.Enabled {
		effective.Proxy = connection.ProxyConfig{}
		return effective
	}

	effective.UseProxy = true
	effective.Proxy = snapshot.Proxy
	return effective
}

func isFileDatabaseType(driverType string) bool {
	switch strings.ToLower(strings.TrimSpace(driverType)) {
	case "sqlite", "duckdb":
		return true
	default:
		return false
	}
}

func newHTTPClientWithGlobalProxy(timeout time.Duration) *http.Client {
	client := &http.Client{
		Timeout: timeout,
	}
	if transport := buildHTTPTransportWithGlobalProxy(); transport != nil {
		client.Transport = transport
	}
	return client
}

func buildHTTPTransportWithGlobalProxy() http.RoundTripper {
	baseTransport, ok := http.DefaultTransport.(*http.Transport)
	if !ok || baseTransport == nil {
		return nil
	}

	transport := baseTransport.Clone()
	snapshot := currentGlobalProxyConfig()
	if !snapshot.Enabled {
		transport.Proxy = http.ProxyFromEnvironment
		return transport
	}

	proxyURL, err := buildProxyURLFromConfig(snapshot.Proxy)
	if err != nil {
		logger.Warnf("全局代理配置无效，回退系统代理：%v", err)
		transport.Proxy = http.ProxyFromEnvironment
		return transport
	}

	transport.Proxy = http.ProxyURL(proxyURL)
	if !isLoopbackProxyHost(snapshot.Proxy.Host) {
		return transport
	}

	fallbackTransport := transport.Clone()
	fallbackTransport.TLSClientConfig = cloneTLSConfigWithInsecureSkipVerify(fallbackTransport.TLSClientConfig)
	return &localProxyTLSFallbackTransport{
		primary:       transport,
		fallback:      fallbackTransport,
		proxyEndpoint: proxyURL.Redacted(),
	}
}

func (t *localProxyTLSFallbackTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	resp, err := t.primary.RoundTrip(req)
	if err == nil {
		return resp, nil
	}
	if !isTLSFallbackCandidate(req.Method, err) {
		return nil, err
	}

	retryReq, cloneErr := cloneRequestForRetry(req)
	if cloneErr != nil {
		return nil, err
	}
	logger.Warnf("检测到本地代理 TLS 证书不受信任，启用兼容回退：代理=%s 目标=%s 错误=%v", t.proxyEndpoint, req.URL.String(), err)
	return t.fallback.RoundTrip(retryReq)
}

func isTLSFallbackCandidate(method string, err error) bool {
	if !isIdempotentRequestMethod(method) {
		return false
	}
	return isUnknownAuthorityError(err)
}

func isIdempotentRequestMethod(method string) bool {
	switch strings.ToUpper(strings.TrimSpace(method)) {
	case http.MethodGet, http.MethodHead:
		return true
	default:
		return false
	}
}

func cloneRequestForRetry(req *http.Request) (*http.Request, error) {
	cloned := req.Clone(req.Context())
	if req.Body == nil || req.Body == http.NoBody {
		return cloned, nil
	}
	if req.GetBody == nil {
		return nil, fmt.Errorf("request body not replayable")
	}
	body, err := req.GetBody()
	if err != nil {
		return nil, err
	}
	cloned.Body = body
	return cloned, nil
}

func isUnknownAuthorityError(err error) bool {
	var unknownErr x509.UnknownAuthorityError
	if errors.As(err, &unknownErr) {
		return true
	}
	return strings.Contains(strings.ToLower(err.Error()), "x509: certificate signed by unknown authority")
}

func cloneTLSConfigWithInsecureSkipVerify(base *tls.Config) *tls.Config {
	if base == nil {
		return &tls.Config{InsecureSkipVerify: true}
	}
	cloned := base.Clone()
	cloned.InsecureSkipVerify = true
	return cloned
}

func isLoopbackProxyHost(host string) bool {
	trimmed := strings.TrimSpace(host)
	if trimmed == "" {
		return false
	}
	if strings.EqualFold(trimmed, "localhost") {
		return true
	}
	ip := net.ParseIP(trimmed)
	if ip == nil {
		return false
	}
	return ip.IsLoopback()
}

func buildProxyURLFromConfig(proxyConfig connection.ProxyConfig) (*url.URL, error) {
	normalizedProxy, err := proxytunnel.NormalizeConfig(proxyConfig)
	if err != nil {
		return nil, err
	}

	proxyType := strings.ToLower(strings.TrimSpace(normalizedProxy.Type))
	if proxyType != "http" && proxyType != "socks5" {
		return nil, fmt.Errorf("不支持的代理类型：%s", normalizedProxy.Type)
	}
	if strings.TrimSpace(normalizedProxy.Host) == "" {
		return nil, fmt.Errorf("代理地址不能为空")
	}
	if normalizedProxy.Port <= 0 || normalizedProxy.Port > 65535 {
		return nil, fmt.Errorf("代理端口无效：%d", normalizedProxy.Port)
	}

	proxyURL := &url.URL{
		Scheme: proxyType,
		Host:   net.JoinHostPort(strings.TrimSpace(normalizedProxy.Host), strconv.Itoa(normalizedProxy.Port)),
	}
	if strings.TrimSpace(normalizedProxy.User) != "" {
		proxyURL.User = url.UserPassword(strings.TrimSpace(normalizedProxy.User), normalizedProxy.Password)
	}
	return proxyURL, nil
}

