from __future__ import annotations

import hashlib
import hmac
import json
import time
from collections.abc import Mapping
from dataclasses import dataclass
from typing import Any

from sqlalchemy import select
from sqlalchemy.orm import Session

from app.core.config import get_settings
from app.models.financial_connector import FinancialConnectorConfig
from app.schemas.financial_connector import FinancialEventEnvelope
from app.services.financial_connector_operational_events import (
    FinancialConnectorOperationalContext,
    FinancialConnectorOperationalEventCandidate,
)

FINANCIAL_EVENT_SIGNATURE_PATH = "/api/v1/integrations/financial-events"


class FinancialConnectorAuthError(PermissionError):
    def __init__(
        self,
        message: str,
        *,
        code: str,
        operational_context: FinancialConnectorOperationalContext | None = None,
    ) -> None:
        super().__init__(message)
        self.code = code
        self.operational_context = operational_context
        self.operational_event: FinancialConnectorOperationalEventCandidate | None = None


@dataclass(frozen=True, slots=True)
class VerifiedConnectorRequest:
    config: FinancialConnectorConfig
    canonical_body: bytes
    request_fingerprint: str
    content_hash: str
    verification_level: str
    evidence_classification: str
    operational_context: FinancialConnectorOperationalContext


class FinancialConnectorSecretResolver:
    """从仅服务端可见配置解析密钥；数据库和响应都不持有密钥。"""

    def __init__(self, secrets: Mapping[str, str] | None = None) -> None:
        self._secrets = {
            str(key).strip(): str(value)
            for key, value in dict(secrets or {}).items()
            if str(key).strip() and str(value)
        }

    @classmethod
    def from_settings(cls) -> FinancialConnectorSecretResolver:
        raw = str(get_settings().financial_connector_hmac_keys_json or "").strip()
        if not raw:
            return cls()
        try:
            parsed = json.loads(raw)
        except (TypeError, ValueError) as error:
            raise FinancialConnectorAuthError(
                "服务端连接器密钥配置无法解析。",
                code="secret_configuration_invalid",
            ) from error
        if not isinstance(parsed, dict):
            raise FinancialConnectorAuthError(
                "服务端连接器密钥配置格式无效。",
                code="secret_configuration_invalid",
            )
        return cls(parsed)

    def resolve(self, secret_ref: str) -> bytes:
        value = self._secrets.get(str(secret_ref or "").strip())
        if not value:
            raise FinancialConnectorAuthError(
                "连接器密钥不可用。",
                code="secret_unavailable",
            )
        secret = value.encode("utf-8")
        if len(secret) < 16 or not value.strip():
            raise FinancialConnectorAuthError(
                "连接器密钥强度不足。",
                code="secret_too_short",
            )
        return secret


class FinancialConnectorAuthenticator:
    def __init__(
        self,
        db: Session,
        *,
        secrets: FinancialConnectorSecretResolver | None = None,
        now_epoch: int | None = None,
    ) -> None:
        self.db = db
        self.secrets = secrets or FinancialConnectorSecretResolver.from_settings()
        self.now_epoch = int(now_epoch) if now_epoch is not None else None

    def verify(
        self,
        envelope: FinancialEventEnvelope,
        *,
        tenant_header: str,
        provider_header: str,
        key_version_header: str,
        timestamp_header: str,
        signature_header: str,
    ) -> VerifiedConnectorRequest:
        tenant_id = str(tenant_header or "").strip()
        provider = str(provider_header or "").strip().lower()
        key_version = str(key_version_header or "").strip()
        if not tenant_id or not provider or not key_version:
            raise FinancialConnectorAuthError(
                "连接器认证头不完整。",
                code="authentication_headers_missing",
            )
        if envelope.tenant_id != tenant_id:
            raise FinancialConnectorAuthError(
                "事件租户与签名租户不一致。",
                code="tenant_mismatch",
            )
        config = self.db.scalar(
            select(FinancialConnectorConfig).where(
                FinancialConnectorConfig.tenant_id == tenant_id,
                FinancialConnectorConfig.provider == provider,
                FinancialConnectorConfig.key_version == key_version,
            )
        )
        if config is None or config.status != "active":
            raise FinancialConnectorAuthError(
                "连接器配置不存在或未激活。",
                code="connector_inactive",
            )
        secret = self.secrets.resolve(config.secret_ref)
        operational_context = _operational_context(
            envelope,
            config=config,
            secret=secret,
        )
        try:
            timestamp = int(str(timestamp_header or "").strip())
        except ValueError as error:
            raise FinancialConnectorAuthError(
                "连接器时间戳无效。",
                code="timestamp_invalid",
                operational_context=operational_context,
            ) from error
        now_epoch = self.now_epoch if self.now_epoch is not None else int(time.time())
        if abs(now_epoch - timestamp) > int(config.clock_skew_seconds):
            raise FinancialConnectorAuthError(
                "连接器事件超出允许时间窗口。",
                code="timestamp_outside_window",
                operational_context=operational_context,
            )
        allowed = {str(item).strip() for item in list(config.allowed_event_types_json or [])}
        if envelope.event_type not in allowed:
            raise FinancialConnectorAuthError(
                "连接器未获准发送该事件类型。",
                code="event_type_not_allowed",
                operational_context=operational_context,
            )

        canonical = canonical_financial_event(envelope)
        signed_request = canonical_financial_request(
            envelope,
            tenant_id=tenant_id,
            provider=provider,
            key_version=key_version,
            timestamp=timestamp,
        )
        expected = hmac.new(
            secret,
            signed_request,
            hashlib.sha256,
        ).hexdigest()
        supplied = str(signature_header or "").strip().lower()
        if supplied.startswith("sha256="):
            supplied = supplied.removeprefix("sha256=")
        if len(supplied) != 64 or not hmac.compare_digest(expected, supplied):
            raise FinancialConnectorAuthError(
                "连接器签名验证失败。",
                code="signature_invalid",
                operational_context=operational_context,
            )

        content_digest = hashlib.sha256(canonical).hexdigest()
        request_digest = hashlib.sha256(
            canonical_financial_request(
                envelope,
                tenant_id=tenant_id,
                provider=provider,
                key_version=key_version,
                timestamp=None,
            )
        ).hexdigest()
        verification, classification = _verification_for_environment(config.environment)
        return VerifiedConnectorRequest(
            config=config,
            canonical_body=canonical,
            request_fingerprint=f"sha256:{request_digest}",
            content_hash=f"sha256:{content_digest}",
            verification_level=verification,
            evidence_classification=classification,
            operational_context=operational_context,
        )


def canonical_financial_event(envelope: FinancialEventEnvelope | dict[str, Any]) -> bytes:
    payload = (
        envelope.model_dump(mode="json")
        if isinstance(envelope, FinancialEventEnvelope)
        else dict(envelope)
    )
    return json.dumps(
        payload,
        ensure_ascii=False,
        sort_keys=True,
        separators=(",", ":"),
    ).encode("utf-8")


def sign_financial_event(
    envelope: FinancialEventEnvelope | dict[str, Any],
    *,
    timestamp: int,
    secret: str,
    tenant_id: str,
    provider: str,
    key_version: str,
) -> str:
    canonical = canonical_financial_request(
        envelope,
        tenant_id=tenant_id,
        provider=provider,
        key_version=key_version,
        timestamp=timestamp,
    )
    digest = hmac.new(
        secret.encode("utf-8"),
        canonical,
        hashlib.sha256,
    ).hexdigest()
    return f"sha256={digest}"


def canonical_financial_request(
    envelope: FinancialEventEnvelope | dict[str, Any],
    *,
    tenant_id: str,
    provider: str,
    key_version: str,
    timestamp: int | None,
) -> bytes:
    """签名上下文绑定来源命名空间与固定 HTTP 入口，时间戳可从幂等指纹排除。"""

    body = (
        envelope.model_dump(mode="json")
        if isinstance(envelope, FinancialEventEnvelope)
        else dict(envelope)
    )
    return json.dumps(
        {
            "version": "x-financial-event-v2",
            "method": "POST",
            "path": FINANCIAL_EVENT_SIGNATURE_PATH,
            "tenant_id": str(tenant_id or "").strip(),
            "provider": str(provider or "").strip().lower(),
            "key_version": str(key_version or "").strip(),
            "timestamp": timestamp,
            "body": body,
        },
        ensure_ascii=False,
        sort_keys=True,
        separators=(",", ":"),
    ).encode("utf-8")


def _verification_for_environment(environment: str) -> tuple[str, str]:
    if environment == "production":
        return "production_verified", "external_cash"
    if environment == "staging":
        return "staging_verified", "staging_connector"
    return "simulated", "simulated_connector"


def _operational_context(
    envelope: FinancialEventEnvelope,
    *,
    config: FinancialConnectorConfig,
    secret: bytes,
) -> FinancialConnectorOperationalContext:
    request_payload = canonical_financial_request(
        envelope,
        tenant_id=config.tenant_id,
        provider=config.provider,
        key_version=config.key_version,
        timestamp=None,
    )
    external_payload = json.dumps(
        {
            "version": "x-financial-operational-event-v1",
            "path": FINANCIAL_EVENT_SIGNATURE_PATH,
            "tenant_id": config.tenant_id,
            "config_id": config.id,
            "provider": config.provider,
            "external_event_id": envelope.external_event_id,
        },
        ensure_ascii=False,
        sort_keys=True,
        separators=(",", ":"),
    ).encode("utf-8")
    return FinancialConnectorOperationalContext(
        tenant_id=config.tenant_id,
        config_id=config.id,
        provider=config.provider,
        environment=config.environment,
        request_fingerprint=_hmac_fingerprint(
            secret,
            b"request:",
            request_payload,
        ),
        external_event_fingerprint=_hmac_fingerprint(
            secret,
            b"external-event:",
            external_payload,
        ),
    )


def _hmac_fingerprint(secret: bytes, domain: bytes, payload: bytes) -> str:
    digest = hmac.new(secret, domain + payload, hashlib.sha256).hexdigest()
    return f"hmac-sha256:{digest}"
