import logging

from alibabacloud_dypnsapi20170525 import models as dypns_models
from alibabacloud_dypnsapi20170525.client import Client as DypnsClient
from alibabacloud_tea_openapi import models as openapi_models

from mode.sms.base import SmsProviderError, SmsVerificationMessage


log = logging.getLogger("qycw.sms.aliyunsms")


TEMPLATE_MAP = {
    "register": "100001",
    "login": "100001",
    "change_phone": "100002",
    "reset_password": "100003",
    "bind": "100004",
}


class AliyunSmsProvider:
    name = "aliyunsms"

    def __init__(self, settings):
        self.access_key_id = settings.SMS_ACCESS_KEY_ID
        self.access_key_secret = settings.SMS_ACCESS_KEY_SECRET
        self.endpoint = settings.SMS_ENDPOINT
        self.sign_name = settings.SMS_SIGN_NAME
        self.default_template_code = settings.SMS_TEMPLATE_CODE
        self._client: DypnsClient | None = None

    async def send_verification_code(self, message: SmsVerificationMessage) -> None:
        self._ensure_configured()
        template_code = TEMPLATE_MAP.get(message.purpose, self.default_template_code)
        if not template_code:
            raise SmsProviderError("sms template code is not configured")

        req = dypns_models.SendSmsVerifyCodeRequest(
            phone_number=message.phone,
            country_code=message.country_code,
            sign_name=self.sign_name,
            template_code=template_code,
            template_param=f'{{"code":"{message.code}","min":"{max(1, message.valid_seconds // 60)}"}}',
            valid_time=message.valid_seconds,
            code_length=6,
            code_type=1,
            duplicate_policy=1,
            interval=60,
            return_verify_code=False,
            scheme_name="QingYun Cloud",
        )
        try:
            resp = await self._get_client().send_sms_verify_code_async(req)
        except Exception as exc:  # noqa: BLE001
            log.error("send sms to %s failed: %s", message.phone, exc)
            raise SmsProviderError("sms send failed") from exc

        body = resp.body
        if not body or not body.success or body.code != "OK":
            provider_msg = (body.message if body else None) or "sms send failed"
            log.warning("send sms to %s rejected: code=%s msg=%s", message.phone, getattr(body, "code", None), provider_msg)
            raise SmsProviderError(provider_msg, status_code=400)

    def _get_client(self) -> DypnsClient:
        if self._client is None:
            config = openapi_models.Config(
                access_key_id=self.access_key_id,
                access_key_secret=self.access_key_secret,
                endpoint=self.endpoint,
            )
            self._client = DypnsClient(config)
        return self._client

    def _ensure_configured(self) -> None:
        missing = [
            name
            for name, value in {
                "SMS_ACCESS_KEY_ID": self.access_key_id,
                "SMS_ACCESS_KEY_SECRET": self.access_key_secret,
                "SMS_ENDPOINT": self.endpoint,
                "SMS_SIGN_NAME": self.sign_name,
            }.items()
            if not value
        ]
        if missing:
            raise SmsProviderError(f"sms provider missing config: {', '.join(missing)}")


def create_provider(settings) -> AliyunSmsProvider:
    return AliyunSmsProvider(settings)

