from __future__ import annotations

import logging
from typing import Annotated

from fastapi import APIRouter, Depends, Header, HTTPException, Query, status
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session

from app.api.deps import (
    CurrentUserContext,
    get_current_user,
    get_db,
    require_platform_admin_user,
)
from app.schemas.financial_connector import (
    FinancialConnectorConfigCreate,
    FinancialConnectorConfigEventRead,
    FinancialConnectorConfigLifecycleAction,
    FinancialConnectorConfigRead,
    FinancialConnectorConfigRotateAction,
    FinancialConnectorConfigRotationRead,
    FinancialConnectorObservabilityRead,
    FinancialConnectorSimulationCreate,
    FinancialConnectorSimulationRead,
    FinancialEventEnvelope,
    FinancialEventIngestionRead,
    FinancialPaymentEvidenceRead,
    PaymentReconciliationActionCreate,
    PaymentReconciliationCaseDetailRead,
    PaymentReconciliationListRead,
)
from app.services.expense_claims import ExpenseClaimService
from app.services.financial_connector_auth import FinancialConnectorAuthError
from app.services.financial_connector_commercial import (
    FinancialConnectorCommercialAccessDenied,
)
from app.services.financial_connector_config_lifecycle import (
    FinancialConnectorConfigConflictError,
    FinancialConnectorConfigLifecycleService,
)
from app.services.financial_connector_configs import (
    FinancialConnectorConfigError,
    FinancialConnectorConfigService,
)
from app.services.financial_connector_ingestion import (
    FinancialConnectorConflictError,
    FinancialConnectorIngestionService,
)
from app.services.financial_connector_mock_adapter import (
    FinancialConnectorMockAdapter,
    FinancialConnectorMockAdapterError,
)
from app.services.financial_connector_observability import (
    FinancialConnectorObservabilityPermissionError,
    FinancialConnectorObservabilityService,
)
from app.services.financial_connector_operational_events import (
    FinancialConnectorOperationalEventCandidate,
    FinancialConnectorOperationalEventService,
)
from app.services.financial_connector_payment_evidence import (
    FinancialConnectorPaymentEvidenceService,
)
from app.services.financial_connector_projection import (
    FinancialConnectorProjectionService,
    FinancialReconciliationConflictError,
    FinancialReconciliationPermissionError,
)

router = APIRouter()
logger = logging.getLogger(__name__)
DbSession = Annotated[Session, Depends(get_db)]
CurrentUser = Annotated[CurrentUserContext, Depends(get_current_user)]
PlatformAdmin = Annotated[CurrentUserContext, Depends(require_platform_admin_user)]


@router.post(
    "/integrations/financial-events",
    response_model=FinancialEventIngestionRead,
    summary="接收租户绑定且签名可验证的财务事件",
)
def ingest_financial_event(
    payload: FinancialEventEnvelope,
    db: DbSession,
    tenant_id: Annotated[str, Header(alias="X-Financial-Tenant")],
    provider: Annotated[str, Header(alias="X-Financial-Provider")],
    key_version: Annotated[str, Header(alias="X-Financial-Key-Version")],
    timestamp_value: Annotated[str, Header(alias="X-Financial-Timestamp")],
    signature: Annotated[str, Header(alias="X-Financial-Signature")],
) -> FinancialEventIngestionRead:
    try:
        result = FinancialConnectorIngestionService(db).ingest(
            payload,
            tenant_header=tenant_id,
            provider_header=provider,
            key_version_header=key_version,
            timestamp_header=timestamp_value,
            signature_header=signature,
        )
        db.commit()
        return result
    except FinancialConnectorAuthError as error:
        db.rollback()
        if error.operational_event is not None:
            _persist_operational_event(db, error.operational_event)
        else:
            logger.warning(
                "financial_connector_auth_failure_unattributed code=%s "
                "path=/api/v1/integrations/financial-events",
                error.code,
            )
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail={"code": error.code, "message": str(error)},
        ) from error
    except FinancialConnectorConflictError as error:
        db.rollback()
        _persist_operational_event(db, error.operational_event)
        raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(error)) from error
    except IntegrityError as error:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="事件正由另一请求处理，请使用相同 external_event_id 安全重试。",
        ) from error
    except FinancialConnectorCommercialAccessDenied as error:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_429_TOO_MANY_REQUESTS,
            detail=str(error),
        ) from error
    except (ValueError, PermissionError) as error:
        db.rollback()
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(error)) from error
    except Exception:
        db.rollback()
        raise


@router.post(
    "/financial-connectors/admin/tenants/{tenant_id}/configs",
    response_model=FinancialConnectorConfigRead,
    status_code=status.HTTP_201_CREATED,
    summary="平台管理员创建不含密钥明文的连接器配置",
)
def create_financial_connector_config(
    tenant_id: str,
    payload: FinancialConnectorConfigCreate,
    db: DbSession,
    current_user: PlatformAdmin,
) -> FinancialConnectorConfigRead:
    try:
        row = FinancialConnectorConfigService(db).create(
            tenant_id=tenant_id,
            payload=payload,
            actor_id=current_user.username,
        )
        db.commit()
        db.refresh(row)
        return FinancialConnectorConfigRead.model_validate(row)
    except (FinancialConnectorConfigError, IntegrityError) as error:
        db.rollback()
        raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(error)) from error


@router.get(
    "/financial-connectors/admin/tenants/{tenant_id}/configs",
    response_model=list[FinancialConnectorConfigRead],
    summary="平台管理员读取脱敏连接器配置",
)
def list_financial_connector_configs(
    tenant_id: str,
    db: DbSession,
    current_user: PlatformAdmin,
) -> list[FinancialConnectorConfigRead]:
    del current_user
    return [
        FinancialConnectorConfigRead.model_validate(item)
        for item in FinancialConnectorConfigService(db).list_for_tenant(tenant_id)
    ]


@router.post(
    "/financial-connectors/admin/tenants/{tenant_id}/configs/{config_id}/activate",
    response_model=FinancialConnectorConfigRead,
    summary="校验服务端密钥后激活指定版本连接器",
)
def activate_financial_connector_config(
    tenant_id: str,
    config_id: str,
    payload: FinancialConnectorConfigLifecycleAction,
    db: DbSession,
    current_user: PlatformAdmin,
) -> FinancialConnectorConfigRead:
    return _change_config_status(
        tenant_id,
        config_id,
        payload,
        db,
        current_user,
        action="activate",
    )


@router.post(
    "/financial-connectors/admin/tenants/{tenant_id}/configs/{config_id}/disable",
    response_model=FinancialConnectorConfigRead,
    summary="按乐观版本停用连接器",
)
def disable_financial_connector_config(
    tenant_id: str,
    config_id: str,
    payload: FinancialConnectorConfigLifecycleAction,
    db: DbSession,
    current_user: PlatformAdmin,
) -> FinancialConnectorConfigRead:
    return _change_config_status(
        tenant_id,
        config_id,
        payload,
        db,
        current_user,
        action="disable",
    )


@router.post(
    "/financial-connectors/admin/tenants/{tenant_id}/configs/{config_id}/rotate",
    response_model=FinancialConnectorConfigRotationRead,
    summary="原子轮换连接器密钥版本",
)
def rotate_financial_connector_config(
    tenant_id: str,
    config_id: str,
    payload: FinancialConnectorConfigRotateAction,
    db: DbSession,
    current_user: PlatformAdmin,
) -> FinancialConnectorConfigRotationRead:
    try:
        result = FinancialConnectorConfigLifecycleService(db).rotate(
            tenant_id=tenant_id,
            config_id=config_id,
            payload=payload,
            actor_id=current_user.username,
        )
        db.commit()
        db.refresh(result.previous)
        db.refresh(result.replacement)
        return FinancialConnectorConfigRotationRead(
            previous=FinancialConnectorConfigRead.model_validate(result.previous),
            replacement=FinancialConnectorConfigRead.model_validate(result.replacement),
        )
    except LookupError as error:
        db.rollback()
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(error)) from error
    except FinancialConnectorAuthError as error:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail={"code": error.code, "message": str(error)},
        ) from error
    except (FinancialConnectorConfigConflictError, IntegrityError) as error:
        db.rollback()
        raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(error)) from error


@router.get(
    "/financial-connectors/admin/tenants/{tenant_id}/config-events",
    response_model=list[FinancialConnectorConfigEventRead],
    summary="读取不含密钥材料的连接器配置审计时间线",
)
def list_financial_connector_config_events(
    tenant_id: str,
    db: DbSession,
    current_user: PlatformAdmin,
    config_id: str | None = Query(default=None),
) -> list[FinancialConnectorConfigEventRead]:
    del current_user
    return [
        FinancialConnectorConfigEventRead.model_validate(item)
        for item in FinancialConnectorConfigLifecycleService(db).list_events(
            tenant_id=tenant_id,
            config_id=config_id,
        )
    ]


@router.post(
    "/financial-connectors/admin/tenants/{tenant_id}/configs/{config_id}/simulate",
    response_model=FinancialConnectorSimulationRead,
    summary="平台管理员运行确定性的非生产连接器场景",
)
def simulate_financial_connector_event(
    tenant_id: str,
    config_id: str,
    payload: FinancialConnectorSimulationCreate,
    db: DbSession,
    current_user: PlatformAdmin,
) -> FinancialConnectorSimulationRead:
    del current_user
    try:
        result = FinancialConnectorMockAdapter(db).run(
            tenant_id=tenant_id,
            config_id=config_id,
            payload=payload,
        )
        db.commit()
        return result
    except LookupError as error:
        db.rollback()
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(error)) from error
    except FinancialConnectorAuthError as error:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail={"code": error.code, "message": str(error)},
        ) from error
    except FinancialConnectorMockAdapterError as error:
        db.rollback()
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(error)) from error
    except FinancialConnectorCommercialAccessDenied as error:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_429_TOO_MANY_REQUESTS,
            detail=str(error),
        ) from error
    except IntegrityError as error:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="模拟场景正由另一请求处理，请使用相同 request_id 安全重试。",
        ) from error
    except Exception:
        db.rollback()
        raise


@router.get(
    "/financial-connectors/observability",
    response_model=FinancialConnectorObservabilityRead,
    summary="读取当前租户脱敏连接器运行指标",
)
def get_current_tenant_connector_observability(
    db: DbSession,
    current_user: CurrentUser,
    window_hours: int = Query(default=24, ge=1, le=720),
) -> FinancialConnectorObservabilityRead:
    try:
        return FinancialConnectorObservabilityService(db).read_for_current_user(
            current_user,
            window_hours=window_hours,
        )
    except FinancialConnectorObservabilityPermissionError as error:
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=str(error)) from error


@router.get(
    "/financial-connectors/admin/tenants/{tenant_id}/observability",
    response_model=FinancialConnectorObservabilityRead,
    summary="平台管理员读取目标租户脱敏连接器运行指标",
)
def get_tenant_connector_observability(
    tenant_id: str,
    db: DbSession,
    current_user: PlatformAdmin,
    window_hours: int = Query(default=24, ge=1, le=720),
) -> FinancialConnectorObservabilityRead:
    del current_user
    return FinancialConnectorObservabilityService(db).read_for_tenant(
        tenant_id,
        window_hours=window_hours,
    )


@router.get(
    "/financial-connectors/payment-evidence/{claim_id}",
    response_model=FinancialPaymentEvidenceRead,
    summary="读取当前租户单据的付款证据等级",
)
def get_financial_payment_evidence(
    claim_id: str,
    db: DbSession,
    current_user: CurrentUser,
) -> FinancialPaymentEvidenceRead:
    claim = ExpenseClaimService(db).get_claim(claim_id, current_user)
    if claim is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="报销单不存在。")
    return FinancialConnectorPaymentEvidenceService.read(claim)


@router.get(
    "/financial-reconciliation/cases",
    response_model=PaymentReconciliationListRead,
    summary="分页读取当前租户对账记录",
)
def list_payment_reconciliation_cases(
    db: DbSession,
    current_user: CurrentUser,
    reconciliation_status: str | None = Query(default=None, alias="status"),
    page: int = Query(default=1, ge=1),
    page_size: int = Query(default=20, ge=1, le=100),
) -> PaymentReconciliationListRead:
    try:
        return FinancialConnectorProjectionService(db).list_cases(
            current_user,
            status_filter=reconciliation_status,
            page=page,
            page_size=page_size,
        )
    except FinancialReconciliationPermissionError as error:
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=str(error)) from error


@router.get(
    "/financial-reconciliation/cases/{case_id}",
    response_model=PaymentReconciliationCaseDetailRead,
    summary="读取当前租户脱敏对账详情和追加式时间线",
)
def get_payment_reconciliation_case(
    case_id: str,
    db: DbSession,
    current_user: CurrentUser,
) -> PaymentReconciliationCaseDetailRead:
    try:
        result = FinancialConnectorProjectionService(db).detail(case_id, current_user)
    except FinancialReconciliationPermissionError as error:
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=str(error)) from error
    if result is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="对账记录不存在。")
    return result


@router.post(
    "/financial-reconciliation/cases/{case_id}/confirm",
    response_model=PaymentReconciliationCaseDetailRead,
    summary="财务人员确认异常处置，不伪造付款成功",
)
def confirm_payment_reconciliation_case(
    case_id: str,
    payload: PaymentReconciliationActionCreate,
    db: DbSession,
    current_user: CurrentUser,
) -> PaymentReconciliationCaseDetailRead:
    return _resolve_case(case_id, payload, db, current_user, action="confirmed")


@router.post(
    "/financial-reconciliation/cases/{case_id}/reject",
    response_model=PaymentReconciliationCaseDetailRead,
    summary="财务人员拒绝错误回执",
)
def reject_payment_reconciliation_case(
    case_id: str,
    payload: PaymentReconciliationActionCreate,
    db: DbSession,
    current_user: CurrentUser,
) -> PaymentReconciliationCaseDetailRead:
    return _resolve_case(case_id, payload, db, current_user, action="rejected")


def _resolve_case(
    case_id: str,
    payload: PaymentReconciliationActionCreate,
    db: Session,
    current_user: CurrentUserContext,
    *,
    action: str,
) -> PaymentReconciliationCaseDetailRead:
    try:
        result = FinancialConnectorProjectionService(db).resolve(
            case_id,
            payload,
            current_user,
            action=action,
        )
        if result is None:
            db.rollback()
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="对账记录不存在。")
        db.commit()
        return result
    except HTTPException:
        raise
    except FinancialReconciliationPermissionError as error:
        db.rollback()
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=str(error)) from error
    except FinancialReconciliationConflictError as error:
        db.rollback()
        raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(error)) from error


def _change_config_status(
    tenant_id: str,
    config_id: str,
    payload: FinancialConnectorConfigLifecycleAction,
    db: Session,
    current_user: CurrentUserContext,
    *,
    action: str,
) -> FinancialConnectorConfigRead:
    service = FinancialConnectorConfigLifecycleService(db)
    try:
        operation = service.activate if action == "activate" else service.disable
        row = operation(
            tenant_id=tenant_id,
            config_id=config_id,
            payload=payload,
            actor_id=current_user.username,
        )
        db.commit()
        db.refresh(row)
        return FinancialConnectorConfigRead.model_validate(row)
    except LookupError as error:
        db.rollback()
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(error)) from error
    except FinancialConnectorAuthError as error:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail={"code": error.code, "message": str(error)},
        ) from error
    except (FinancialConnectorConfigConflictError, IntegrityError) as error:
        db.rollback()
        raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(error)) from error


def _persist_operational_event(
    db: Session,
    candidate: FinancialConnectorOperationalEventCandidate,
) -> None:
    """失败响应回滚后独立提交运营事实，写入失败不得改变原始业务结果。"""

    try:
        FinancialConnectorOperationalEventService(db).record(candidate)
        db.commit()
    except Exception:  # noqa: BLE001 - 审计降级不能覆盖原始 401/409 契约
        db.rollback()
        logger.exception(
            "financial_connector_operational_event_persist_failed "
            "event_type=%s reason_code=%s config_id=%s",
            candidate.event_type,
            candidate.reason_code,
            candidate.context.config_id,
        )
