"""Appointment application service — Phase 14.1."""
from __future__ import annotations

from datetime import datetime, timezone
from uuid import UUID

from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.asyncio import AsyncSession

from app.events.publisher import get_event_publisher
from app.events.types import BeautyBusinessEventType
from app.models.appointments import Appointment, AppointmentStatusHistory
from app.models.types import AppointmentLifecycleAction, AppointmentStatus, AuditAction
from app.policies.appointments import AppointmentLifecyclePolicy
from app.repositories.appointment_core import (
    AppointmentRepository,
    AppointmentStatusHistoryRepository,
)
from app.repositories.foundation import BeautyBranchRepository, BeautyOrganizationRepository
from app.schemas.appointment_core import (
    AppointmentCreate,
    AppointmentLifecycleRequest,
    AppointmentUpdate,
)
from app.services.audit_service import AuditService
from app.validators import ensure_optimistic_version, validate_code
from shared.exceptions import AppError, NotFoundError
from shared.security import CurrentUser

_ACTION_EVENTS = {
    AppointmentLifecycleAction.CONFIRM: BeautyBusinessEventType.APPOINTMENT_CONFIRMED,
    AppointmentLifecycleAction.CHECK_IN: BeautyBusinessEventType.APPOINTMENT_CHECKED_IN,
    AppointmentLifecycleAction.START: BeautyBusinessEventType.APPOINTMENT_STARTED,
    AppointmentLifecycleAction.COMPLETE: BeautyBusinessEventType.APPOINTMENT_COMPLETED,
    AppointmentLifecycleAction.CANCEL: BeautyBusinessEventType.APPOINTMENT_CANCELLED,
    AppointmentLifecycleAction.NO_SHOW: BeautyBusinessEventType.APPOINTMENT_NO_SHOW,
}

_ACTION_AUDIT = {
    AppointmentLifecycleAction.CONFIRM: AuditAction.CONFIRM,
    AppointmentLifecycleAction.CHECK_IN: AuditAction.CHECK_IN,
    AppointmentLifecycleAction.COMPLETE: AuditAction.COMPLETE,
    AppointmentLifecycleAction.CANCEL: AuditAction.CANCEL,
    AppointmentLifecycleAction.NO_SHOW: AuditAction.NO_SHOW,
    AppointmentLifecycleAction.START: AuditAction.STATUS_CHANGE,
}


def _actor(actor: CurrentUser | None) -> str | None:
    return actor.user_id if actor else None


class AppointmentCoreService:
    def __init__(self, session: AsyncSession) -> None:
        self.session = session
        self.repo = AppointmentRepository(session)
        self.history = AppointmentStatusHistoryRepository(session)
        self.orgs = BeautyOrganizationRepository(session)
        self.branches = BeautyBranchRepository(session)
        self.audit = AuditService(session)
        self.events = get_event_publisher()
        self.policy = AppointmentLifecyclePolicy()

    async def create(
        self, tenant_id: UUID, body: AppointmentCreate, *, actor: CurrentUser | None = None
    ) -> Appointment:
        org = await self.orgs.get(tenant_id, body.organization_id)
        if org is None:
            raise NotFoundError("سازمان یافت نشد")
        branch = await self.branches.get(tenant_id, body.branch_id)
        if branch 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)
        if body.status not in {AppointmentStatus.REQUESTED, AppointmentStatus.CONFIRMED}:
            raise AppError("وضعیت اولیه نامعتبر است", status_code=422, error_code="invalid_initial_status")
        entity = Appointment(
            tenant_id=tenant_id,
            organization_id=body.organization_id,
            branch_id=body.branch_id,
            appointment_type_id=body.appointment_type_id,
            code=code,
            customer_ref=body.customer_ref,
            staff_ref=body.staff_ref,
            room_ref=body.room_ref,
            service_ref=body.service_ref,
            status=body.status,
            scheduled_start=body.scheduled_start,
            scheduled_end=body.scheduled_end,
            notes=body.notes,
            metadata_json=body.metadata_json,
            status_changed_at=datetime.now(timezone.utc),
            created_by=_actor(actor),
            updated_by=_actor(actor),
        )
        try:
            await self.repo.add(entity)
        except IntegrityError as exc:
            raise AppError("کد نوبت تکراری است", error_code="duplicate_code", status_code=409) from exc
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="appointment",
            entity_id=entity.id,
            action=AuditAction.CREATE,
            actor_user_id=_actor(actor),
            changes=body.model_dump(mode="json"),
        )
        self.events.publish(
            event_type=BeautyBusinessEventType.APPOINTMENT_CREATED,
            aggregate_type="appointment",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"code": entity.code, "status": entity.status.value},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def get(self, tenant_id: UUID, appointment_id: UUID) -> Appointment:
        entity = await self.repo.get(tenant_id, appointment_id)
        if entity is None:
            raise NotFoundError("نوبت یافت نشد")
        return entity

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

    async def update(
        self,
        tenant_id: UUID,
        appointment_id: UUID,
        body: AppointmentUpdate,
        *,
        actor: CurrentUser | None = None,
    ) -> Appointment:
        entity = await self.get(tenant_id, appointment_id)
        ensure_optimistic_version(entity, body.version)
        data = body.model_dump(exclude_unset=True, exclude={"version"})
        for key, value in data.items():
            setattr(entity, key, value)
        entity.version += 1
        entity.updated_by = _actor(actor)
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="appointment",
            entity_id=entity.id,
            action=AuditAction.UPDATE,
            actor_user_id=_actor(actor),
            changes=data,
        )
        self.events.publish(
            event_type=BeautyBusinessEventType.APPOINTMENT_UPDATED,
            aggregate_type="appointment",
            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_lifecycle(
        self,
        tenant_id: UUID,
        appointment_id: UUID,
        action: AppointmentLifecycleAction,
        body: AppointmentLifecycleRequest,
        *,
        actor: CurrentUser | None = None,
    ) -> Appointment:
        entity = await self.get(tenant_id, appointment_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.status_reason = reason
        entity.status_changed_at = datetime.now(timezone.utc)
        entity.version += 1
        entity.updated_by = _actor(actor)
        hist = AppointmentStatusHistory(
            tenant_id=tenant_id,
            appointment_id=entity.id,
            action=action,
            from_status=from_status,
            to_status=to_status,
            reason=reason,
            actor_user_id=_actor(actor),
        )
        await self.history.add(hist)
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="appointment",
            entity_id=entity.id,
            action=_ACTION_AUDIT.get(action, AuditAction.STATUS_CHANGE),
            actor_user_id=_actor(actor),
            changes={"from_status": from_status.value, "to_status": to_status.value},
        )
        self.events.publish(
            event_type=BeautyBusinessEventType.APPOINTMENT_STATUS_CHANGED,
            aggregate_type="appointment",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"action": action.value, "from_status": from_status.value, "to_status": to_status.value},
        )
        self.events.publish(
            event_type=_ACTION_EVENTS[action],
            aggregate_type="appointment",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"code": entity.code, "status": to_status.value},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def list_history(self, tenant_id: UUID, appointment_id: UUID):
        await self.get(tenant_id, appointment_id)
        return list(await self.history.list_for_appointment(tenant_id, appointment_id))
