package main

import (
	"context"
	"encoding/json"
	"apigo.cc/go/log"
	"apigo.cc/go/redis"
	"apigo.cc/go/service"
	"io"
	"net"
	gohttp "net/http"
	"os"
	"strings"
	"testing"
	"time"
)

func TestGateway(t *testing.T) {
	// 1. 设置服务发现和 Redis 依赖
	registry := "127.0.0.1:6379"
	rd := redis.GetRedis(registry, log.DefaultLogger)
	if rd.Error != nil {
		t.Skip("Redis is not available, skipping gateway integration tests")
		return
	}

	// 清理测试数据
	rd.Do("DEL", "gateway:host:gw.test", "gateway:hosts")

	// 2. 启动一个后端测试服务 test-backend
	service.Config.App = "test-backend"
	service.Config.Listen = ":0"
	service.Config.Register = registry

	service.Host("*").GET("/hello", func(req *service.Request) string {
		return "hello from backend, path: " + req.RequestURI
	})
	service.Host("*").GET("/hello/world", func(req *service.Request) string {
		return "hello from backend, path: " + req.RequestURI
	})

	asBackend := service.AsyncStart()
	defer asBackend.Stop()
	time.Sleep(200 * time.Millisecond) // 等待后端启动和注册

	// 3. 配置 Gateway
	GatewayConf.Redis = registry
	GatewayConf.Prefix = "gateway"

	// 初始化网关的配置到 Redis
	proxyRules := []service.ProxyRule{
		{Path: "^/api/(.*)$", ToApp: "test-backend", ToPath: "/$1"},
		{Path: "/direct", ToApp: "test-backend", ToPath: "/hello"},
		{Path: "/v2/*", ToApp: "test-backend", ToPath: "/hello/*"},
	}
	rewriteRules := []service.RewriteRule{
		{Path: "^/old-api/(.*)$", ToPath: "/api/$1"},
	}
	
	proxyJson, _ := json.Marshal(proxyRules)
	rewriteJson, _ := json.Marshal(rewriteRules)
	
	rd.Do("HSET", "gateway:host:gw.test", "proxies", string(proxyJson))
	rd.Do("HSET", "gateway:host:gw.test", "rewrites", string(rewriteJson))

	// 创建临时静态目录
	tmpDir, _ := os.MkdirTemp("", "gateway-static")
	defer os.RemoveAll(tmpDir)
	_ = os.WriteFile(tmpDir+"/index.html", []byte("static content"), 0644)
	
	staticConfig := map[string]string{
		"/ui": tmpDir,
	}
	staticJson, _ := json.Marshal(staticConfig)
	rd.Do("HSET", "gateway:host:gw.test", "statics", string(staticJson))

	// 4. 启动 Gateway 应用
	app := NewGatewayApp()
	
	// 设置独立的端口给 Gateway 的 WebServer 避免与 backend 冲突
	service.Config.App = "gateway"
	service.Config.Listen = ":0"
	// 重置发现，确保网关独立
	service.SetDiscovererForTest(nil)
	// 配置网关可以通过 discover 找到 test-backend (网关也需要开启 discover)
	service.Config.Register = registry
	service.Config.Calls = map[string]service.CallConfig{
		"test-backend": {Timeout: 1000},
	}

	ctx := context.Background()
	err := app.Start(ctx, log.DefaultLogger)
	if err != nil {
		t.Fatalf("Failed to start gateway: %v", err)
	}
	defer app.Stop(ctx)

	time.Sleep(200 * time.Millisecond)
	
	_, gwPort, _ := net.SplitHostPort(app.Addr)

	client := &gohttp.Client{Timeout: 2 * time.Second}

	// ============================================
	// 测试 1: 直接代理匹配 (Discover)
	// ============================================
	req, _ := gohttp.NewRequest("GET", "http://127.0.0.1:"+gwPort+"/direct", nil)
	req.Host = "gw.test"
	res, err := client.Do(req)
	var body string
	if err == nil && res != nil {
		b, _ := io.ReadAll(res.Body)
		res.Body.Close()
		body = string(b)
	}
	if err != nil || body != "hello from backend, path: /hello" {
		t.Fatalf("Proxy /direct failed, got: %s, err: %v", body, err)
	}

	// ============================================
	// 测试 2: 正则代理匹配 (Discover)
	// ============================================
	req, _ = gohttp.NewRequest("GET", "http://127.0.0.1:"+gwPort+"/api/hello?a=1", nil)
	req.Host = "gw.test"
	res, err = client.Do(req)
	body = ""
	if err == nil && res != nil {
		b, _ := io.ReadAll(res.Body)
		res.Body.Close()
		body = string(b)
	}
	if err != nil || !strings.Contains(body, "hello from backend, path: /hello?a=1") {
		t.Fatalf("Proxy regexp failed, got: %s", body)
	}

	// ============================================
	// 测试 2.1: 极速通配符前缀匹配 (Discover)
	// ============================================
	req, _ = gohttp.NewRequest("GET", "http://127.0.0.1:"+gwPort+"/v2/world?x=2", nil)
	req.Host = "gw.test"
	res, err = client.Do(req)
	body = ""
	if err == nil && res != nil {
		b, _ := io.ReadAll(res.Body)
		res.Body.Close()
		body = string(b)
	}
	if err != nil || !strings.Contains(body, "hello from backend, path: /hello/world?x=2") {
		t.Fatalf("Proxy wildcard failed, got: %s", body)
	}

	// ============================================
	// 测试 3: Rewrite 后再 Proxy
	// ============================================
	req, _ = gohttp.NewRequest("GET", "http://127.0.0.1:"+gwPort+"/old-api/hello", nil)
	req.Host = "gw.test"
	res, err = client.Do(req)
	body = ""
	if err == nil && res != nil {
		b, _ := io.ReadAll(res.Body)
		res.Body.Close()
		body = string(b)
	}
	if err != nil || body != "hello from backend, path: /hello" {
		t.Fatalf("Rewrite+Proxy failed, got: %s", body)
	}

	// ============================================
	// 测试 4: 静态文件服务
	// ============================================
	req, _ = gohttp.NewRequest("GET", "http://127.0.0.1:"+gwPort+"/ui/index.html", nil)
	req.Host = "gw.test"
	res, err = client.Do(req)
	body = ""
	if err == nil && res != nil {
		b, _ := io.ReadAll(res.Body)
		res.Body.Close()
		body = string(b)
	}
	if err != nil || body != "static content" {
		t.Fatalf("Static service failed, got: %s", body)
	}

	// ============================================
	// 测试 5: 通过 Redis Pub/Sub 动态更新路由
	// ============================================
	// 新增一个新代理规则，删除老的
	newProxyRules := []service.ProxyRule{
		{Path: "/new-direct", ToApp: "test-backend", ToPath: "/hello"},
	}
	newProxyJson, _ := json.Marshal(newProxyRules)
	rd.Do("HSET", "gateway:host:gw.test", "proxies", string(newProxyJson))
	
	// 发布更新消息
	rd.Do("PUBLISH", "gateway:channel", `"gw.test"`)

	// 等待接收和更新
	time.Sleep(300 * time.Millisecond)

	// 测试新路由应该生效
	req, _ = gohttp.NewRequest("GET", "http://127.0.0.1:"+gwPort+"/new-direct", nil)
	req.Host = "gw.test"
	res, err = client.Do(req)
	body = ""
	if err == nil && res != nil {
		b, _ := io.ReadAll(res.Body)
		res.Body.Close()
		body = string(b)
	}
	if err != nil || body != "hello from backend, path: /hello" {
		t.Fatalf("Pub/Sub dynamic proxy failed, got: %s", body)
	}

	// 测试老路由应该 404 (原子替换)
	req, _ = gohttp.NewRequest("GET", "http://127.0.0.1:"+gwPort+"/direct", nil)
	req.Host = "gw.test"
	res, err = client.Do(req)
	if err != nil {
		t.Fatalf("Request failed: %v", err)
	}
	if res.StatusCode != 404 {
		t.Fatalf("Old proxy rule should be deleted (404), got status: %v", res.StatusCode)
	}
	res.Body.Close()
}
