// Package security 提供安全相关的 HTTP 中间件。
//
// 该文件实现 auth_request 外部认证子请求中间件，支持将认证委托给
// 外部服务。根据认证服务的响应状态码决定是否允许原请求继续。
//
// 行为规则：
//   - 2xx 响应：认证通过，原请求继续处理
//   - 401/403 响应：认证失败，返回相应状态码
//   - 超时或连接失败：返回 500 内部服务器错误
//   - 其他响应：返回 500 内部服务器错误
//
// 使用示例：
//
//	cfg := &config.AuthRequestConfig{
//	    Enabled:  true,
//	    URI:      "http://auth-service:8080/verify",
//	    Method:   "GET",
//	    Timeout:  5 * time.Second,
//	    Headers: map[string]string{
//	        "X-Original-Uri": "$request_uri",
//	    },
//	}
//
//	authReq, err := security.NewAuthRequest(cfg)
//	if err != nil {
//	    log.Fatal(err)
//	}
//
//	// 应用为中间件
//	chain := middleware.NewChain(authReq)
//	handler := chain.Apply(finalHandler)
//
// 作者：xfy
package security

import (
	"errors"
	"net"
	"strings"
	"sync"
	"time"

	"github.com/valyala/fasthttp"
	"rua.plus/lolly/internal/config"
	"rua.plus/lolly/internal/middleware"
	"rua.plus/lolly/internal/variable"
)

// AuthRequest 实现外部认证子请求中间件。
//
// 将认证委托给外部服务，根据认证服务的响应状态码决定是否允许原请求继续。
// 支持完整 URL 和相对路径两种认证服务地址。
type AuthRequest struct {
	// client 用于向认证服务发送子请求的 HTTP 客户端
	client *fasthttp.HostClient
	// config 认证子请求配置
	config config.AuthRequestConfig
	// mu 读写锁，保护并发访问 client 和 config
	mu sync.RWMutex
}

// applyDefaults 设置配置默认值。
func applyDefaults(cfg *config.AuthRequestConfig) {
	if cfg.Method == "" {
		cfg.Method = "GET"
	}
	cfg.Method = strings.ToUpper(cfg.Method)

	if cfg.Timeout == 0 {
		cfg.Timeout = 5 * time.Second
	}

	if cfg.ForwardHeaders == nil {
		cfg.ForwardHeaders = []string{
			"Cookie",
			"Authorization",
			"X-Forwarded-For",
			"X-Real-Ip",
		}
	}
}

// NewAuthRequest 使用给定的配置创建一个新的 AuthRequest 中间件。
//
// 参数：
//   - cfg: 认证子请求配置
//
// 返回值：
//   - *AuthRequest: 配置完成的中间件实例
//   - error: 配置无效时返回错误
func NewAuthRequest(cfg config.AuthRequestConfig) (*AuthRequest, error) {
	if !cfg.Enabled {
		return &AuthRequest{config: cfg}, nil
	}

	if cfg.URI == "" {
		return nil, errors.New("auth_request: uri is required")
	}

	applyDefaults(&cfg)

	ar := &AuthRequest{
		config: cfg,
	}

	// 如果 URI 是完整 URL（非相对路径），初始化 HTTP 客户端
	if isFullURL(cfg.URI) {
		if err := ar.initClient(); err != nil {
			return nil, err
		}
	}

	return ar, nil
}

// isFullURL 检查 URI 是否为完整 URL（以 http:// 或 https:// 开头）。
//
// 参数：
//   - uri: 待检查的 URI 字符串
//
// 返回值：
//   - bool: true 表示为完整 URL
func isFullURL(uri string) bool {
	return strings.HasPrefix(uri, "http://") || strings.HasPrefix(uri, "https://")
}

// initClient 初始化用于认证子请求的 HTTP 客户端。
//
// 解析认证服务 URL 并创建独立连接池的 HTTP 客户端。
// 调用者需持有写锁。
//
// 返回值：
//   - error: URL 解析失败时返回错误
func (a *AuthRequest) initClient() error {
	a.mu.Lock()
	defer a.mu.Unlock()

	// 解析目标地址
	addr, isTLS, err := parseAuthURL(a.config.URI)
	if err != nil {
		return err
	}

	// 创建独立连接池的客户端
	a.client = &fasthttp.HostClient{
		Addr:                   addr,
		IsTLS:                  isTLS,
		ReadTimeout:            a.config.Timeout,
		WriteTimeout:           a.config.Timeout,
		MaxIdleConnDuration:    90 * time.Second,
		MaxConns:               100,
		MaxConnWaitTimeout:     a.config.Timeout,
		RetryIf:                nil, // 禁用自动重试
		DisablePathNormalizing: false,
	}

	return nil
}

// parseAuthURL 解析认证服务 URL。
//
// 返回值：
//   - addr: 主机地址（如 "auth-service:8080"）
//   - isTLS: 是否使用 HTTPS
//   - error: 解析错误
func parseAuthURL(url string) (string, bool, error) {
	// 移除协议前缀
	var isTLS bool
	if strings.HasPrefix(url, "https://") {
		isTLS = true
		url = strings.TrimPrefix(url, "https://")
	} else if after, ok := strings.CutPrefix(url, "http://"); ok {
		url = after
	}

	// 提取主机部分
	host := url
	if idx := strings.Index(host, "/"); idx != -1 {
		host = host[:idx]
	}
	if idx := strings.Index(host, "?"); idx != -1 {
		host = host[:idx]
	}

	// 验证地址
	if host == "" {
		return "", false, errors.New("auth_request: invalid URL")
	}

	// 添加默认端口
	if _, _, err := net.SplitHostPort(host); err != nil {
		if isTLS {
			host = net.JoinHostPort(host, "443")
		} else {
			host = net.JoinHostPort(host, "80")
		}
	}

	return host, isTLS, nil
}

// Name 返回中间件名称。
//
// 返回值：
//   - string: 中间件标识名 "auth_request"
func (a *AuthRequest) Name() string {
	return "auth_request"
}

// Process 实现中间件处理逻辑。
//
// 向认证服务发送子请求，根据响应决定是否允许原请求继续。
// 配置未启用时直接透传。
//
// 参数：
//   - next: 下一个请求处理器
//
// 返回值：
//   - fasthttp.RequestHandler: 包装后的请求处理器
func (a *AuthRequest) Process(next fasthttp.RequestHandler) fasthttp.RequestHandler {
	if !a.config.Enabled {
		return next
	}

	return func(ctx *fasthttp.RequestCtx) {
		// 执行认证子请求
		allowed, statusCode, err := a.doAuthRequest(ctx)
		if err != nil {
			// 认证服务不可用或超时，返回 500
			ctx.Error("Internal Server Error", fasthttp.StatusInternalServerError)
			return
		}

		if !allowed {
			// 认证失败，返回认证服务的状态码
			ctx.Error("Unauthorized", statusCode)
			return
		}

		// 认证通过，继续处理原请求
		next(ctx)
	}
}

// doAuthRequest 执行认证子请求。
//
// 返回值：
//   - allowed: 是否允许请求继续
//   - statusCode: 认证服务的响应状态码
//   - error: 请求过程中的错误
func (a *AuthRequest) doAuthRequest(ctx *fasthttp.RequestCtx) (bool, int, error) {
	// 创建认证子请求
	req := fasthttp.AcquireRequest()
	resp := fasthttp.AcquireResponse()
	defer fasthttp.ReleaseRequest(req)
	defer fasthttp.ReleaseResponse(resp)

	// 设置请求方法
	req.Header.SetMethod(a.config.Method)

	// 构建请求 URI（支持变量展开）
	uri := a.expandVars(ctx, a.config.URI)

	// 如果是相对路径，转换为完整 URL
	if !isFullURL(uri) {
		// 从原请求构建完整 URL
		scheme := "http"
		if ctx.IsTLS() {
			scheme = "https"
		}
		host := string(ctx.Host())
		if host == "" {
			host = "localhost"
		}
		uri = scheme + "://" + host + uri
	}
	req.SetRequestURI(uri)

	// 转发原请求的头
	for _, headerName := range a.config.ForwardHeaders {
		if value := ctx.Request.Header.Peek(headerName); len(value) > 0 {
			req.Header.Set(headerName, string(value))
		}
	}

	// 设置自定义头（支持变量展开）
	for key, value := range a.config.Headers {
		expanded := a.expandVars(ctx, value)
		req.Header.Set(key, expanded)
	}

	// 发送认证请求
	a.mu.RLock()
	client := a.client
	a.mu.RUnlock()

	if client != nil {
		// 使用独立连接池
		err := client.Do(req, resp)
		if err != nil {
			return false, 0, err
		}
	} else {
		// 使用默认客户端（相对路径情况）
		err := fasthttp.Do(req, resp)
		if err != nil {
			return false, 0, err
		}
	}

	// 根据响应状态码判断认证结果
	statusCode := resp.StatusCode()
	switch {
	case statusCode >= 200 && statusCode < 300:
		// 2xx：认证通过
		return true, statusCode, nil
	case statusCode == 401 || statusCode == 403:
		// 401/403：认证失败
		return false, statusCode, nil
	default:
		// 其他状态码：视为认证服务错误
		return false, 500, nil
	}
}

// expandVars 展开字符串中的变量（如 $request_uri）。
//
// 使用 variable 包展开请求上下文中的变量引用。
// 如果字符串中不包含变量标记，则直接返回原字符串。
//
// 参数：
//   - ctx: FastHTTP 请求上下文
//   - template: 包含变量引用的模板字符串
//
// 返回值：
//   - string: 展开后的字符串
func (a *AuthRequest) expandVars(ctx *fasthttp.RequestCtx, template string) string {
	if template == "" {
		return ""
	}

	// 快速检查：如果没有变量则直接返回
	if !strings.Contains(template, "$") {
		return template
	}

	// 创建变量上下文
	vc := variable.NewContext(ctx)
	defer variable.ReleaseContext(vc)

	return vc.Expand(template)
}

// UpdateConfig 动态更新配置。
// 用于配置热重载场景。
func (a *AuthRequest) UpdateConfig(cfg config.AuthRequestConfig) error {
	if !cfg.Enabled {
		a.mu.Lock()
		a.config = cfg
		a.client = nil
		a.mu.Unlock()
		return nil
	}

	if cfg.URI == "" {
		return errors.New("auth_request: uri is required")
	}

	applyDefaults(&cfg)

	a.mu.Lock()
	a.config = cfg

	// 重新初始化客户端
	if isFullURL(cfg.URI) {
		if err := a.initClientUnlocked(); err != nil {
			a.mu.Unlock()
			return err
		}
	} else {
		a.client = nil
	}

	a.mu.Unlock()
	return nil
}

// initClientUnlocked 在无锁状态下初始化客户端。
// 调用者必须持有写锁。
func (a *AuthRequest) initClientUnlocked() error {
	addr, isTLS, err := parseAuthURL(a.config.URI)
	if err != nil {
		return err
	}

	a.client = &fasthttp.HostClient{
		Addr:                   addr,
		IsTLS:                  isTLS,
		ReadTimeout:            a.config.Timeout,
		WriteTimeout:           a.config.Timeout,
		MaxIdleConnDuration:    90 * time.Second,
		MaxConns:               100,
		MaxConnWaitTimeout:     a.config.Timeout,
		RetryIf:                nil,
		DisablePathNormalizing: false,
	}

	return nil
}

// Close 关闭中间件并释放资源。
func (a *AuthRequest) Close() error {
	a.mu.Lock()
	a.client = nil
	a.mu.Unlock()
	return nil
}

// Middleware 返回中间件接口实现。
// 用于兼容中间件链。
func (a *AuthRequest) Middleware() middleware.Middleware {
	return a
}

// Ensure security implements Middleware interface
var _ middleware.Middleware = (*AuthRequest)(nil)
