package service

import (
	"context"
	"errors"
	"fmt"
	"log"
	"strconv"
	"strings"
	"sync/atomic"
	"time"

	"gorm.io/gorm"

	"oci-portal/internal/crypto"
	"oci-portal/internal/model"
)

// Telegram 通知相关的配置键。
const (
	settingTelegramEnabled  = "telegram_enabled"   // "1" / "0"
	settingTelegramBotToken = "telegram_bot_token" // AES-GCM 密文
	settingTelegramChatID   = "telegram_chat_id"   // 明文
)

// 通知事件开关的配置键（notify_event_{kind}），值 "1"/"0"；
// 键缺省（存量部署从未保存过）视为开启，只有显式 "0" 视为关闭。
// 云端事件按类别细分(旧总开关键 notify_event_log_event 已废弃,库中遗留无害)。
const (
	settingNotifyEventTaskFail         = "notify_event_task_fail"
	settingNotifyEventTaskRecover      = "notify_event_task_recover"
	settingNotifyEventSnatchSuccess    = "notify_event_snatch_success"
	settingNotifyEventTenantDead       = "notify_event_tenant_dead"
	settingNotifyEventTaskStop         = "notify_event_task_stop"
	settingNotifyEventLoginLock        = "notify_event_login_lock"
	settingNotifyEventModelDeprecated  = "notify_event_model_deprecated"
	settingNotifyEventLogEventInstance = "notify_event_log_event_instance"
	settingNotifyEventLogEventIdentity = "notify_event_log_event_identity"
	settingNotifyEventLogEventPolicy   = "notify_event_log_event_policy"
	settingNotifyEventLogEventRegion   = "notify_event_log_event_region"
	settingNotifyEventLogEventLogin    = "notify_event_log_event_login"
)

// 任务行为相关的配置键与取值约束（抢机连续 NotAuthenticated 熔断阈值）。
const (
	settingSnatchAuthFailLimit = "snatch_auth_fail_limit"
	defaultSnatchAuthFailLimit = 3
	minSnatchAuthFailLimit     = 1
	maxSnatchAuthFailLimit     = 10
)

// ErrInvalidAuthFailLimit 标记抢机熔断阈值越界，api 层据此返回 400。
var ErrInvalidAuthFailLimit = fmt.Errorf("熔断阈值须在 %d-%d 之间", minSnatchAuthFailLimit, maxSnatchAuthFailLimit)

// SettingService 读写系统级键值配置,敏感值经 Cipher 加密落库;
// 安全设置(WAF/真实IP头/app_url)另持内存快照供每请求热路径零 IO 读取。
type SettingService struct {
	db           *gorm.DB
	cipher       *crypto.Cipher
	security     atomic.Value // SecuritySettings 快照,ReloadSecurity/UpdateSecurity 刷新
	envPublicURL string       // PUBLIC_URL 环境变量,app_url 未配置时的回退
}

// NewSettingService 组装依赖。
func NewSettingService(db *gorm.DB, cipher *crypto.Cipher) *SettingService {
	return &SettingService{db: db, cipher: cipher}
}

// get 读取键值；键不存在视为未配置，返回空串。
func (s *SettingService) get(ctx context.Context, key string) (string, error) {
	var st model.Setting
	err := s.db.WithContext(ctx).First(&st, "key = ?", key).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return "", nil
	}
	if err != nil {
		return "", fmt.Errorf("get setting %s: %w", key, err)
	}
	return st.Value, nil
}

// set 写入键值（key 为主键，Save 即 upsert）。
func (s *SettingService) set(ctx context.Context, key, value string) error {
	st := model.Setting{Key: key, Value: value, UpdatedAt: time.Now()}
	if err := s.db.WithContext(ctx).Save(&st).Error; err != nil {
		return fmt.Errorf("set setting %s: %w", key, err)
	}
	return nil
}

// getMany 批量读取多个键;任一失败即返回错误。
func (s *SettingService) getMany(ctx context.Context, keys ...string) (map[string]string, error) {
	out := make(map[string]string, len(keys))
	for _, key := range keys {
		value, err := s.get(ctx, key)
		if err != nil {
			return nil, err
		}
		out[key] = value
	}
	return out, nil
}

// TelegramConfig 是通知发送所需的完整配置，token 已解密，仅供服务内部消费。
type TelegramConfig struct {
	Enabled  bool
	BotToken string
	ChatID   string
}

// TelegramConfig 返回解密后的 Telegram 配置。
func (s *SettingService) TelegramConfig(ctx context.Context) (TelegramConfig, error) {
	enabled, err := s.get(ctx, settingTelegramEnabled)
	if err != nil {
		return TelegramConfig{}, err
	}
	chatID, err := s.get(ctx, settingTelegramChatID)
	if err != nil {
		return TelegramConfig{}, err
	}
	token, err := s.telegramToken(ctx)
	if err != nil {
		return TelegramConfig{}, err
	}
	return TelegramConfig{Enabled: enabled == "1", BotToken: token, ChatID: chatID}, nil
}

// telegramToken 读取并解密 bot token；未配置时返回空串。
func (s *SettingService) telegramToken(ctx context.Context) (string, error) {
	enc, err := s.get(ctx, settingTelegramBotToken)
	if err != nil || enc == "" {
		return "", err
	}
	token, err := s.cipher.DecryptString(enc)
	if err != nil {
		return "", fmt.Errorf("decrypt telegram token: %w", err)
	}
	return token, nil
}

// TelegramView 是设置接口的响应视图，绝不包含 token 明文。
type TelegramView struct {
	Enabled   bool   `json:"enabled"`
	ChatID    string `json:"chatId"`
	TokenSet  bool   `json:"tokenSet"`
	TokenTail string `json:"tokenTail"`
}

// TelegramView 返回脱敏后的 Telegram 配置视图。
func (s *SettingService) TelegramView(ctx context.Context) (TelegramView, error) {
	cfg, err := s.TelegramConfig(ctx)
	if err != nil {
		return TelegramView{}, err
	}
	return TelegramView{
		Enabled:   cfg.Enabled,
		ChatID:    cfg.ChatID,
		TokenSet:  cfg.BotToken != "",
		TokenTail: tokenTail(cfg.BotToken),
	}, nil
}

// tokenTail 取 token 尾 4 位用于展示；过短时不展示，避免变相泄露。
func tokenTail(token string) string {
	if len(token) < 8 {
		return ""
	}
	return token[len(token)-4:]
}

// UpdateTelegramInput 是更新 Telegram 配置的输入；
// BotToken 为 nil 沿用已存 token，非 nil 时覆盖（空串表示清除）。
type UpdateTelegramInput struct {
	Enabled  bool
	BotToken *string
	ChatID   string
}

// UpdateTelegram 保存 Telegram 配置，token 加密落库。
func (s *SettingService) UpdateTelegram(ctx context.Context, in UpdateTelegramInput) error {
	enabled := "0"
	if in.Enabled {
		enabled = "1"
	}
	if err := s.set(ctx, settingTelegramEnabled, enabled); err != nil {
		return err
	}
	if err := s.set(ctx, settingTelegramChatID, in.ChatID); err != nil {
		return err
	}
	if in.BotToken == nil {
		return nil
	}
	return s.saveTelegramToken(ctx, *in.BotToken)
}

// saveTelegramToken 加密保存 token；空串直接落空值表示清除。
func (s *SettingService) saveTelegramToken(ctx context.Context, token string) error {
	if token == "" {
		return s.set(ctx, settingTelegramBotToken, "")
	}
	enc, err := s.cipher.EncryptString(token)
	if err != nil {
		return fmt.Errorf("encrypt telegram token: %w", err)
	}
	return s.set(ctx, settingTelegramBotToken, enc)
}

// NotifyEventsView 是通知事件开关视图；零值无意义，一律由 NotifyEvents 构造。
// 云端事件按类别细分:实例生命周期 / 用户与凭据 / 策略 / 区域订阅 / 控制台登录。
type NotifyEventsView struct {
	TaskFail         bool `json:"taskFail"`
	TaskRecover      bool `json:"taskRecover"`
	SnatchSuccess    bool `json:"snatchSuccess"`
	TenantDead       bool `json:"tenantDead"`
	TaskStop         bool `json:"taskStop"`
	LoginLock        bool `json:"loginLock"`
	ModelDeprecated  bool `json:"modelDeprecated"`
	LogEventInstance bool `json:"logEventInstance"`
	LogEventIdentity bool `json:"logEventIdentity"`
	LogEventPolicy   bool `json:"logEventPolicy"`
	LogEventRegion   bool `json:"logEventRegion"`
	LogEventLogin    bool `json:"logEventLogin"`
}

// notifyEventFields 把视图字段与配置键一一对应，供读写复用。
func notifyEventFields(v *NotifyEventsView) []struct {
	key string
	on  *bool
} {
	return []struct {
		key string
		on  *bool
	}{
		{settingNotifyEventTaskFail, &v.TaskFail},
		{settingNotifyEventTaskRecover, &v.TaskRecover},
		{settingNotifyEventSnatchSuccess, &v.SnatchSuccess},
		{settingNotifyEventTenantDead, &v.TenantDead},
		{settingNotifyEventTaskStop, &v.TaskStop},
		{settingNotifyEventLoginLock, &v.LoginLock},
		{settingNotifyEventModelDeprecated, &v.ModelDeprecated},
		{settingNotifyEventLogEventInstance, &v.LogEventInstance},
		{settingNotifyEventLogEventIdentity, &v.LogEventIdentity},
		{settingNotifyEventLogEventPolicy, &v.LogEventPolicy},
		{settingNotifyEventLogEventRegion, &v.LogEventRegion},
		{settingNotifyEventLogEventLogin, &v.LogEventLogin},
	}
}

// NotifyEvents 返回全部通知事件开关；键缺省视为开启，兼容存量部署。
func (s *SettingService) NotifyEvents(ctx context.Context) (NotifyEventsView, error) {
	var view NotifyEventsView
	for _, f := range notifyEventFields(&view) {
		val, err := s.get(ctx, f.key)
		if err != nil {
			return NotifyEventsView{}, err
		}
		*f.on = val != "0"
	}
	return view, nil
}

// UpdateNotifyEvents 全量保存五个通知事件开关。
func (s *SettingService) UpdateNotifyEvents(ctx context.Context, in NotifyEventsView) error {
	for _, f := range notifyEventFields(&in) {
		value := "0"
		if *f.on {
			value = "1"
		}
		if err := s.set(ctx, f.key, value); err != nil {
			return err
		}
	}
	return nil
}

// NotifyEventEnabled 查询单个通知事件开关，供发送前过滤；
// 只有显式 "0" 视为关闭，键缺省或读取失败一律按开启返回（降级不漏发），错误仅记日志。
func (s *SettingService) NotifyEventEnabled(ctx context.Context, kind string) bool {
	val, err := s.get(ctx, "notify_event_"+kind)
	if err != nil {
		log.Printf("notify event %s switch: %v", kind, err)
		return true
	}
	return val != "0"
}

// NotifyTemplate 读取 kind 的自定义模板;空串表示未自定义(按默认模板发送)。
func (s *SettingService) NotifyTemplate(ctx context.Context, kind string) (string, error) {
	return s.get(ctx, notifyTplKey(kind))
}

// NotifyTemplates 返回全部通知模板视图(默认模板 + 自定义现值),顺序稳定。
func (s *SettingService) NotifyTemplates(ctx context.Context) ([]NotifyTemplateView, error) {
	out := make([]NotifyTemplateView, 0, len(notifyTplOrder))
	for _, kind := range notifyTplOrder {
		def := notifyTplDefs[kind]
		custom, err := s.get(ctx, notifyTplKey(kind))
		if err != nil {
			return nil, err
		}
		out = append(out, NotifyTemplateView{
			Kind: kind, Label: def.Label, Vars: def.Vars,
			DefaultTemplate: def.Default, Template: custom,
		})
	}
	return out, nil
}

// UpdateNotifyTemplate 保存 kind 的自定义模板;空串清除自定义(恢复默认)。
func (s *SettingService) UpdateNotifyTemplate(ctx context.Context, kind, tpl string) error {
	if !notifyTplValid(kind) {
		return fmt.Errorf("未知通知类型 %q", kind)
	}
	tpl = strings.TrimSpace(tpl)
	if len([]rune(tpl)) > 2000 {
		return fmt.Errorf("模板长度不能超过 2000 字符")
	}
	return s.set(ctx, notifyTplKey(kind), tpl)
}

// TaskSettingsView 是任务行为设置的读写视图。
type TaskSettingsView struct {
	SnatchAuthFailLimit int `json:"snatchAuthFailLimit"`
}

// TaskSettings 返回任务行为设置；阈值缺省或非法（非整数 / 越界脏数据）时回退默认值。
func (s *SettingService) TaskSettings(ctx context.Context) (TaskSettingsView, error) {
	val, err := s.get(ctx, settingSnatchAuthFailLimit)
	if err != nil {
		return TaskSettingsView{}, err
	}
	limit, err := strconv.Atoi(val)
	if err != nil || limit < minSnatchAuthFailLimit || limit > maxSnatchAuthFailLimit {
		limit = defaultSnatchAuthFailLimit
	}
	return TaskSettingsView{SnatchAuthFailLimit: limit}, nil
}

// UpdateTaskSettings 保存任务行为设置；阈值越界返回 ErrInvalidAuthFailLimit。
func (s *SettingService) UpdateTaskSettings(ctx context.Context, in TaskSettingsView) error {
	if in.SnatchAuthFailLimit < minSnatchAuthFailLimit || in.SnatchAuthFailLimit > maxSnatchAuthFailLimit {
		return fmt.Errorf("update task settings: %w", ErrInvalidAuthFailLimit)
	}
	return s.set(ctx, settingSnatchAuthFailLimit, strconv.Itoa(in.SnatchAuthFailLimit))
}
