package service

import (
	"context"
	"fmt"
	"regexp"
	"strconv"
	"strings"
)

// 安全设置键:WAF 参数 / 真实IP请求头 / 面板地址(app_url)。
const (
	settingSecLoginFailLimit = "security_login_fail_limit"
	settingSecLoginLockMin   = "security_login_lock_minutes"
	settingSecIPRateRPS      = "security_ip_rate_rps"
	settingSecIPRateBurst    = "security_ip_rate_burst"
	settingSecRealIPHeader   = "security_real_ip_header"
	settingSecAppURL         = "security_app_url"
	// 密码登录禁用开关("1" 为禁用);启停校验在 AuthService.SetPasswordLoginDisabled
	settingSecPasswordLoginOff = "security_password_login_off"
)

// SecuritySettings 是安全设置的读写视图;零值无意义,一律经 Security/SecurityCached 构造。
type SecuritySettings struct {
	// 登录守卫:LockMinutes 分钟窗口内失败 FailLimit 次锁 LockMinutes 分钟
	LoginFailLimit   int `json:"loginFailLimit"`
	LoginLockMinutes int `json:"loginLockMinutes"`
	// 全局 IP 限速令牌桶
	IPRateRPS   int `json:"ipRateRps"`
	IPRateBurst int `json:"ipRateBurst"`
	// 真实IP请求头:空 = 直连(RemoteAddr);反代后必须选择与反代匹配的头
	RealIPHeader string `json:"realIpHeader"`
	// 面板公网基址;优先于 PUBLIC_URL 环境变量,回传链路与 OAuth 回调用
	AppURL string `json:"appUrl"`
}

// securityDefaults 与探索文档主题三的推荐参数一致。
var securityDefaults = SecuritySettings{
	LoginFailLimit:   5,
	LoginLockMinutes: 15,
	IPRateRPS:        10,
	IPRateBurst:      30,
	RealIPHeader:     "",
	AppURL:           "",
}

// realIPHeaderPattern 校验真实IP请求头名:空为直连,非空仅限字母数字与连字符
// (RFC 7230 token 的常用子集),支持 X-Forwarded-For 等预设外的自定义头。
var realIPHeaderPattern = regexp.MustCompile(`^[A-Za-z0-9-]{1,64}$`)

func realIPHeaderValid(h string) bool {
	return h == "" || realIPHeaderPattern.MatchString(h)
}

// ErrInvalidSecurity 标记安全设置越界或非法,api 层据此返回 400。
var ErrInvalidSecurity = fmt.Errorf("安全设置非法")

// validateSecurity 校验范围;AppURL 规范化(去尾斜杠)。
func validateSecurity(in *SecuritySettings) error {
	switch {
	case in.LoginFailLimit < 1 || in.LoginFailLimit > 20:
		return fmt.Errorf("登录失败阈值须在 1-20 之间: %w", ErrInvalidSecurity)
	case in.LoginLockMinutes < 1 || in.LoginLockMinutes > 1440:
		return fmt.Errorf("锁定时长须在 1-1440 分钟之间: %w", ErrInvalidSecurity)
	case in.IPRateRPS < 1 || in.IPRateRPS > 100:
		return fmt.Errorf("IP 限速须在 1-100 req/s 之间: %w", ErrInvalidSecurity)
	case in.IPRateBurst < 1 || in.IPRateBurst > 500:
		return fmt.Errorf("突发额度须在 1-500 之间: %w", ErrInvalidSecurity)
	case !realIPHeaderValid(in.RealIPHeader):
		return fmt.Errorf("真实IP请求头仅限字母数字与连字符: %w", ErrInvalidSecurity)
	}
	in.AppURL = strings.TrimRight(strings.TrimSpace(in.AppURL), "/")
	if in.AppURL != "" && !strings.HasPrefix(in.AppURL, "http://") && !strings.HasPrefix(in.AppURL, "https://") {
		return fmt.Errorf("面板地址须以 http(s):// 开头: %w", ErrInvalidSecurity)
	}
	return nil
}

// Security 从库读安全设置;键缺省或脏数据按字段回退默认值。
func (s *SettingService) Security(ctx context.Context) (SecuritySettings, error) {
	out := securityDefaults
	ints := []struct {
		key string
		dst *int
		lo  int
		hi  int
	}{
		{settingSecLoginFailLimit, &out.LoginFailLimit, 1, 20},
		{settingSecLoginLockMin, &out.LoginLockMinutes, 1, 1440},
		{settingSecIPRateRPS, &out.IPRateRPS, 1, 100},
		{settingSecIPRateBurst, &out.IPRateBurst, 1, 500},
	}
	for _, f := range ints {
		val, err := s.get(ctx, f.key)
		if err != nil {
			return SecuritySettings{}, err
		}
		if n, err := strconv.Atoi(val); err == nil && n >= f.lo && n <= f.hi {
			*f.dst = n
		}
	}
	header, err := s.get(ctx, settingSecRealIPHeader)
	if err != nil {
		return SecuritySettings{}, err
	}
	if realIPHeaderValid(header) {
		out.RealIPHeader = header
	}
	if out.AppURL, err = s.get(ctx, settingSecAppURL); err != nil {
		return SecuritySettings{}, err
	}
	return out, nil
}

// UpdateSecurity 校验并保存安全设置,随后刷新内存快照立即生效。
func (s *SettingService) UpdateSecurity(ctx context.Context, in SecuritySettings) error {
	if err := validateSecurity(&in); err != nil {
		return err
	}
	kv := map[string]string{
		settingSecLoginFailLimit: strconv.Itoa(in.LoginFailLimit),
		settingSecLoginLockMin:   strconv.Itoa(in.LoginLockMinutes),
		settingSecIPRateRPS:      strconv.Itoa(in.IPRateRPS),
		settingSecIPRateBurst:    strconv.Itoa(in.IPRateBurst),
		settingSecRealIPHeader:   in.RealIPHeader,
		settingSecAppURL:         in.AppURL,
	}
	for key, value := range kv {
		if err := s.set(ctx, key, value); err != nil {
			return err
		}
	}
	s.security.Store(in)
	return nil
}

// ReloadSecurity 从库加载安全设置到内存快照;进程启动时调用一次。
func (s *SettingService) ReloadSecurity(ctx context.Context) error {
	sec, err := s.Security(ctx)
	if err != nil {
		return err
	}
	s.security.Store(sec)
	return nil
}

// SecurityCached 返回安全设置内存快照,供每请求热路径零 IO 读取;
// 快照未初始化(启动未 Reload 或测试环境)时返回默认值。
func (s *SettingService) SecurityCached() SecuritySettings {
	if v, ok := s.security.Load().(SecuritySettings); ok {
		return v
	}
	return securityDefaults
}

// securityOf 是 nil 容忍的快照读取,供未注入 settings 的调用方(测试)回退默认。
func securityOf(s *SettingService) SecuritySettings {
	if s == nil {
		return securityDefaults
	}
	return s.SecurityCached()
}

// SetEnvPublicURL 记录 PUBLIC_URL 环境变量,作为 app_url 未配置时的回退。
func (s *SettingService) SetEnvPublicURL(url string) {
	s.envPublicURL = strings.TrimRight(url, "/")
}

// PasswordLoginDisabled 读密码登录禁用开关;键缺省视为未禁用。
func (s *SettingService) PasswordLoginDisabled(ctx context.Context) (bool, error) {
	v, err := s.get(ctx, settingSecPasswordLoginOff)
	return v == "1", err
}

// SetPasswordLoginDisabled 保存密码登录禁用开关。
func (s *SettingService) SetPasswordLoginDisabled(ctx context.Context, disabled bool) error {
	v := ""
	if disabled {
		v = "1"
	}
	return s.set(ctx, settingSecPasswordLoginOff, v)
}

// EffectiveAppURL 返回生效的面板公网基址:设置里的 app_url 优先,回退环境变量。
func (s *SettingService) EffectiveAppURL() string {
	if u := s.SecurityCached().AppURL; u != "" {
		return u
	}
	return s.envPublicURL
}
