// Package proxy 提供 DNS 代理功能的测试。
//
// 该文件测试 proxy_dns.go 中的 DNS 相关功能，包括：
//   - DNS 解析器设置和获取
//   - DNS 缓存刷新机制
//   - HostClient 地址更新
//   - 错误处理场景
//
// 作者：xfy
package proxy

import (
	"context"
	"errors"
	"sync"
	"testing"
	"time"

	"rua.plus/lolly/internal/config"
	"rua.plus/lolly/internal/loadbalance"
	"rua.plus/lolly/internal/resolver"
)

// mockResolver 实现 resolver.Resolver 接口的模拟解析器。
type mockResolver struct {
	mu                   sync.RWMutex
	lookupResults        map[string][]string
	lookupError          error
	startErr             error
	stopErr              error
	lookupHostCalls      int
	lookupWithCacheCalls int
	startCalls           int
	stopCalls            int
}

func (m *mockResolver) LookupHost(_ context.Context, host string) ([]string, error) {
	m.mu.Lock()
	m.lookupHostCalls++
	m.mu.Unlock()
	return m.resolve(host)
}

func (m *mockResolver) LookupHostWithCache(_ context.Context, host string) ([]string, error) {
	m.mu.Lock()
	m.lookupWithCacheCalls++
	m.mu.Unlock()
	return m.resolve(host)
}

func (m *mockResolver) resolve(host string) ([]string, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	if m.lookupError != nil {
		return nil, m.lookupError
	}
	if ips, ok := m.lookupResults[host]; ok {
		return ips, nil
	}
	return nil, errors.New("host not found in mock resolver")
}

func (m *mockResolver) Refresh(_ string) error {
	return nil
}

func (m *mockResolver) Start() error {
	m.mu.Lock()
	m.startCalls++
	m.mu.Unlock()
	return m.startErr
}

func (m *mockResolver) Stop() error {
	m.mu.Lock()
	m.stopCalls++
	m.mu.Unlock()
	return m.stopErr
}

func (m *mockResolver) Stats() resolver.Stats {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return resolver.Stats{
		CacheHits:   int64(m.lookupWithCacheCalls),
		CacheMisses: int64(m.lookupHostCalls),
	}
}

// TestSetResolver 测试设置 DNS 解析器。
func TestSetResolver(t *testing.T) {
	cfg := &config.ProxyConfig{
		Path:        "/api",
		LoadBalance: "round_robin",
		Timeout:     config.ProxyTimeout{Connect: 5 * time.Second},
	}
	targets := []*loadbalance.Target{
		{URL: "http://localhost:8080"},
	}

	p, err := NewProxy(cfg, targets, nil, nil)
	if err != nil {
		t.Fatalf("NewProxy() error: %v", err)
	}

	// 初始状态 resolver 为 nil
	if p.resolver != nil {
		t.Error("resolver should be nil initially")
	}

	// 设置解析器
	mr := &mockResolver{}
	p.SetResolver(mr)

	if p.resolver != mr {
		t.Error("SetResolver() did not set resolver correctly")
	}
}

// TestGetResolverStats_NoResolver 测试没有解析器时返回空统计。

// TestGetResolverStats_WithResolver 测试有解析器时返回统计。

// TestStartWithResolver 测试启动代理时解析器正确启动。

// TestStartResolverFails 测试解析器启动失败时代理返回错误。

// TestStartIdempotent 测试 Start 是幂等的。

// TestStopIdempotent 测试 Stop 是幂等的。

// TestStopWithoutResolver 测试没有解析器时停止代理。

// TestRefreshDNS_Success 测试 DNS 刷新成功场景。

// TestRefreshDNS_LookupError 测试 DNS 刷新时查找失败场景。

// TestRefreshDNS_NoResolver 测试没有解析器时刷新不执行任何操作。

// TestRefreshDNS_IPAddressTarget 测试 IP 类型的目标不需要解析。

// TestRefreshDNS_RecentlyResolved 测试最近已解析的目标不需要再次解析。

// TestRefreshDNS_ExpiredResolve 测试 TTL 过期后需要重新解析。
// 该测试验证 TTL 过期检查的正确性：
// - 短时间内（< TTL）不需要重新解析
// - 长时间后（> TTL）需要重新解析

// TestUpdateHostClientAddr_HTTP 测试 HTTP 目标地址更新。

// TestUpdateHostClientAddr_HTTPS 测试 HTTPS 目标地址更新。

// TestUpdateHostClientAddr_DefaultPort 测试没有端口时使用默认端口。

// TestUpdateHostClientAddr_NonExistentTarget 测试不存在的目标不更新。

// TestGetResolverTTL 测试 TTL 获取。

// TestDNSRefreshLoop_StartStop 测试 DNS 刷新循环的启动和停止。

// TestMultipleTargets_Refresh 测试多目标刷新。

// TestUpdateHostClientAddr_ReplacesClient 验证 DNS 更新时重建 HostClient 而不是修改 Addr。
func TestUpdateHostClientAddr_ReplacesClient(t *testing.T) {
	cfg := &config.ProxyConfig{
		Path:        "/api",
		LoadBalance: "round_robin",
		Timeout:     config.ProxyTimeout{Connect: 5 * time.Second},
	}
	targets := []*loadbalance.Target{
		{URL: "http://backend.example.com:8080"},
	}

	p, err := NewProxy(cfg, targets, nil, nil)
	if err != nil {
		t.Fatalf("NewProxy() error: %v", err)
	}

	oldClient := p.clients["http://backend.example.com:8080"]
	if oldClient == nil {
		t.Fatal("old client should not be nil")
	}
	oldAddr := oldClient.Addr

	p.updateHostClientAddr(targets[0], "192.168.1.100")

	newClient := p.clients["http://backend.example.com:8080"]
	if newClient == nil {
		t.Fatal("new client should not be nil")
	}
	if newClient == oldClient {
		t.Error("updateHostClientAddr should replace the HostClient instead of mutating it")
	}
	if newClient.Addr != "192.168.1.100:8080" {
		t.Errorf("new client addr = %q, want %q", newClient.Addr, "192.168.1.100:8080")
	}
	// 旧 client 的 Addr 不应被修改，旧连接继续使用
	if oldClient.Addr != oldAddr {
		t.Errorf("old client addr was mutated: got %q, want %q", oldClient.Addr, oldAddr)
	}
}

// TestUpdateHostClientAddr_WithProxyBind 验证配置了 proxy_bind 时使用正确的 client key。
func TestUpdateHostClientAddr_WithProxyBind(t *testing.T) {
	cfg := &config.ProxyConfig{
		Path:        "/api",
		LoadBalance: "round_robin",
		Timeout:     config.ProxyTimeout{Connect: 5 * time.Second},
		ProxyBind:   "127.0.0.1",
	}
	targets := []*loadbalance.Target{
		{URL: "http://backend.example.com:8080"},
	}

	p, err := NewProxy(cfg, targets, nil, nil)
	if err != nil {
		t.Fatalf("NewProxy() error: %v", err)
	}

	key := "http://backend.example.com:8080|127.0.0.1"
	if p.clients[key] == nil {
		t.Fatalf("client with proxy_bind key should exist")
	}

	p.updateHostClientAddr(targets[0], "192.168.1.200")

	newClient := p.clients[key]
	if newClient == nil {
		t.Fatal("new client should not be nil")
	}
	if newClient.Addr != "192.168.1.200:8080" {
		t.Errorf("new client addr = %q, want %q", newClient.Addr, "192.168.1.200:8080")
	}
}

// TestUpdateHostClientAddr_ConcurrentRead 验证更新 Addr 时不会与 getClient 产生数据竞争。
func TestUpdateHostClientAddr_ConcurrentRead(t *testing.T) {
	cfg := &config.ProxyConfig{
		Path:        "/api",
		LoadBalance: "round_robin",
		Timeout:     config.ProxyTimeout{Connect: 5 * time.Second},
	}
	targets := []*loadbalance.Target{
		{URL: "http://backend.example.com:8080"},
	}

	p, err := NewProxy(cfg, targets, nil, nil)
	if err != nil {
		t.Fatalf("NewProxy() error: %v", err)
	}

	var wg sync.WaitGroup
	// 并发读取 client
	for range 10 {
		wg.Add(1)
		go func() {
			defer wg.Done()
			client := p.getClient(targets[0].URL)
			_ = client.Addr
		}()
	}

	// 并发更新 client
	for i := range 10 {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			p.updateHostClientAddr(targets[0], "192.168.1."+string(rune('0'+id)))
		}(i)
	}

	wg.Wait()
}

// TestStopResolverFails 测试停止解析器失败时返回错误。
