from __future__ import annotations

import json
import re
import time
from dataclasses import dataclass
from pathlib import Path

import requests


class VerificationCodeError(RuntimeError):
    pass


@dataclass(frozen=True)
class FeishuConfig:
    app_id: str
    app_secret: str
    chat_id: str
    timeout_seconds: int = 60
    poll_interval_seconds: int = 3


class FeishuSmsCodeClient:
    def __init__(self, config: FeishuConfig):
        self.config = config

    def fetch_code(self, phone: str, after_timestamp: float | None = None) -> str:
        token = self._tenant_access_token()
        deadline = time.time() + self.config.timeout_seconds
        last_error = None

        while time.time() < deadline:
            try:
                messages = self._list_recent_messages(token)
                code = self._extract_code(messages, phone, after_timestamp=after_timestamp)
                if code:
                    return code
            except Exception as exc:
                last_error = exc
            time.sleep(self.config.poll_interval_seconds)

        detail = f" Last error: {last_error}" if last_error else ""
        raise VerificationCodeError(f"未在飞书测试通知群中找到手机号 {phone} 的短信验证码。{detail}")

    def _tenant_access_token(self) -> str:
        response = requests.post(
            "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
            json={
                "app_id": self.config.app_id,
                "app_secret": self.config.app_secret,
            },
            timeout=15,
        )
        response.raise_for_status()
        payload = response.json()
        if payload.get("code") != 0:
            raise VerificationCodeError(f"获取飞书 tenant_access_token 失败: {payload}")
        return payload["tenant_access_token"]

    def _list_recent_messages(self, token: str) -> list[dict]:
        response = requests.get(
            "https://open.feishu.cn/open-apis/im/v1/messages",
            headers={"Authorization": f"Bearer {token}"},
            params={
                "container_id_type": "chat",
                "container_id": self.config.chat_id,
                "page_size": 50,
                "sort_type": "ByCreateTimeDesc",
            },
            timeout=15,
        )
        response.raise_for_status()
        payload = response.json()
        if payload.get("code") != 0:
            raise VerificationCodeError(f"读取飞书群消息失败: {payload}")
        return payload.get("data", {}).get("items", [])

    def _extract_code(self, messages: list[dict], phone: str, after_timestamp: float | None = None) -> str | None:
        for message in messages:
            if after_timestamp is not None:
                create_time = self._message_create_timestamp(message)
                if create_time is not None and create_time + 1 < after_timestamp:
                    continue
            text = self._message_text(message)
            if phone not in text:
                continue
            match = re.search(r"(?<!\d)(\d{4,8})(?!\d)", text)
            if match:
                return match.group(1)
        return None

    def _message_create_timestamp(self, message: dict) -> float | None:
        raw_time = message.get("create_time") or message.get("update_time")
        if raw_time is None:
            return None
        try:
            value = float(raw_time)
        except (TypeError, ValueError):
            return None
        if value > 10_000_000_000:
            value = value / 1000
        return value

    def _message_text(self, message: dict) -> str:
        content = message.get("body", {}).get("content", "")
        if not content:
            return ""
        try:
            decoded = json.loads(content)
        except json.JSONDecodeError:
            return content

        parts = []
        for value in decoded.values():
            if isinstance(value, str):
                parts.append(value)
            elif isinstance(value, list):
                parts.extend(str(item) for item in value)
            elif value is not None:
                parts.append(str(value))
        return " ".join(parts)


@dataclass(frozen=True)
class VerificationCodeProvider:
    mode: str
    static_code: str = ""
    feishu_config: FeishuConfig | None = None
    manual_code_file: str = "runtime/sms_code.txt"
    manual_timeout_seconds: int = 180
    manual_poll_interval_seconds: int = 2

    def get_code(self, phone: str, after_timestamp: float | None = None) -> str:
        if self.mode == "static":
            if not self.static_code:
                raise VerificationCodeError("E2E_VERIFICATION_CODE_STATIC is empty")
            return self.static_code

        if self.mode == "manual":
            code = self._manual_input(phone)
            if not code:
                raise VerificationCodeError("手动输入的短信验证码为空")
            return code

        if self.mode == "feishu":
            if self.feishu_config is None:
                raise VerificationCodeError("飞书验证码配置缺失，请配置 FEISHU_APP_ID、FEISHU_APP_SECRET、FEISHU_CHAT_ID")
            return FeishuSmsCodeClient(self.feishu_config).fetch_code(phone, after_timestamp=after_timestamp)

        raise VerificationCodeError(f"Unsupported verification code mode: {self.mode}")

    def _manual_input(self, phone: str) -> str:
        code_file = Path(self.manual_code_file)
        code_file.parent.mkdir(parents=True, exist_ok=True)
        code_file.write_text("", encoding="utf-8")

        print("")
        print("[E2E manual SMS] 短信验证码已触发。")
        print(f"[E2E manual SMS] 手机号: {phone}")
        print(f"[E2E manual SMS] 请把飞书测试通知群收到的验证码写入: {code_file.resolve()}")
        print(f"[E2E manual SMS] 最长等待 {self.manual_timeout_seconds} 秒...")

        deadline = time.time() + self.manual_timeout_seconds
        while time.time() < deadline:
            raw_code = code_file.read_text(encoding="utf-8").strip()
            match = re.search(r"(?<!\d)(\d{4,8})(?!\d)", raw_code)
            if match:
                code = match.group(1)
                code_file.write_text("", encoding="utf-8")
                return code
            time.sleep(self.manual_poll_interval_seconds)

        raise VerificationCodeError(
            f"等待手动短信验证码超时，请确认已将手机号 {phone} 的验证码写入 {code_file.resolve()}"
        )
