"""Kitchen application services — Phase 12.6.

Kitchen display / ticket routing shell. Tickets reference their POS
ticket / QR ordering session source by UUID only — no cross-aggregate
relationships or direct writes into POS/QR tables.
"""
from __future__ import annotations

from uuid import UUID

from sqlalchemy.ext.asyncio import AsyncSession

from app.events.publisher import InMemoryEventPublisher, get_event_publisher
from app.events.types import HospitalityEventType
from app.models.kitchen import KitchenStation, KitchenTicket, KitchenTicketItem
from app.models.types import AuditAction, KitchenItemStatus, KitchenTicketStatus
from app.repositories.foundation import VenueRepository
from app.repositories.kitchen import (
    KitchenStationRepository,
    KitchenTicketItemRepository,
    KitchenTicketRepository,
)
from app.schemas.kitchen import (
    KitchenStationCreate,
    KitchenTicketCreate,
    KitchenTicketItemCreate,
)
from app.services.audit_service import AuditService
from app.validators import (
    validate_code,
    validate_kitchen_item_status,
    validate_kitchen_ticket_status,
    validate_lifecycle_status,
    validate_non_empty,
    validate_positive_int,
)
from shared.exceptions import AppError, NotFoundError
from shared.security import CurrentUser

KITCHEN_TICKET_TERMINAL_STATUSES = frozenset(
    {KitchenTicketStatus.BUMPED, KitchenTicketStatus.CANCELLED}
)
KITCHEN_ITEM_TERMINAL_STATUSES = frozenset(
    {KitchenItemStatus.BUMPED, KitchenItemStatus.CANCELLED}
)


class _KitchenBase:
    def __init__(
        self,
        session: AsyncSession,
        publisher: InMemoryEventPublisher | None = None,
    ) -> None:
        self.session = session
        self.audit = AuditService(session)
        self.publisher = publisher or get_event_publisher()
        self.venues = VenueRepository(session)

    async def _require_venue(self, tenant_id: UUID, venue_id: UUID):
        venue = await self.venues.get(tenant_id, venue_id)
        if venue is None:
            raise NotFoundError("واحد پذیرایی یافت نشد", error_code="venue_not_found")
        return venue


class KitchenStationService(_KitchenBase):
    def __init__(self, session: AsyncSession, publisher: InMemoryEventPublisher | None = None) -> None:
        super().__init__(session, publisher)
        self.repo = KitchenStationRepository(session)

    async def create(
        self, tenant_id: UUID, body: KitchenStationCreate, *, actor: CurrentUser | None = None
    ) -> KitchenStation:
        await self._require_venue(tenant_id, body.venue_id)
        code = validate_code(body.code)
        if await self.repo.get_by_code(tenant_id, body.venue_id, code) is not None:
            raise AppError(
                "کد ایستگاه آشپزخانه تکراری است",
                status_code=409,
                error_code="kitchen_station_code_exists",
            )
        actor_id = actor.user_id if actor else None
        entity = KitchenStation(
            tenant_id=tenant_id,
            venue_id=body.venue_id,
            code=code,
            name=validate_non_empty(body.name, field="name"),
            status=validate_lifecycle_status(body.status),
            created_by=actor_id,
            updated_by=actor_id,
        )
        await self.repo.add(entity)
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="kitchen_station",
            entity_id=entity.id,
            action=AuditAction.CREATE,
            actor_user_id=actor_id,
        )
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            event_type=HospitalityEventType.KITCHEN_STATION_CREATED,
            aggregate_type="kitchen_station",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"code": entity.code},
        )
        return entity

    async def get(self, tenant_id: UUID, entity_id: UUID) -> KitchenStation:
        entity = await self.repo.get(tenant_id, entity_id)
        if entity is None:
            raise NotFoundError("ایستگاه آشپزخانه یافت نشد", error_code="kitchen_station_not_found")
        return entity

    async def list(self, tenant_id: UUID, *, offset: int, limit: int):
        return await self.repo.list_by_tenant(tenant_id, offset=offset, limit=limit)


class KitchenTicketService(_KitchenBase):
    def __init__(self, session: AsyncSession, publisher: InMemoryEventPublisher | None = None) -> None:
        super().__init__(session, publisher)
        self.repo = KitchenTicketRepository(session)
        self.stations = KitchenStationRepository(session)

    async def create(
        self, tenant_id: UUID, body: KitchenTicketCreate, *, actor: CurrentUser | None = None
    ) -> KitchenTicket:
        await self._require_venue(tenant_id, body.venue_id)
        if body.station_id is not None and await self.stations.get(tenant_id, body.station_id) is None:
            raise NotFoundError("ایستگاه آشپزخانه یافت نشد", error_code="kitchen_station_not_found")
        code = validate_code(body.code)
        if await self.repo.get_by_code(tenant_id, body.venue_id, code) is not None:
            raise AppError(
                "کد تیکت آشپزخانه تکراری است",
                status_code=409,
                error_code="kitchen_ticket_code_exists",
            )
        actor_id = actor.user_id if actor else None
        entity = KitchenTicket(
            tenant_id=tenant_id,
            venue_id=body.venue_id,
            station_id=body.station_id,
            source_type=body.source_type,
            source_id=body.source_id,
            code=code,
            status=KitchenTicketStatus.QUEUED,
            notes=body.notes,
            created_by=actor_id,
            updated_by=actor_id,
        )
        await self.repo.add(entity)
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="kitchen_ticket",
            entity_id=entity.id,
            action=AuditAction.CREATE,
            actor_user_id=actor_id,
        )
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            event_type=HospitalityEventType.KITCHEN_TICKET_CREATED,
            aggregate_type="kitchen_ticket",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"code": entity.code, "source_type": entity.source_type.value},
        )
        return entity

    async def get(self, tenant_id: UUID, entity_id: UUID) -> KitchenTicket:
        entity = await self.repo.get(tenant_id, entity_id)
        if entity is None:
            raise NotFoundError("تیکت آشپزخانه یافت نشد", error_code="kitchen_ticket_not_found")
        return entity

    async def list(self, tenant_id: UUID, *, offset: int, limit: int):
        return await self.repo.list_by_tenant(tenant_id, offset=offset, limit=limit)

    async def change_status(
        self,
        tenant_id: UUID,
        entity_id: UUID,
        new_status: KitchenTicketStatus | str,
        *,
        actor: CurrentUser | None = None,
    ) -> KitchenTicket:
        entity = await self.get(tenant_id, entity_id)
        resolved = validate_kitchen_ticket_status(new_status)
        if entity.status in KITCHEN_TICKET_TERMINAL_STATUSES:
            raise AppError(
                "وضعیت تیکت آشپزخانه نهایی شده و قابل تغییر نیست",
                status_code=409,
                error_code="kitchen_ticket_status_final",
            )
        actor_id = actor.user_id if actor else None
        entity.status = resolved
        entity.updated_by = actor_id
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="kitchen_ticket",
            entity_id=entity.id,
            action=AuditAction.STATUS_CHANGE,
            actor_user_id=actor_id,
            changes={"status": resolved},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            event_type=HospitalityEventType.KITCHEN_TICKET_STATUS_CHANGED,
            aggregate_type="kitchen_ticket",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"status": entity.status.value},
        )
        return entity


class KitchenTicketItemService(_KitchenBase):
    def __init__(self, session: AsyncSession, publisher: InMemoryEventPublisher | None = None) -> None:
        super().__init__(session, publisher)
        self.repo = KitchenTicketItemRepository(session)
        self.tickets = KitchenTicketRepository(session)

    async def create(
        self, tenant_id: UUID, body: KitchenTicketItemCreate, *, actor: CurrentUser | None = None
    ) -> KitchenTicketItem:
        await self._require_venue(tenant_id, body.venue_id)
        if await self.tickets.get(tenant_id, body.kitchen_ticket_id) is None:
            raise NotFoundError("تیکت آشپزخانه یافت نشد", error_code="kitchen_ticket_not_found")
        quantity = validate_positive_int(body.quantity, field="quantity")
        actor_id = actor.user_id if actor else None
        entity = KitchenTicketItem(
            tenant_id=tenant_id,
            venue_id=body.venue_id,
            kitchen_ticket_id=body.kitchen_ticket_id,
            menu_item_id=body.menu_item_id,
            quantity=quantity,
            status=KitchenItemStatus.QUEUED,
            sort_order=body.sort_order or 0,
            created_by=actor_id,
            updated_by=actor_id,
        )
        await self.repo.add(entity)
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="kitchen_ticket_item",
            entity_id=entity.id,
            action=AuditAction.CREATE,
            actor_user_id=actor_id,
        )
        await self.session.commit()
        await self.session.refresh(entity)
        self.publisher.publish(
            event_type=HospitalityEventType.KITCHEN_TICKET_ITEM_ADDED,
            aggregate_type="kitchen_ticket_item",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"kitchen_ticket_id": str(entity.kitchen_ticket_id)},
        )
        return entity

    async def get(self, tenant_id: UUID, entity_id: UUID) -> KitchenTicketItem:
        entity = await self.repo.get(tenant_id, entity_id)
        if entity is None:
            raise NotFoundError(
                "آیتم تیکت آشپزخانه یافت نشد", error_code="kitchen_ticket_item_not_found"
            )
        return entity

    async def list(self, tenant_id: UUID, *, offset: int, limit: int):
        return await self.repo.list_by_tenant(tenant_id, offset=offset, limit=limit)

    async def change_status(
        self,
        tenant_id: UUID,
        entity_id: UUID,
        new_status: KitchenItemStatus | str,
        *,
        actor: CurrentUser | None = None,
    ) -> KitchenTicketItem:
        entity = await self.get(tenant_id, entity_id)
        resolved = validate_kitchen_item_status(new_status)
        if entity.status in KITCHEN_ITEM_TERMINAL_STATUSES:
            raise AppError(
                "وضعیت آیتم آشپزخانه نهایی شده و قابل تغییر نیست",
                status_code=409,
                error_code="kitchen_ticket_item_status_final",
            )
        actor_id = actor.user_id if actor else None
        entity.status = resolved
        entity.updated_by = actor_id
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="kitchen_ticket_item",
            entity_id=entity.id,
            action=AuditAction.STATUS_CHANGE,
            actor_user_id=actor_id,
            changes={"status": resolved},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity
