"""Settlement application services — Phase 10.8.

Uses AccountingProvider protocol refs only — NO journal entries in delivery_db.
"""
from __future__ import annotations

from typing import Any
from uuid import UUID

from sqlalchemy.ext.asyncio import AsyncSession

from app.events.publisher import TransactionalEventPublisher
from app.events.types import DeliveryEventType
from app.models.settlement import SettlementIntent, SettlementLine
from app.models.types import AuditAction, SettlementIntentStatus
from app.policies.settlement import SettlementIntentPolicy
from app.providers.contracts import AccountingProvider
from app.repositories.foundation import DeliveryOrganizationRepository
from app.repositories.settlement import SettlementIntentRepository, SettlementLineRepository
from app.schemas.settlement import (
    SettlementActionRequest,
    SettlementIntentCreate,
    SettlementIntentUpdate,
    SettlementLineCreate,
)
from app.services._helpers import actor_id, jsonable
from app.services.audit_service import AuditService
from app.specifications.settlement import SettlementIntentListSpec
from app.validators import ensure_optimistic_version, validate_code
from app.validators.pricing import validate_currency, validate_line_amount
from app.validators.settlement import validate_settlement_amount
from shared.exceptions import AppError, NotFoundError
from shared.security import CurrentUser


class StubAccountingProvider:
    """Test/dev stub — creates external settlement refs only."""

    async def create_settlement_ref(
        self, *, tenant_id: UUID, reference: str, payload: dict[str, Any]
    ) -> dict[str, Any]:
        return {
            "settlement_ref": f"acct://settlement/{tenant_id}/{reference}",
            "status": "accepted",
            "payload": payload,
        }


class SettlementService:
    def __init__(
        self,
        session: AsyncSession,
        *,
        publisher: TransactionalEventPublisher | None = None,
        accounting: AccountingProvider | None = None,
    ) -> None:
        self.session = session
        self.repo = SettlementIntentRepository(session)
        self.lines = SettlementLineRepository(session)
        self.orgs = DeliveryOrganizationRepository(session)
        self.audit = AuditService(session)
        self.publisher = publisher or TransactionalEventPublisher(session)
        self.accounting = accounting or StubAccountingProvider()
        self.policy = SettlementIntentPolicy()

    async def create(
        self,
        tenant_id: UUID,
        body: SettlementIntentCreate,
        *,
        actor: CurrentUser | None = None,
    ) -> SettlementIntent:
        if await self.orgs.get(tenant_id, body.organization_id) is None:
            raise NotFoundError("سازمان یافت نشد")
        code = validate_code(body.code)
        if await self.repo.get_by_code(tenant_id, code):
            raise AppError("کد تسویه تکراری است", error_code="duplicate_code", status_code=409)
        entity = SettlementIntent(
            tenant_id=tenant_id,
            organization_id=body.organization_id,
            code=code,
            driver_id=body.driver_id,
            dispatch_job_id=body.dispatch_job_id,
            total_amount=validate_settlement_amount(body.total_amount),
            currency_code=validate_currency(body.currency_code),
            notes=body.notes,
            metadata_json=body.metadata_json,
            created_by=actor_id(actor),
            updated_by=actor_id(actor),
        )
        await self.repo.add(entity)
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="settlement_intent",
            entity_id=entity.id,
            action=AuditAction.CREATE,
            actor_user_id=actor_id(actor),
            changes=jsonable(body.model_dump()),
        )
        await self.publisher.publish(
            event_type=DeliveryEventType.SETTLEMENT_INTENT_CREATED,
            aggregate_type="settlement_intent",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"code": entity.code},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def get(self, tenant_id: UUID, intent_id: UUID) -> SettlementIntent:
        entity = await self.repo.get(tenant_id, intent_id)
        if entity is None:
            raise NotFoundError("قصد تسویه یافت نشد")
        return entity

    async def list(
        self,
        tenant_id: UUID,
        *,
        offset: int = 0,
        limit: int = 20,
        spec: SettlementIntentListSpec | None = None,
    ) -> tuple[list[SettlementIntent], int]:
        items = list(await self.repo.list_filtered(tenant_id, offset=offset, limit=limit, spec=spec))
        return items, await self.repo.count_filtered(tenant_id, spec=spec)

    async def update(
        self,
        tenant_id: UUID,
        intent_id: UUID,
        body: SettlementIntentUpdate,
        *,
        actor: CurrentUser | None = None,
    ) -> SettlementIntent:
        entity = await self.get(tenant_id, intent_id)
        if entity.status != SettlementIntentStatus.DRAFT:
            raise AppError(
                "فقط تسویه پیش‌نویس قابل ویرایش است",
                status_code=409,
                error_code="settlement_not_editable",
            )
        ensure_optimistic_version(entity, body.version)
        data = body.model_dump(exclude_unset=True, exclude={"version"})
        if "total_amount" in data and data["total_amount"] is not None:
            data["total_amount"] = validate_settlement_amount(data["total_amount"])
        if "currency_code" in data and data["currency_code"] is not None:
            data["currency_code"] = validate_currency(data["currency_code"])
        for key, value in data.items():
            setattr(entity, key, value)
        entity.version += 1
        entity.updated_by = actor_id(actor)
        await self.publisher.publish(
            event_type=DeliveryEventType.SETTLEMENT_INTENT_UPDATED,
            aggregate_type="settlement_intent",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"code": entity.code},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def _apply_action(
        self,
        tenant_id: UUID,
        intent_id: UUID,
        action: str,
        body: SettlementActionRequest,
        *,
        actor: CurrentUser | None = None,
    ) -> SettlementIntent:
        entity = await self.get(tenant_id, intent_id)
        ensure_optimistic_version(entity, body.version)
        from_status = entity.status
        to_status, reason = self.policy.assert_transition(
            action=action, current=from_status, reason=body.reason
        )
        entity.status = to_status
        entity.version += 1
        entity.updated_by = actor_id(actor)
        if action == "submit":
            await self.publisher.publish(
                event_type=DeliveryEventType.SETTLEMENT_INTENT_SUBMITTED,
                aggregate_type="settlement_intent",
                aggregate_id=entity.id,
                tenant_id=tenant_id,
                payload={"code": entity.code},
            )
        elif action == "settle":
            result = await self.accounting.create_settlement_ref(
                tenant_id=tenant_id,
                reference=entity.code,
                payload={
                    "amount": str(entity.total_amount),
                    "currency": entity.currency_code,
                    "driver_id": str(entity.driver_id) if entity.driver_id else None,
                    "dispatch_job_id": str(entity.dispatch_job_id)
                    if entity.dispatch_job_id
                    else None,
                },
            )
            entity.external_settlement_ref = result.get("settlement_ref")
            await self.publisher.publish(
                event_type=DeliveryEventType.SETTLEMENT_INTENT_SETTLED,
                aggregate_type="settlement_intent",
                aggregate_id=entity.id,
                tenant_id=tenant_id,
                payload={
                    "code": entity.code,
                    "external_settlement_ref": entity.external_settlement_ref,
                },
            )
        elif action == "fail":
            await self.publisher.publish(
                event_type=DeliveryEventType.SETTLEMENT_INTENT_FAILED,
                aggregate_type="settlement_intent",
                aggregate_id=entity.id,
                tenant_id=tenant_id,
                payload={"code": entity.code, "reason": reason},
            )
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="settlement_intent",
            entity_id=entity.id,
            action=AuditAction.STATUS_CHANGE,
            actor_user_id=actor_id(actor),
            changes={
                "action": action,
                "from_status": from_status.value,
                "to_status": to_status.value,
            },
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def submit(
        self,
        tenant_id: UUID,
        intent_id: UUID,
        body: SettlementActionRequest,
        *,
        actor: CurrentUser | None = None,
    ) -> SettlementIntent:
        return await self._apply_action(
            tenant_id, intent_id, "submit", body, actor=actor
        )

    async def settle(
        self,
        tenant_id: UUID,
        intent_id: UUID,
        body: SettlementActionRequest,
        *,
        actor: CurrentUser | None = None,
    ) -> SettlementIntent:
        return await self._apply_action(
            tenant_id, intent_id, "settle", body, actor=actor
        )

    async def fail(
        self,
        tenant_id: UUID,
        intent_id: UUID,
        body: SettlementActionRequest,
        *,
        actor: CurrentUser | None = None,
    ) -> SettlementIntent:
        return await self._apply_action(
            tenant_id, intent_id, "fail", body, actor=actor
        )

    async def cancel(
        self,
        tenant_id: UUID,
        intent_id: UUID,
        body: SettlementActionRequest,
        *,
        actor: CurrentUser | None = None,
    ) -> SettlementIntent:
        return await self._apply_action(
            tenant_id, intent_id, "cancel", body, actor=actor
        )

    async def add_line(
        self,
        tenant_id: UUID,
        intent_id: UUID,
        body: SettlementLineCreate,
        *,
        actor: CurrentUser | None = None,
    ) -> SettlementLine:
        entity = await self.get(tenant_id, intent_id)
        if entity.status != SettlementIntentStatus.DRAFT:
            raise AppError(
                "فقط به تسویه پیش‌نویس می‌توان سطر اضافه کرد",
                status_code=409,
                error_code="settlement_not_editable",
            )
        line = SettlementLine(
            tenant_id=tenant_id,
            settlement_intent_id=intent_id,
            kind=body.kind,
            description=body.description,
            amount=validate_line_amount(body.amount),
            currency_code=validate_currency(body.currency_code),
            metadata_json=body.metadata_json,
            created_by=actor_id(actor),
            updated_by=actor_id(actor),
        )
        await self.lines.add(line)
        await self.publisher.publish(
            event_type=DeliveryEventType.SETTLEMENT_LINE_ADDED,
            aggregate_type="settlement_line",
            aggregate_id=line.id,
            tenant_id=tenant_id,
            payload={"settlement_intent_id": str(intent_id), "kind": body.kind.value},
        )
        await self.session.commit()
        await self.session.refresh(line)
        return line

    async def list_lines(
        self, tenant_id: UUID, intent_id: UUID
    ) -> list[SettlementLine]:
        await self.get(tenant_id, intent_id)
        return list(await self.lines.list_for_intent(tenant_id, intent_id))
