from __future__ import annotations

import hashlib
import json
import uuid
from dataclasses import dataclass
from datetime import UTC, datetime

from sqlalchemy import select
from sqlalchemy.dialects.postgresql import insert as postgresql_insert
from sqlalchemy.dialects.sqlite import insert as sqlite_insert
from sqlalchemy.orm import Session

from app.models.financial_connector import FinancialConnectorOperationalEvent

FINANCIAL_CONNECTOR_OPERATIONAL_SOURCE_REVISION = "20260716_0022"


@dataclass(frozen=True, slots=True)
class FinancialConnectorOperationalContext:
    """认证后可安全归属的最小上下文，只包含不可逆指纹。"""

    tenant_id: str
    config_id: str
    provider: str
    environment: str
    request_fingerprint: str
    external_event_fingerprint: str


@dataclass(frozen=True, slots=True)
class FinancialConnectorOperationalEventCandidate:
    context: FinancialConnectorOperationalContext
    event_type: str
    reason_code: str
    occurred_at: datetime
    idempotency_key: str


def operational_event_candidate(
    context: FinancialConnectorOperationalContext,
    *,
    event_type: str,
    reason_code: str,
    occurred_at: datetime | None = None,
) -> FinancialConnectorOperationalEventCandidate:
    event_kind = str(event_type or "").strip()
    reason = str(reason_code or "").strip()
    occurrence_time = _aware_utc(occurred_at or datetime.now(UTC))
    payload = json.dumps(
        {
            "version": FINANCIAL_CONNECTOR_OPERATIONAL_SOURCE_REVISION,
            "tenant_id": context.tenant_id,
            "config_id": context.config_id,
            "event_type": event_kind,
            "reason_code": reason,
            "request_fingerprint": context.request_fingerprint,
            "external_event_fingerprint": context.external_event_fingerprint,
            # 同一个 candidate 的补偿重试必须幂等；不同 HTTP 尝试即使载荷
            # 完全相同也必须分别计数，因此把本次发生时间纳入幂等命名空间。
            "occurred_at": occurrence_time.isoformat(timespec="microseconds"),
        },
        ensure_ascii=False,
        sort_keys=True,
        separators=(",", ":"),
    ).encode("utf-8")
    return FinancialConnectorOperationalEventCandidate(
        context=context,
        event_type=event_kind,
        reason_code=reason,
        occurred_at=occurrence_time,
        idempotency_key=f"sha256:{hashlib.sha256(payload).hexdigest()}",
    )


class FinancialConnectorOperationalEventService:
    """以数据库幂等唯一约束写入最小化运营事实。"""

    def __init__(self, db: Session) -> None:
        self.db = db

    def record(self, candidate: FinancialConnectorOperationalEventCandidate) -> None:
        values = {
            "id": str(uuid.uuid4()),
            "tenant_id": candidate.context.tenant_id,
            "config_id": candidate.context.config_id,
            "provider": candidate.context.provider,
            "environment": candidate.context.environment,
            "event_type": candidate.event_type,
            "reason_code": candidate.reason_code,
            "request_fingerprint": candidate.context.request_fingerprint,
            "external_event_fingerprint": candidate.context.external_event_fingerprint,
            "idempotency_key": candidate.idempotency_key,
            "occurred_at": candidate.occurred_at,
        }
        bind = self.db.get_bind()
        dialect_name = bind.dialect.name if bind is not None else ""
        if dialect_name == "postgresql":
            statement = postgresql_insert(FinancialConnectorOperationalEvent).values(**values)
            self.db.execute(
                statement.on_conflict_do_nothing(
                    index_elements=["tenant_id", "idempotency_key"]
                )
            )
            return
        if dialect_name == "sqlite":
            statement = sqlite_insert(FinancialConnectorOperationalEvent).values(**values)
            self.db.execute(
                statement.on_conflict_do_nothing(
                    index_elements=["tenant_id", "idempotency_key"]
                )
            )
            return
        existing_id = self.db.scalar(
            select(FinancialConnectorOperationalEvent.id).where(
                FinancialConnectorOperationalEvent.tenant_id
                == candidate.context.tenant_id,
                FinancialConnectorOperationalEvent.idempotency_key
                == candidate.idempotency_key,
            )
        )
        if existing_id is None:
            self.db.add(FinancialConnectorOperationalEvent(**values))


def _aware_utc(value: datetime) -> datetime:
    if value.tzinfo is None:
        return value.replace(tzinfo=UTC)
    return value.astimezone(UTC)
