from __future__ import annotations

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

from sqlalchemy import func, select
from sqlalchemy.orm import Session

from app.api.deps import CurrentUserContext
from app.models.financial_connector import (
    PaymentReconciliationCase,
    PaymentReconciliationEvent,
)
from app.models.financial_record import ExpenseClaim
from app.schemas.financial_connector import (
    PaymentReconciliationActionCreate,
    PaymentReconciliationCaseDetailRead,
    PaymentReconciliationCaseRead,
    PaymentReconciliationEventRead,
    PaymentReconciliationListRead,
)


class FinancialReconciliationPermissionError(PermissionError):
    pass


class FinancialReconciliationConflictError(ValueError):
    pass


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

    @staticmethod
    def require_finance(current_user: CurrentUserContext) -> None:
        roles = {str(item).strip().lower() for item in current_user.role_codes}
        if not roles & {"finance", "executive"}:
            raise FinancialReconciliationPermissionError(
                "只有财务业务角色可以访问或处置对账记录。"
            )

    def list_cases(
        self,
        current_user: CurrentUserContext,
        *,
        status_filter: str | None,
        page: int,
        page_size: int,
    ) -> PaymentReconciliationListRead:
        self.require_finance(current_user)
        conditions = [PaymentReconciliationCase.tenant_id == current_user.tenant_id]
        if str(status_filter or "").strip():
            conditions.append(PaymentReconciliationCase.status == str(status_filter).strip())
        total = int(
            self.db.scalar(
                select(func.count(PaymentReconciliationCase.id)).where(*conditions)
            )
            or 0
        )
        items = list(
            self.db.scalars(
                select(PaymentReconciliationCase)
                .where(*conditions)
                .order_by(PaymentReconciliationCase.updated_at.desc())
                .offset((page - 1) * page_size)
                .limit(page_size)
            ).all()
        )
        return PaymentReconciliationListRead(
            items=[PaymentReconciliationCaseRead.model_validate(item) for item in items],
            total=total,
            page=page,
            page_size=page_size,
        )

    def detail(
        self,
        case_id: str,
        current_user: CurrentUserContext,
    ) -> PaymentReconciliationCaseDetailRead | None:
        self.require_finance(current_user)
        row = self.db.scalar(
            select(PaymentReconciliationCase).where(
                PaymentReconciliationCase.tenant_id == current_user.tenant_id,
                PaymentReconciliationCase.id == case_id,
            )
        )
        if row is None:
            return None
        events = list(
            self.db.scalars(
                select(PaymentReconciliationEvent)
                .where(
                    PaymentReconciliationEvent.tenant_id == current_user.tenant_id,
                    PaymentReconciliationEvent.reconciliation_case_id == row.id,
                )
                .order_by(PaymentReconciliationEvent.occurred_at)
            ).all()
        )
        base = PaymentReconciliationCaseRead.model_validate(row).model_dump()
        return PaymentReconciliationCaseDetailRead(
            **base,
            timeline=[PaymentReconciliationEventRead.model_validate(item) for item in events],
        )

    def resolve(
        self,
        case_id: str,
        payload: PaymentReconciliationActionCreate,
        current_user: CurrentUserContext,
        *,
        action: str,
    ) -> PaymentReconciliationCaseDetailRead | None:
        self.require_finance(current_user)
        statement = select(PaymentReconciliationCase).where(
            PaymentReconciliationCase.tenant_id == current_user.tenant_id,
            PaymentReconciliationCase.id == case_id,
        )
        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:
            return None
        if row.version != payload.expected_version:
            raise FinancialReconciliationConflictError(
                f"对账记录版本已变化，当前版本为 {row.version}。"
            )
        if row.status not in {"exception", "reopened"}:
            raise FinancialReconciliationConflictError("当前对账状态不允许人工处置。")
        claim = self.db.get(ExpenseClaim, row.claim_id)
        if claim is None:
            raise FinancialReconciliationConflictError("关联报销单不存在。")
        if self._is_claim_owner(claim, current_user):
            raise FinancialReconciliationPermissionError("申请人不能确认或拒绝自己的对账异常。")

        before = _state(row)
        row.status = "confirmed" if action == "confirmed" else "rejected"
        row.assigned_to = current_user.username
        row.version += 1
        row.updated_at = datetime.now(UTC)
        fingerprint = _fingerprint(
            case_id=row.id,
            action=action,
            expected_version=payload.expected_version,
            actor_id=current_user.username,
            reason=payload.reason,
        )
        self.db.add(
            PaymentReconciliationEvent(
                id=str(uuid.uuid4()),
                tenant_id=row.tenant_id,
                reconciliation_case_id=row.id,
                connector_event_id=row.last_connector_event_id,
                action=action,
                actor_type="user",
                actor_id=current_user.username,
                request_fingerprint=fingerprint,
                before_json=before,
                after_json=_state(row),
                response_json={"status": row.status, "version": row.version},
                reason=payload.reason,
                correlation_id=f"reconciliation:{row.id}"[:64],
                occurred_at=datetime.now(UTC),
            )
        )
        self.db.flush()
        return self.detail(row.id, current_user)

    @staticmethod
    def _is_claim_owner(claim: ExpenseClaim, current_user: CurrentUserContext) -> bool:
        if current_user.employee_id and str(claim.employee_id or "") == current_user.employee_id:
            return True
        return bool(
            current_user.name
            and str(claim.employee_name or "").strip().casefold()
            == str(current_user.name).strip().casefold()
        )


def _state(row: PaymentReconciliationCase) -> dict[str, object]:
    return {
        "status": row.status,
        "exception_code": row.exception_code,
        "erp_status": row.erp_status,
        "version": row.version,
    }


def _fingerprint(
    *,
    case_id: str,
    action: str,
    expected_version: int,
    actor_id: str,
    reason: str,
) -> str:
    raw = json.dumps(
        {
            "case_id": case_id,
            "action": action,
            "expected_version": expected_version,
            "actor_id": actor_id,
            "reason": reason,
        },
        ensure_ascii=False,
        sort_keys=True,
        separators=(",", ":"),
    ).encode("utf-8")
    return f"sha256:{hashlib.sha256(raw).hexdigest()}"
