package identity

import (
	"context"
	"encoding/json"
	"fmt"
	"log/slog"
	"strings"

	openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
	dypns "github.com/alibabacloud-go/dypnsapi-20170525/v3/client"
	"github.com/alibabacloud-go/tea/tea"
	"gorm.io/gorm"

	"qycw/go-server/internal/plugins"
)

type SMSProvider interface {
	SendVerificationCode(ctx context.Context, phone string, code string, purpose string) error
}

type LoggingSMSProvider struct {
	logger *slog.Logger
}

func NewLoggingSMSProvider(logger *slog.Logger) LoggingSMSProvider {
	return LoggingSMSProvider{logger: logger}
}

func (p LoggingSMSProvider) SendVerificationCode(ctx context.Context, phone string, code string, purpose string) error {
	_ = ctx
	if p.logger != nil {
		p.logger.Warn("sms provider is not migrated yet; verification code saved but not sent", "purpose", purpose, "phone", phone)
	}
	return nil
}

type DBSMSProvider struct {
	db      *gorm.DB
	logger  *slog.Logger
	plugins *plugins.Service
}

type smsConfig struct {
	Enabled         bool
	Provider        string
	AccessKeyID     string
	AccessKeySecret string
	Endpoint        string
	SignName        string
	TemplateCode    string
}

var smsTemplateByPurpose = map[string]string{
	"register":       "100001",
	"login":          "100001",
	"change_phone":   "100002",
	"reset_password": "100003",
	"bind":           "100004",
}

func NewDBSMSProvider(db *gorm.DB, logger *slog.Logger, pluginSvc *plugins.Service) DBSMSProvider {
	return DBSMSProvider{db: db, logger: logger, plugins: pluginSvc}
}

func (p DBSMSProvider) SendVerificationCode(ctx context.Context, phone string, code string, purpose string) error {
	cfg, err := p.loadConfig(ctx)
	if err != nil {
		return err
	}
	if !cfg.Enabled {
		return BadRequest("短信服务未启用")
	}
	if pluginID := plugins.PluginIDFromProvider(cfg.Provider); pluginID != "" {
		if p.plugins == nil {
			return ServiceUnavailable("短信插件运行时未启用")
		}
		resp, err := p.plugins.SendSMS(ctx, pluginID, plugins.SMSSendRequest{
			Phone:        phone,
			Code:         code,
			Purpose:      purpose,
			TemplateCode: cfg.TemplateCode,
			TemplateData: map[string]any{"code": code, "purpose": purpose},
			Config:       p.plugins.MergeConfig(ctx, pluginID, smsConfigMap(cfg)),
		})
		if err != nil {
			return ServiceUnavailable("短信插件发送失败：" + err.Error())
		}
		if !resp.OK {
			message := strings.TrimSpace(resp.Message)
			if message == "" {
				message = "短信插件发送失败"
			}
			return ServiceUnavailable(message)
		}
		return nil
	}
	if !strings.Contains(cfg.Provider, "aliyunsms") {
		return ServiceUnavailable("当前短信插件尚未支持 Go 原生发送")
	}
	missing := missingSMSConfig(cfg)
	if len(missing) > 0 {
		return ServiceUnavailable("短信服务未配置完整：" + strings.Join(missing, ", "))
	}
	templateCode := smsTemplateByPurpose[purpose]
	if templateCode == "" {
		templateCode = cfg.TemplateCode
	}
	if templateCode == "" {
		return ServiceUnavailable("短信模板 Code 未配置")
	}

	client, err := dypns.NewClient(&openapi.Config{
		AccessKeyId:     tea.String(cfg.AccessKeyID),
		AccessKeySecret: tea.String(cfg.AccessKeySecret),
		Endpoint:        tea.String(cfg.Endpoint),
	})
	if err != nil {
		return err
	}
	validSeconds := int64(600)
	resp, err := client.SendSmsVerifyCode(&dypns.SendSmsVerifyCodeRequest{
		PhoneNumber:      tea.String(phone),
		CountryCode:      tea.String("86"),
		SignName:         tea.String(cfg.SignName),
		TemplateCode:     tea.String(templateCode),
		TemplateParam:    tea.String(fmt.Sprintf(`{"code":"%s","min":"%d"}`, code, validSeconds/60)),
		ValidTime:        tea.Int64(validSeconds),
		CodeLength:       tea.Int64(6),
		CodeType:         tea.Int64(1),
		DuplicatePolicy:  tea.Int64(1),
		Interval:         tea.Int64(60),
		ReturnVerifyCode: tea.Bool(false),
		SchemeName:       tea.String("QingYun Cloud"),
	})
	if err != nil {
		if p.logger != nil {
			p.logger.Warn("aliyun sms send failed", "phone", phone, "error", err)
		}
		return ServiceUnavailable("短信发送失败，请稍后重试")
	}
	if resp == nil || resp.Body == nil || !tea.BoolValue(resp.Body.Success) || tea.StringValue(resp.Body.Code) != "OK" {
		message := "短信发送失败"
		if resp != nil && resp.Body != nil && tea.StringValue(resp.Body.Message) != "" {
			message = tea.StringValue(resp.Body.Message)
		}
		if p.logger != nil {
			p.logger.Warn("aliyun sms rejected", "phone", phone, "message", message)
		}
		return BadRequest(message)
	}
	return nil
}

func (p DBSMSProvider) loadConfig(ctx context.Context) (smsConfig, error) {
	cfg := smsConfig{
		Provider: "mode.sms.aliyunsms.provider:create_provider",
		Endpoint: "dypnsapi.aliyuncs.com",
	}
	if p.db == nil {
		return cfg, ServiceUnavailable("数据库未启用，无法读取短信配置")
	}
	var settings []SystemSetting
	if err := p.db.WithContext(ctx).Where("`key` LIKE ?", "notification.sms.%").Find(&settings).Error; err != nil {
		return cfg, err
	}
	for _, item := range settings {
		switch item.Key {
		case "notification.sms.enabled":
			cfg.Enabled = item.Value.Bool(false)
		case "notification.sms.provider":
			cfg.Provider = settingString(item.Value, cfg.Provider)
		case "notification.sms.access_key_id":
			cfg.AccessKeyID = settingString(item.Value, "")
		case "notification.sms.access_key_secret":
			cfg.AccessKeySecret = settingString(item.Value, "")
		case "notification.sms.endpoint":
			cfg.Endpoint = settingString(item.Value, cfg.Endpoint)
		case "notification.sms.sign_name":
			cfg.SignName = settingString(item.Value, "")
		case "notification.sms.template_code":
			cfg.TemplateCode = settingString(item.Value, "")
		}
	}
	return cfg, nil
}

func missingSMSConfig(cfg smsConfig) []string {
	missing := []string{}
	if cfg.AccessKeyID == "" {
		missing = append(missing, "access_key_id")
	}
	if cfg.AccessKeySecret == "" {
		missing = append(missing, "access_key_secret")
	}
	if cfg.Endpoint == "" {
		missing = append(missing, "endpoint")
	}
	if cfg.SignName == "" {
		missing = append(missing, "sign_name")
	}
	return missing
}

func smsConfigMap(cfg smsConfig) map[string]any {
	return map[string]any{
		"enabled":           cfg.Enabled,
		"provider":          cfg.Provider,
		"access_key_id":     cfg.AccessKeyID,
		"access_key_secret": cfg.AccessKeySecret,
		"endpoint":          cfg.Endpoint,
		"sign_name":         cfg.SignName,
		"template_code":     cfg.TemplateCode,
	}
}

func settingString(raw SettingValue, fallback string) string {
	if len(raw) == 0 {
		return fallback
	}
	var decoded any
	if err := json.Unmarshal(raw, &decoded); err == nil {
		switch value := decoded.(type) {
		case string:
			if strings.TrimSpace(value) != "" {
				return strings.TrimSpace(value)
			}
		case float64, bool:
			return strings.TrimSpace(fmt.Sprint(value))
		}
	}
	text := strings.TrimSpace(string(raw))
	if text == "" {
		return fallback
	}
	return strings.Trim(text, `"`)
}
