package proxy

import (
	"io"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strings"
	"time"
)

type Director struct {
	Platform *url.URL
	AI       *url.URL
	Timeout  time.Duration
}

func New(platformURL, aiURL string, timeoutSec int) (*Director, error) {
	p, err := url.Parse(platformURL)
	if err != nil {
		return nil, err
	}
	a, err := url.Parse(aiURL)
	if err != nil {
		return nil, err
	}
	if timeoutSec <= 0 {
		timeoutSec = 60
	}
	return &Director{Platform: p, AI: a, Timeout: time.Duration(timeoutSec) * time.Second}, nil
}

func (d *Director) Handler() http.HandlerFunc {
	transport := &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		DialContext: (&net.Dialer{
			Timeout:   10 * time.Second,
			KeepAlive: 30 * time.Second,
		}).DialContext,
		MaxIdleConns:          100,
		IdleConnTimeout:       90 * time.Second,
		TLSHandshakeTimeout:   10 * time.Second,
		ExpectContinueTimeout: 1 * time.Second,
	}

	return func(w http.ResponseWriter, r *http.Request) {
		target, path := d.route(r.URL.Path)
		proxy := httputil.NewSingleHostReverseProxy(target)
		proxy.Transport = transport
		proxy.FlushInterval = 100 * time.Millisecond
		proxy.ErrorHandler = func(rw http.ResponseWriter, req *http.Request, err error) {
			rw.Header().Set("Content-Type", "application/json")
			rw.WriteHeader(http.StatusBadGateway)
			_, _ = rw.Write([]byte(`{"code":502,"message":"upstream unavailable: ` + escape(err.Error()) + `"}`))
		}
		proxy.ModifyResponse = func(resp *http.Response) error {
			// 网关已统一加 CORS；去掉上游重复头，避免浏览器报 Failed to fetch
			for _, h := range []string{
				"Access-Control-Allow-Origin",
				"Access-Control-Allow-Credentials",
				"Access-Control-Allow-Headers",
				"Access-Control-Allow-Methods",
				"Access-Control-Expose-Headers",
				"Access-Control-Max-Age",
			} {
				resp.Header.Del(h)
			}
			resp.Header.Set("X-Gateway", "aijianzhan-gateway")
			return nil
		}

		// 重写路径
		r.URL.Path = path
		r.Host = target.Host
		r.Header.Set("X-Forwarded-Host", r.Header.Get("Host"))
		r.Header.Set("X-Forwarded-Proto", "http")
		if r.Header.Get("X-Request-Id") == "" {
			r.Header.Set("X-Request-Id", newRequestID())
		}

		// 超时上下文
		ctx := r.Context()
		proxy.ServeHTTP(w, r.WithContext(ctx))
	}
}

func (d *Director) route(path string) (*url.URL, string) {
	// AI：/ai/* → 上游去掉 /ai 前缀
	if strings.HasPrefix(path, "/ai/") || path == "/ai" {
		next := strings.TrimPrefix(path, "/ai")
		if next == "" {
			next = "/"
		}
		return d.AI, next
	}
	// 生成蓝图：统一 /api/v1/apps/generate（兼容旧冒号路径，避免重复实现）
	if path == "/api/v1/apps/generate" || strings.HasPrefix(path, "/api/v1/apps/generate?") {
		return d.AI, path
	}
	if strings.HasPrefix(path, "/api/v1/apps:generate") || strings.Contains(path, "/apps%3Agenerate") {
		return d.AI, "/api/v1/apps/generate"
	}
	// 其余 /api 走中台
	return d.Platform, path
}

func escape(s string) string {
	s = strings.ReplaceAll(s, `\`, `\\`)
	s = strings.ReplaceAll(s, `"`, `\"`)
	if len(s) > 200 {
		s = s[:200]
	}
	return s
}

func newRequestID() string {
	return strings.ReplaceAll(time.Now().UTC().Format("20060102T150405.000000000"), ".", "")
}

// Health 聚合探测
func (d *Director) Health(w http.ResponseWriter, _ *http.Request) {
	type st struct {
		Name   string `json:"name"`
		OK     bool   `json:"ok"`
		Detail string `json:"detail,omitempty"`
	}
	client := &http.Client{Timeout: 2 * time.Second}
	out := []st{
		probe(client, "platform", d.Platform.String()+"/api/v1/auth/login"),
		probe(client, "ai", d.AI.String()+"/health"),
	}
	allOK := true
	for _, s := range out {
		if !s.OK {
			allOK = false
			break
		}
	}
	code := http.StatusOK
	if !allOK {
		code = http.StatusServiceUnavailable
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(code)
	_, _ = io.WriteString(w, `{"gateway":"ok","upstreams":[`)
	for i, s := range out {
		if i > 0 {
			_, _ = io.WriteString(w, ",")
		}
		ok := "false"
		if s.OK {
			ok = "true"
		}
		_, _ = io.WriteString(w, `{"name":"`+s.Name+`","ok":`+ok+`}`)
	}
	_, _ = io.WriteString(w, `]}`)
}

func probe(client *http.Client, name, rawURL string) struct {
	Name   string `json:"name"`
	OK     bool   `json:"ok"`
	Detail string `json:"detail,omitempty"`
} {
	// login 用 OPTIONS/GET 可能 405；用短超时 HEAD/GET health 风格
	req, _ := http.NewRequest(http.MethodGet, rawURL, nil)
	if name == "platform" {
		// 未登录会 401/405 都说明服务活着；连接失败才算挂
		req, _ = http.NewRequest(http.MethodPost, rawURL, strings.NewReader(`{}`))
		req.Header.Set("Content-Type", "application/json")
	}
	resp, err := client.Do(req)
	if err != nil {
		return struct {
			Name   string `json:"name"`
			OK     bool   `json:"ok"`
			Detail string `json:"detail,omitempty"`
		}{Name: name, OK: false, Detail: err.Error()}
	}
	defer resp.Body.Close()
	return struct {
		Name   string `json:"name"`
		OK     bool   `json:"ok"`
		Detail string `json:"detail,omitempty"`
	}{Name: name, OK: true}
}
