package sms

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

	openapi "github.com/alibabacloud-go/darabonba-openapi/v2/utils"
	dysms "github.com/alibabacloud-go/dysmsapi-20170525/v5/client"
	"github.com/alibabacloud-go/tea/dara"
	"go.uber.org/zap"
)

type AliyunConfig struct {
	AccessKeyID       string
	AccessKeySecret   string
	Endpoint          string
	SignName          string
	LoginTemplateCode string
}

type AliyunProvider struct {
	client            *dysms.Client
	signName          string
	loginTemplateCode string
	log               *zap.Logger
}

func NewAliyunProvider(cfg AliyunConfig, log *zap.Logger) (*AliyunProvider, error) {
	cfg.Endpoint = strings.TrimSpace(cfg.Endpoint)
	if cfg.Endpoint == "" {
		cfg.Endpoint = "dysmsapi.aliyuncs.com"
	}
	if strings.TrimSpace(cfg.AccessKeyID) == "" ||
		strings.TrimSpace(cfg.AccessKeySecret) == "" ||
		strings.TrimSpace(cfg.SignName) == "" ||
		strings.TrimSpace(cfg.LoginTemplateCode) == "" {
		return nil, ErrProviderConfigInvalid
	}

	client, err := dysms.NewClient(&openapi.Config{
		AccessKeyId:     dara.String(strings.TrimSpace(cfg.AccessKeyID)),
		AccessKeySecret: dara.String(strings.TrimSpace(cfg.AccessKeySecret)),
		Endpoint:        dara.String(cfg.Endpoint),
	})
	if err != nil {
		return nil, err
	}

	return &AliyunProvider{
		client:            client,
		signName:          strings.TrimSpace(cfg.SignName),
		loginTemplateCode: strings.TrimSpace(cfg.LoginTemplateCode),
		log:               log,
	}, nil
}

func (p *AliyunProvider) SendLoginCode(ctx context.Context, phone string, code string) error {
	params, err := json.Marshal(map[string]string{"code": code})
	if err != nil {
		return err
	}

	req := &dysms.SendSmsRequest{}
	req.SetPhoneNumbers(phone)
	req.SetSignName(p.signName)
	req.SetTemplateCode(p.loginTemplateCode)
	req.SetTemplateParam(string(params))

	resp, err := p.client.SendSmsWithContext(ctx, req, &dara.RuntimeOptions{})
	if err != nil {
		if p.log != nil {
			p.log.Warn("aliyun sms send failed", zap.String("phone", maskPhone(phone)), zap.Error(err))
		}
		return fmt.Errorf("aliyun sms send failed: %w", err)
	}
	if resp == nil || resp.Body == nil {
		return fmt.Errorf("aliyun sms empty response")
	}

	resultCode := dara.StringValue(resp.Body.Code)
	if resultCode != "OK" {
		if p.log != nil {
			p.log.Warn(
				"aliyun sms rejected",
				zap.String("phone", maskPhone(phone)),
				zap.String("request_id", dara.StringValue(resp.Body.RequestId)),
				zap.String("code", resultCode),
				zap.String("message", dara.StringValue(resp.Body.Message)),
			)
		}
		return fmt.Errorf("aliyun sms rejected: %s", resultCode)
	}

	if p.log != nil {
		p.log.Info(
			"aliyun sms sent",
			zap.String("phone", maskPhone(phone)),
			zap.String("request_id", dara.StringValue(resp.Body.RequestId)),
			zap.String("biz_id", dara.StringValue(resp.Body.BizId)),
		)
	}
	return nil
}

func maskPhone(phone string) string {
	if len(phone) < 7 {
		return phone
	}
	return phone[:3] + "****" + phone[len(phone)-4:]
}
