from __future__ import annotations

import uuid
from dataclasses import dataclass

from sqlalchemy import select
from sqlalchemy.orm import Session

from app.models.financial_connector import (
    FinancialConnectorConfig,
    FinancialConnectorConfigEvent,
)
from app.schemas.financial_connector import (
    FinancialConnectorConfigLifecycleAction,
    FinancialConnectorConfigRotateAction,
)
from app.services.financial_connector_auth import FinancialConnectorSecretResolver
from app.services.financial_connector_config_audit import (
    append_connector_config_event,
    connector_config_state,
)
from app.services.financial_connector_configs import FinancialConnectorConfigError


class FinancialConnectorConfigConflictError(FinancialConnectorConfigError):
    pass


@dataclass(frozen=True, slots=True)
class FinancialConnectorRotationResult:
    previous: FinancialConnectorConfig
    replacement: FinancialConnectorConfig


class FinancialConnectorConfigLifecycleService:
    """以乐观版本和数据库行锁编排配置激活、停用与密钥轮换。"""

    def __init__(
        self,
        db: Session,
        *,
        secrets: FinancialConnectorSecretResolver | None = None,
    ) -> None:
        self.db = db
        self.secrets = secrets or FinancialConnectorSecretResolver.from_settings()

    def activate(
        self,
        *,
        tenant_id: str,
        config_id: str,
        payload: FinancialConnectorConfigLifecycleAction,
        actor_id: str,
    ) -> FinancialConnectorConfig:
        row = self._lock_config(tenant_id, config_id)
        self._require_version(row, payload.expected_version)
        if row.status != "disabled":
            raise FinancialConnectorConfigConflictError("只有停用配置可以激活。")
        self._ensure_provider_has_no_live_config(row)
        self.secrets.resolve(row.secret_ref)

        before = connector_config_state(row)
        row.status = "active"
        row.version += 1
        append_connector_config_event(
            self.db,
            row,
            action="activated",
            actor_id=actor_id,
            request_id=payload.request_id,
            reason=payload.reason,
            expected_version=payload.expected_version,
            before=before,
        )
        self.db.flush()
        return row

    def disable(
        self,
        *,
        tenant_id: str,
        config_id: str,
        payload: FinancialConnectorConfigLifecycleAction,
        actor_id: str,
    ) -> FinancialConnectorConfig:
        row = self._lock_config(tenant_id, config_id)
        self._require_version(row, payload.expected_version)
        if row.status not in {"active", "rotating"}:
            raise FinancialConnectorConfigConflictError("只有激活或轮换中的配置可以停用。")

        before = connector_config_state(row)
        row.status = "disabled"
        row.version += 1
        append_connector_config_event(
            self.db,
            row,
            action="disabled",
            actor_id=actor_id,
            request_id=payload.request_id,
            reason=payload.reason,
            expected_version=payload.expected_version,
            before=before,
        )
        self.db.flush()
        return row

    def rotate(
        self,
        *,
        tenant_id: str,
        config_id: str,
        payload: FinancialConnectorConfigRotateAction,
        actor_id: str,
    ) -> FinancialConnectorRotationResult:
        row = self._lock_config(tenant_id, config_id)
        self._require_version(row, payload.expected_version)
        if row.status != "active":
            raise FinancialConnectorConfigConflictError("只有激活配置可以发起密钥轮换。")
        if payload.new_key_version == row.key_version:
            raise FinancialConnectorConfigConflictError("新密钥版本必须不同于当前版本。")
        existing = self.db.scalar(
            select(FinancialConnectorConfig.id).where(
                FinancialConnectorConfig.tenant_id == row.tenant_id,
                FinancialConnectorConfig.provider == row.provider,
                FinancialConnectorConfig.key_version == payload.new_key_version,
            )
        )
        if existing is not None:
            raise FinancialConnectorConfigConflictError("新密钥版本的配置已存在。")
        self.secrets.resolve(payload.new_secret_ref)

        before = connector_config_state(row)
        replacement = FinancialConnectorConfig(
            id=str(uuid.uuid4()),
            tenant_id=row.tenant_id,
            provider=row.provider,
            environment=row.environment,
            key_version=payload.new_key_version,
            secret_ref=payload.new_secret_ref,
            allowed_event_types_json=list(row.allowed_event_types_json or []),
            clock_skew_seconds=row.clock_skew_seconds,
            status="active",
            version=1,
            created_by=str(actor_id).strip(),
        )
        row.status = "rotating"
        row.version += 1
        self.db.add(replacement)
        self.db.flush([row, replacement])

        previous_after = connector_config_state(row)
        previous_after["replacement_config_id"] = replacement.id
        append_connector_config_event(
            self.db,
            row,
            action="rotation_started",
            actor_id=actor_id,
            request_id=payload.request_id,
            reason=payload.reason,
            expected_version=payload.expected_version,
            before=before,
            after=previous_after,
        )
        replacement_after = connector_config_state(replacement)
        replacement_after["rotated_from_config_id"] = row.id
        append_connector_config_event(
            self.db,
            replacement,
            action="rotation_replacement_created",
            actor_id=actor_id,
            request_id=payload.request_id,
            reason=payload.reason,
            expected_version=None,
            before={},
            after=replacement_after,
        )
        self.db.flush()
        return FinancialConnectorRotationResult(previous=row, replacement=replacement)

    def list_events(
        self,
        *,
        tenant_id: str,
        config_id: str | None = None,
    ) -> list[FinancialConnectorConfigEvent]:
        conditions = [
            FinancialConnectorConfigEvent.tenant_id == str(tenant_id).strip()
        ]
        if str(config_id or "").strip():
            conditions.append(
                FinancialConnectorConfigEvent.config_id == str(config_id).strip()
            )
        return list(
            self.db.scalars(
                select(FinancialConnectorConfigEvent)
                .where(*conditions)
                .order_by(
                    FinancialConnectorConfigEvent.occurred_at,
                    FinancialConnectorConfigEvent.id,
                )
            ).all()
        )

    def _lock_config(self, tenant_id: str, config_id: str) -> FinancialConnectorConfig:
        statement = select(FinancialConnectorConfig).where(
            FinancialConnectorConfig.tenant_id == str(tenant_id).strip(),
            FinancialConnectorConfig.id == str(config_id).strip(),
        )
        bind = self.db.get_bind()
        if bind is not None and bind.dialect.name == "postgresql":
            statement = statement.with_for_update()
        row = self.db.scalar(statement.execution_options(populate_existing=True))
        if row is None:
            raise LookupError("连接器配置不存在。")
        return row

    @staticmethod
    def _require_version(row: FinancialConnectorConfig, expected_version: int) -> None:
        if row.version != expected_version:
            raise FinancialConnectorConfigConflictError(
                f"连接器配置版本已变化，当前版本为 {row.version}。"
            )

    def _ensure_provider_has_no_live_config(self, row: FinancialConnectorConfig) -> None:
        statement = select(FinancialConnectorConfig.id).where(
            FinancialConnectorConfig.tenant_id == row.tenant_id,
            FinancialConnectorConfig.provider == row.provider,
            FinancialConnectorConfig.id != row.id,
            FinancialConnectorConfig.status.in_(("active", "rotating")),
        )
        bind = self.db.get_bind()
        if bind is not None and bind.dialect.name == "postgresql":
            statement = statement.with_for_update()
        if self.db.scalar(statement) is not None:
            raise FinancialConnectorConfigConflictError(
                "同一来源已有激活或轮换中的配置，请使用轮换动作切换密钥。"
            )
