package service

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

// 通知渠道配置键,前缀 notify_ch_<type>_;标注「密文」的键 AES-GCM 加密落库。
const (
	settingChWebhookEnabled = "notify_ch_webhook_enabled" // "1"/"0"
	settingChWebhookURL     = "notify_ch_webhook_url"
	settingChWebhookBodyTpl = "notify_ch_webhook_body_tpl"
	settingChNtfyEnabled    = "notify_ch_ntfy_enabled"
	settingChNtfyServer     = "notify_ch_ntfy_server"
	settingChNtfyTopic      = "notify_ch_ntfy_topic"
	settingChNtfyToken      = "notify_ch_ntfy_token" // 密文
	settingChBarkEnabled    = "notify_ch_bark_enabled"
	settingChBarkServer     = "notify_ch_bark_server"
	settingChBarkDeviceKey  = "notify_ch_bark_device_key" // 密文
	settingChSMTPEnabled    = "notify_ch_smtp_enabled"
	settingChSMTPHost       = "notify_ch_smtp_host"
	settingChSMTPPort       = "notify_ch_smtp_port"
	settingChSMTPUsername   = "notify_ch_smtp_username"
	settingChSMTPPassword   = "notify_ch_smtp_password" // 密文
	settingChSMTPFrom       = "notify_ch_smtp_from"
	settingChSMTPTo         = "notify_ch_smtp_to"
)

// ErrUnknownNotifyChannel 标记未知渠道类型,api 层映射 400。
var ErrUnknownNotifyChannel = fmt.Errorf("未知通知渠道类型")

// NotifyChannelView 是单渠道的脱敏视图:密文字段只回是否已配置与尾 4 位。
type NotifyChannelView struct {
	Type    string `json:"type"`
	Enabled bool   `json:"enabled"`
	// webhook
	URL          string `json:"url,omitempty"`
	BodyTemplate string `json:"bodyTemplate,omitempty"`
	// ntfy / bark 共用 Server
	Server string `json:"server,omitempty"`
	Topic  string `json:"topic,omitempty"`
	// smtp
	Host     string `json:"host,omitempty"`
	Port     int    `json:"port,omitempty"`
	Username string `json:"username,omitempty"`
	From     string `json:"from,omitempty"`
	To       string `json:"to,omitempty"`
	// 密文字段状态(ntfy token / bark device key / smtp password)
	SecretSet  bool   `json:"secretSet"`
	SecretTail string `json:"secretTail,omitempty"`
}

// UpdateNotifyChannelInput 是单渠道更新入参;Secret 为 nil 沿用已存密文,空串清除。
type UpdateNotifyChannelInput struct {
	Enabled      bool
	URL          string
	BodyTemplate string
	Server       string
	Topic        string
	Host         string
	Port         int
	Username     string
	From         string
	To           string
	Secret       *string
}

// notifyChannelKeys 返回渠道的(启用键, 明文键列表, 密文键);未知类型返回启用键空串。
func notifyChannelKeys(chType string) (enabled string, plain []string, secret string) {
	switch chType {
	case ChannelWebhook:
		return settingChWebhookEnabled, []string{settingChWebhookURL, settingChWebhookBodyTpl}, ""
	case ChannelNtfy:
		return settingChNtfyEnabled, []string{settingChNtfyServer, settingChNtfyTopic}, settingChNtfyToken
	case ChannelBark:
		return settingChBarkEnabled, []string{settingChBarkServer}, settingChBarkDeviceKey
	case ChannelSMTP:
		return settingChSMTPEnabled, []string{settingChSMTPHost, settingChSMTPPort,
			settingChSMTPUsername, settingChSMTPFrom, settingChSMTPTo}, settingChSMTPPassword
	}
	return "", nil, ""
}

// NotifyChannels 返回全部新增渠道的脱敏视图(不含 telegram,其沿用独立接口)。
func (s *SettingService) NotifyChannels(ctx context.Context) ([]NotifyChannelView, error) {
	out := make([]NotifyChannelView, 0, len(NotifyChannelTypes))
	for _, t := range NotifyChannelTypes {
		v, err := s.notifyChannelView(ctx, t)
		if err != nil {
			return nil, err
		}
		out = append(out, v)
	}
	return out, nil
}

// notifyChannelView 组装单渠道脱敏视图。
func (s *SettingService) notifyChannelView(ctx context.Context, chType string) (NotifyChannelView, error) {
	enabledKey, plain, secretKey := notifyChannelKeys(chType)
	vals, err := s.getMany(ctx, append(plain, enabledKey)...)
	if err != nil {
		return NotifyChannelView{}, err
	}
	view := NotifyChannelView{Type: chType, Enabled: vals[enabledKey] == "1"}
	fillChannelView(&view, chType, vals)
	if secretKey == "" {
		return view, nil
	}
	secret, err := s.decryptSetting(ctx, secretKey)
	if err != nil {
		return NotifyChannelView{}, err
	}
	view.SecretSet = secret != ""
	view.SecretTail = tokenTail(secret)
	return view, nil
}

// fillChannelView 把明文键值填入视图对应字段。
func fillChannelView(v *NotifyChannelView, chType string, vals map[string]string) {
	switch chType {
	case ChannelWebhook:
		v.URL, v.BodyTemplate = vals[settingChWebhookURL], vals[settingChWebhookBodyTpl]
	case ChannelNtfy:
		v.Server, v.Topic = vals[settingChNtfyServer], vals[settingChNtfyTopic]
	case ChannelBark:
		v.Server = vals[settingChBarkServer]
	case ChannelSMTP:
		v.Host, v.Username = vals[settingChSMTPHost], vals[settingChSMTPUsername]
		v.From, v.To = vals[settingChSMTPFrom], vals[settingChSMTPTo]
		v.Port, _ = strconv.Atoi(vals[settingChSMTPPort])
	}
}

// UpdateNotifyChannel 保存单渠道配置;字段先经 validateChannelInput 校验。
func (s *SettingService) UpdateNotifyChannel(ctx context.Context, chType string, in UpdateNotifyChannelInput) error {
	enabledKey, _, secretKey := notifyChannelKeys(chType)
	if enabledKey == "" {
		return ErrUnknownNotifyChannel
	}
	if err := validateChannelInput(chType, in); err != nil {
		return err
	}
	pairs := channelPlainPairs(chType, in)
	pairs[enabledKey] = boolSetting(in.Enabled)
	for k, v := range pairs {
		if err := s.set(ctx, k, v); err != nil {
			return err
		}
	}
	if secretKey == "" || in.Secret == nil {
		return nil
	}
	return s.encryptSetting(ctx, secretKey, *in.Secret)
}

// channelPlainPairs 返回渠道明文键与入参值的对应关系。
func channelPlainPairs(chType string, in UpdateNotifyChannelInput) map[string]string {
	switch chType {
	case ChannelWebhook:
		return map[string]string{settingChWebhookURL: in.URL, settingChWebhookBodyTpl: in.BodyTemplate}
	case ChannelNtfy:
		return map[string]string{settingChNtfyServer: in.Server, settingChNtfyTopic: in.Topic}
	case ChannelBark:
		return map[string]string{settingChBarkServer: in.Server}
	case ChannelSMTP:
		return map[string]string{settingChSMTPHost: in.Host, settingChSMTPPort: strconv.Itoa(in.Port),
			settingChSMTPUsername: in.Username, settingChSMTPFrom: in.From, settingChSMTPTo: in.To}
	}
	return nil
}

// validateChannelInput 按渠道校验入参;仅在启用时强校验必填,便于「先存草稿后启用」。
func validateChannelInput(chType string, in UpdateNotifyChannelInput) error {
	if !in.Enabled {
		return validateChannelURLs(chType, in)
	}
	switch chType {
	case ChannelWebhook:
		if strings.TrimSpace(in.URL) == "" {
			return fmt.Errorf("启用 Webhook 须填写 URL")
		}
	case ChannelNtfy:
		if strings.TrimSpace(in.Topic) == "" {
			return fmt.Errorf("启用 ntfy 须填写 topic")
		}
	case ChannelSMTP:
		if in.Host == "" || in.Port <= 0 || in.Port > 65535 || in.From == "" || in.To == "" {
			return fmt.Errorf("启用 SMTP 须填写主机、端口(1-65535)、发件人与收件人")
		}
	}
	return validateChannelURLs(chType, in)
}

// validateChannelURLs 校验渠道内的 URL 形字段(非空才校验)。
func validateChannelURLs(chType string, in UpdateNotifyChannelInput) error {
	if chType == ChannelWebhook && in.URL != "" {
		return validNotifyURL(in.URL)
	}
	if (chType == ChannelNtfy || chType == ChannelBark) && in.Server != "" {
		return validNotifyURL(in.Server)
	}
	return nil
}

// boolSetting 把布尔序列化为设置值 "1"/"0"。
func boolSetting(b bool) string {
	if b {
		return "1"
	}
	return "0"
}

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

// decryptSetting 读取并解密设置值;未配置返回空串。
func (s *SettingService) decryptSetting(ctx context.Context, key string) (string, error) {
	enc, err := s.get(ctx, key)
	if err != nil || enc == "" {
		return "", err
	}
	val, err := s.cipher.DecryptString(enc)
	if err != nil {
		return "", fmt.Errorf("decrypt setting %s: %w", key, err)
	}
	return val, nil
}

// webhookChannel 读取解密后的 Webhook 配置,供发送端消费。
func (s *SettingService) webhookChannel(ctx context.Context) (WebhookChannel, error) {
	vals, err := s.getMany(ctx, settingChWebhookEnabled, settingChWebhookURL, settingChWebhookBodyTpl)
	if err != nil {
		return WebhookChannel{}, err
	}
	return WebhookChannel{
		Enabled:      vals[settingChWebhookEnabled] == "1",
		URL:          vals[settingChWebhookURL],
		BodyTemplate: vals[settingChWebhookBodyTpl],
	}, nil
}

// ntfyChannel 读取解密后的 ntfy 配置。
func (s *SettingService) ntfyChannel(ctx context.Context) (NtfyChannel, error) {
	vals, err := s.getMany(ctx, settingChNtfyEnabled, settingChNtfyServer, settingChNtfyTopic)
	if err != nil {
		return NtfyChannel{}, err
	}
	token, err := s.decryptSetting(ctx, settingChNtfyToken)
	if err != nil {
		return NtfyChannel{}, err
	}
	return NtfyChannel{
		Enabled: vals[settingChNtfyEnabled] == "1",
		Server:  vals[settingChNtfyServer],
		Topic:   vals[settingChNtfyTopic],
		Token:   token,
	}, nil
}

// barkChannel 读取解密后的 Bark 配置。
func (s *SettingService) barkChannel(ctx context.Context) (BarkChannel, error) {
	vals, err := s.getMany(ctx, settingChBarkEnabled, settingChBarkServer)
	if err != nil {
		return BarkChannel{}, err
	}
	key, err := s.decryptSetting(ctx, settingChBarkDeviceKey)
	if err != nil {
		return BarkChannel{}, err
	}
	return BarkChannel{
		Enabled:   vals[settingChBarkEnabled] == "1",
		Server:    vals[settingChBarkServer],
		DeviceKey: key,
	}, nil
}

// smtpChannel 读取解密后的 SMTP 配置。
func (s *SettingService) smtpChannel(ctx context.Context) (SMTPChannel, error) {
	vals, err := s.getMany(ctx, settingChSMTPEnabled, settingChSMTPHost, settingChSMTPPort,
		settingChSMTPUsername, settingChSMTPFrom, settingChSMTPTo)
	if err != nil {
		return SMTPChannel{}, err
	}
	password, err := s.decryptSetting(ctx, settingChSMTPPassword)
	if err != nil {
		return SMTPChannel{}, err
	}
	port, _ := strconv.Atoi(vals[settingChSMTPPort])
	return SMTPChannel{
		Enabled:  vals[settingChSMTPEnabled] == "1",
		Host:     vals[settingChSMTPHost],
		Port:     port,
		Username: vals[settingChSMTPUsername],
		Password: password,
		From:     vals[settingChSMTPFrom],
		To:       vals[settingChSMTPTo],
	}, nil
}
