from __future__ import annotations

import hashlib
import hmac
import json
import time
from datetime import datetime, timezone
from typing import Any
from urllib.parse import urlparse

import aiohttp

from mode.verification.base import RedirectSession, VerificationProviderError, VerificationResult

TENCENT_FACEID_HOST = "faceid.tencentcloudapi.com"
TENCENT_FACEID_VERSION = "2018-03-01"
TENCENT_FACEID_SERVICE = "faceid"
TC3_ALGORITHM = "TC3-HMAC-SHA256"
LARGE_RESULT_KEYS = {
    "BestFrame",
    "BestFrames",
    "BestFrameBase64",
    "VideoData",
    "VideoBase64",
    "IdCardData",
    "IdCardImage",
    "ImageBase64",
}
PROCESSING_MARKERS = (
    "not finished",
    "not complete",
    "not completed",
    "unfinished",
    "processing",
    "in progress",
    "未完成",
    "处理中",
    "进行中",
    "等待",
    "暂无结果",
)


def _clean_text(value: Any) -> str:
    return str(value or "").strip()


def _first_config_value(config: Any, *names: str) -> str:
    for name in names:
        value = _clean_text(getattr(config, name, None))
        if value:
            return value
    return ""


def _to_int(value: Any, default: int | None = None) -> int | None:
    if value in (None, ""):
        return default
    try:
        return int(value)
    except (TypeError, ValueError):
        return default


def _to_float(value: Any, default: float | None = None) -> float | None:
    if value in (None, ""):
        return default
    try:
        return float(value)
    except (TypeError, ValueError):
        return default


def _hmac_sha256(key: bytes, value: str) -> bytes:
    return hmac.new(key, value.encode("utf-8"), hashlib.sha256).digest()


def _sha256_hex(value: str) -> str:
    return hashlib.sha256(value.encode("utf-8")).hexdigest()


def _parse_endpoint(value: str) -> tuple[str, str]:
    text = _clean_text(value) or TENCENT_FACEID_HOST
    if text.startswith("http://") or text.startswith("https://"):
        parsed = urlparse(text)
        host = parsed.netloc or TENCENT_FACEID_HOST
        scheme = parsed.scheme or "https"
        return host, f"{scheme}://{host}"
    return text, f"https://{text}"


def _parse_text_payload(value: Any) -> dict[str, Any]:
    if isinstance(value, dict):
        return value
    if isinstance(value, str):
        text = value.strip()
        if not text:
            return {}
        try:
            parsed = json.loads(text)
        except json.JSONDecodeError:
            return {"RawText": text}
        return parsed if isinstance(parsed, dict) else {"RawText": parsed}
    return {}


def _get_case(data: dict[str, Any], *names: str) -> Any:
    lowered = {str(key).lower(): value for key, value in data.items()}
    for name in names:
        if name in data:
            return data[name]
        key = name.lower()
        if key in lowered:
            return lowered[key]
    return None


def _looks_processing(*values: Any) -> bool:
    text = " ".join(_clean_text(value).lower() for value in values if value is not None)
    return any(marker.lower() in text for marker in PROCESSING_MARKERS)


def _sanitize_response(response: dict[str, Any]) -> dict[str, Any]:
    safe: dict[str, Any] = {}
    for key, value in response.items():
        if key in LARGE_RESULT_KEYS:
            continue
        if key == "Text":
            safe[key] = _parse_text_payload(value)
        elif isinstance(value, dict):
            safe[key] = {k: v for k, v in value.items() if k not in LARGE_RESULT_KEYS}
        else:
            safe[key] = value
    return safe


class TencentFaceIdRedirectProvider:
    supported_modes = ("face_redirect",)

    def __init__(
        self,
        config: Any,
        *,
        name: str,
        rule_id_attrs: tuple[str, ...],
    ):
        self.name = name
        self.secret_id = _first_config_value(config, "TENCENT_SECRET_ID", "TENCENTCLOUD_SECRET_ID", "SECRET_ID")
        self.secret_key = _first_config_value(config, "TENCENT_SECRET_KEY", "TENCENTCLOUD_SECRET_KEY", "SECRET_KEY")
        self.rule_id = _first_config_value(config, *rule_id_attrs, "TENCENT_RULE_ID", "RULE_ID")
        endpoint_value = _first_config_value(config, "TENCENT_ENDPOINT", "TENCENT_FACEID_ENDPOINT", "ENDPOINT")
        self.host, self.endpoint = _parse_endpoint(endpoint_value)
        self.info_type = _first_config_value(config, "TENCENT_INFO_TYPE", "INFO_TYPE") or "1"
        self.score_threshold = float(getattr(config, "TENCENT_SCORE_THRESHOLD", 80) or 80)
        self.timeout = aiohttp.ClientTimeout(total=float(getattr(config, "TENCENT_TIMEOUT", 20) or 20))

        if not self.secret_id or not self.secret_key:
            raise VerificationProviderError("腾讯云实名核身插件未配置 SecretId / SecretKey", status_code=400)
        if not self.rule_id:
            raise VerificationProviderError("腾讯云实名核身插件未配置 RuleId", status_code=400)

    def _signed_headers(self, *, action: str, payload: dict[str, Any]) -> tuple[dict[str, str], str]:
        timestamp = int(time.time())
        payload_json = json.dumps(payload, ensure_ascii=False, separators=(",", ":"))
        date = datetime.fromtimestamp(timestamp, timezone.utc).strftime("%Y-%m-%d")
        content_type = "application/json; charset=utf-8"
        canonical_headers = f"content-type:{content_type}\nhost:{self.host}\n"
        signed_headers = "content-type;host"
        canonical_request = "\n".join(
            [
                "POST",
                "/",
                "",
                canonical_headers,
                signed_headers,
                _sha256_hex(payload_json),
            ]
        )
        credential_scope = f"{date}/{TENCENT_FACEID_SERVICE}/tc3_request"
        string_to_sign = "\n".join(
            [
                TC3_ALGORITHM,
                str(timestamp),
                credential_scope,
                _sha256_hex(canonical_request),
            ]
        )
        secret_date = _hmac_sha256(("TC3" + self.secret_key).encode("utf-8"), date)
        secret_service = _hmac_sha256(secret_date, TENCENT_FACEID_SERVICE)
        secret_signing = _hmac_sha256(secret_service, "tc3_request")
        signature = hmac.new(secret_signing, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest()
        authorization = (
            f"{TC3_ALGORITHM} "
            f"Credential={self.secret_id}/{credential_scope}, "
            f"SignedHeaders={signed_headers}, Signature={signature}"
        )
        headers = {
            "Authorization": authorization,
            "Content-Type": content_type,
            "Host": self.host,
            "X-TC-Action": action,
            "X-TC-Timestamp": str(timestamp),
            "X-TC-Version": TENCENT_FACEID_VERSION,
        }
        return headers, payload_json

    async def _request(self, action: str, payload: dict[str, Any], *, allow_error: bool = False) -> dict[str, Any]:
        headers, payload_json = self._signed_headers(action=action, payload=payload)
        try:
            async with aiohttp.ClientSession(timeout=self.timeout) as session:
                async with session.post(self.endpoint, data=payload_json.encode("utf-8"), headers=headers) as response:
                    body = await response.json(content_type=None)
        except aiohttp.ClientError as exc:
            raise VerificationProviderError(f"请求腾讯云实名核身接口失败：{exc}", status_code=502) from exc
        except json.JSONDecodeError as exc:
            raise VerificationProviderError("腾讯云实名核身接口返回非 JSON 响应", status_code=502) from exc

        response_body = body.get("Response") if isinstance(body, dict) else None
        if not isinstance(response_body, dict):
            raise VerificationProviderError("腾讯云实名核身接口响应格式异常", status_code=502, raw=body)
        error = response_body.get("Error")
        if error and not allow_error:
            message = error.get("Message") if isinstance(error, dict) else str(error)
            code = error.get("Code") if isinstance(error, dict) else None
            raise VerificationProviderError(f"腾讯云实名核身接口错误：{message or code}", status_code=502, raw=response_body)
        return response_body

    async def verify_two_factor(self, *, real_name: str, id_card_no: str) -> VerificationResult:
        raise VerificationProviderError("腾讯云微信 H5 实名核身插件只支持跳转扫脸认证", status_code=400)

    async def verify_three_factor(self, *, real_name: str, id_card_no: str, phone: str) -> VerificationResult:
        raise VerificationProviderError("腾讯云微信 H5 实名核身插件只支持跳转扫脸认证", status_code=400)

    async def verify_face_api(
        self,
        *,
        real_name: str,
        id_card_no: str,
        image_base64: str,
        image_type: str = "BASE64",
    ) -> VerificationResult:
        raise VerificationProviderError("腾讯云微信 H5 实名核身插件不支持本地图片扫脸 API", status_code=400)

    async def create_redirect_session(
        self,
        *,
        real_name: str,
        id_card_no: str,
        return_url: str,
        notify_url: str | None = None,
    ) -> RedirectSession:
        payload: dict[str, Any] = {
            "RuleId": self.rule_id,
            "Name": real_name,
            "IdCard": id_card_no,
            "RedirectUrl": return_url,
        }
        response = await self._request("DetectAuth", payload)
        url = _clean_text(response.get("Url"))
        biz_token = _clean_text(response.get("BizToken"))
        if not url or not biz_token:
            raise VerificationProviderError("腾讯云 DetectAuth 未返回 Url 或 BizToken", status_code=502, raw=response)
        return RedirectSession(
            url=url,
            external_id=biz_token,
            expires_in=7200,
            raw=_sanitize_response(response),
        )

    async def get_redirect_result(
        self,
        *,
        external_id: str,
        real_name: str,
        id_card_no: str,
    ) -> VerificationResult:
        biz_token = _clean_text(external_id)
        response = await self._request(
            "GetDetectInfoEnhanced",
            {
                "BizToken": biz_token,
                "RuleId": self.rule_id,
                "InfoType": self.info_type,
            },
            allow_error=True,
        )
        error = response.get("Error")
        if error:
            code = error.get("Code") if isinstance(error, dict) else ""
            message = error.get("Message") if isinstance(error, dict) else str(error)
            if _looks_processing(code, message):
                return VerificationResult(
                    passed=False,
                    mode="face_redirect",
                    provider=self.name,
                    status="processing",
                    reason="核身尚未完成",
                    external_id=biz_token,
                    raw=_sanitize_response(response),
                )
            raise VerificationProviderError(f"腾讯云实名核身结果查询失败：{message or code}", status_code=502, raw=response)

        text = _parse_text_payload(response.get("Text"))
        if not text:
            return VerificationResult(
                passed=False,
                mode="face_redirect",
                provider=self.name,
                status="processing",
                reason="核身尚未完成",
                external_id=biz_token,
                raw=_sanitize_response(response),
            )

        err_code = _to_int(_get_case(text, "ErrCode"), default=None)
        err_msg = _clean_text(_get_case(text, "ErrMsg"))
        compare_status = _to_int(_get_case(text, "CompareStatus", "Comparestatus"), default=None)
        compare_msg = _clean_text(_get_case(text, "CompareMsg", "Comparemsg"))
        live_status = _to_int(_get_case(text, "LiveStatus"), default=None)
        live_msg = _clean_text(_get_case(text, "LiveMsg"))
        score = _to_float(_get_case(text, "Sim"), default=None)

        reason = err_msg or compare_msg or live_msg or "腾讯云实名核身未通过"
        if err_code not in (None, 0) or compare_status not in (None, 0) or live_status not in (None, 0):
            if _looks_processing(err_msg, compare_msg, live_msg, reason):
                status = "processing"
                reason = "核身尚未完成"
            else:
                status = "rejected"
            return VerificationResult(
                passed=False,
                mode="face_redirect",
                provider=self.name,
                status=status,
                reason=reason,
                score=score,
                external_id=biz_token,
                raw=_sanitize_response(response),
            )

        returned_name = _clean_text(_get_case(text, "Name"))
        returned_id_card = _clean_text(_get_case(text, "IdCard", "IdCardNo")).upper()
        if returned_name and returned_name != real_name.strip():
            return VerificationResult(
                passed=False,
                mode="face_redirect",
                provider=self.name,
                status="rejected",
                reason="腾讯云返回姓名与提交信息不一致",
                score=score,
                external_id=biz_token,
                raw=_sanitize_response(response),
            )
        if returned_id_card and returned_id_card != id_card_no.strip().upper():
            return VerificationResult(
                passed=False,
                mode="face_redirect",
                provider=self.name,
                status="rejected",
                reason="腾讯云返回身份证号与提交信息不一致",
                score=score,
                external_id=biz_token,
                raw=_sanitize_response(response),
            )
        if score is not None and score < self.score_threshold:
            return VerificationResult(
                passed=False,
                mode="face_redirect",
                provider=self.name,
                status="rejected",
                reason=f"人脸比对分数 {score:.2f} 低于阈值 {self.score_threshold:.2f}",
                score=score,
                external_id=biz_token,
                raw=_sanitize_response(response),
            )

        return VerificationResult(
            passed=True,
            mode="face_redirect",
            provider=self.name,
            status="verified",
            reason="",
            score=score,
            external_id=biz_token,
            raw=_sanitize_response(response),
        )
