"""Pharmacy application services — Phase 13.6."""
from __future__ import annotations

from datetime import datetime, timezone
from uuid import UUID

from sqlalchemy.ext.asyncio import AsyncSession

from app.events.publisher import get_event_publisher
from app.events.types import HealthcareEventType
from app.models.pharmacy import Pharmacy, PrescriptionLine, PrescriptionOrder, PrescriptionStatusHistory
from app.models.types import PrescriptionLifecycleAction, PrescriptionOrderStatus
from app.policies.pharmacy import PrescriptionLifecyclePolicy
from app.providers.mocks import MockCommunicationProvider
from app.repositories.pharmacy import (
    PharmacyRepository,
    PrescriptionLineRepository,
    PrescriptionOrderRepository,
    PrescriptionStatusHistoryRepository,
)
from app.repositories.profiles import DoctorRepository, PatientRepository
from app.schemas.pharmacy import PharmacyCreate, PrescriptionLifecycleRequest, PrescriptionOrderCreate
from app.services.foundation import _actor_id
from app.validators import ensure_optimistic_version, validate_code
from shared.exceptions import AppError, NotFoundError
from shared.security import CurrentUser


class PharmacyService:
    def __init__(self, session: AsyncSession) -> None:
        self.session = session
        self.pharmacies = PharmacyRepository(session)
        self.orders = PrescriptionOrderRepository(session)
        self.lines = PrescriptionLineRepository(session)
        self.history = PrescriptionStatusHistoryRepository(session)
        self.patients = PatientRepository(session)
        self.doctors = DoctorRepository(session)
        self.policy = PrescriptionLifecyclePolicy()
        self.communication = MockCommunicationProvider()
        self.events = get_event_publisher()

    async def create_pharmacy(
        self, tenant_id: UUID, body: PharmacyCreate, *, actor: CurrentUser | None = None
    ) -> Pharmacy:
        code = validate_code(body.code)
        if await self.pharmacies.get_by_code(tenant_id, code):
            raise AppError("کد تکراری است", error_code="duplicate_code", status_code=409)
        entity = Pharmacy(
            tenant_id=tenant_id,
            code=code,
            name=body.name,
            contact=body.contact,
            hours=body.hours,
            service_area_ref=body.service_area_ref,
            status=body.status,
            metadata_json=body.metadata_json,
            created_by=_actor_id(actor),
            updated_by=_actor_id(actor),
        )
        await self.pharmacies.add(entity)
        self.events.publish(
            event_type=HealthcareEventType.PHARMACY_CREATED,
            aggregate_type="pharmacy",
            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 submit_order(
        self, tenant_id: UUID, body: PrescriptionOrderCreate, *, actor: CurrentUser | None = None
    ) -> PrescriptionOrder:
        if not body.lines:
            raise AppError("حداقل یک خط نسخه الزامی است", status_code=422, error_code="empty_prescription")
        if await self.pharmacies.get(tenant_id, body.pharmacy_id) is None:
            raise NotFoundError("داروخانه یافت نشد")
        if await self.patients.get(tenant_id, body.patient_id) is None:
            raise NotFoundError("بیمار یافت نشد")
        if await self.doctors.get(tenant_id, body.doctor_id) is None:
            raise NotFoundError("پزشک یافت نشد")
        code = validate_code(body.code)
        if await self.orders.get_by_code(tenant_id, code):
            raise AppError("کد تکراری است", error_code="duplicate_code", status_code=409)
        entity = PrescriptionOrder(
            tenant_id=tenant_id,
            pharmacy_id=body.pharmacy_id,
            encounter_id=body.encounter_id,
            visit_session_id=body.visit_session_id,
            patient_id=body.patient_id,
            doctor_id=body.doctor_id,
            code=code,
            status=PrescriptionOrderStatus.SUBMITTED,
            status_changed_at=datetime.now(timezone.utc),
            notes=body.notes,
            metadata_json=body.metadata_json,
            created_by=_actor_id(actor),
            updated_by=_actor_id(actor),
        )
        await self.orders.add(entity)
        for line in body.lines:
            await self.lines.add(
                PrescriptionLine(
                    tenant_id=tenant_id,
                    prescription_order_id=entity.id,
                    line_no=line.line_no,
                    medication_code=line.medication_code,
                    display=line.display,
                    quantity=line.quantity,
                    dosage_instructions=line.dosage_instructions,
                    metadata_json=line.metadata_json,
                    created_by=_actor_id(actor),
                    updated_by=_actor_id(actor),
                )
            )
        self.events.publish(
            event_type=HealthcareEventType.PRESCRIPTION_ORDER_SUBMITTED,
            aggregate_type="prescription_order",
            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,
        order_id: UUID,
        action: PrescriptionLifecycleAction,
        body: PrescriptionLifecycleRequest,
        *,
        actor: CurrentUser | None = None,
    ) -> PrescriptionOrder:
        entity = await self.orders.get(tenant_id, order_id)
        if entity is None:
            raise NotFoundError("سفارش نسخه یافت نشد")
        ensure_optimistic_version(entity, body.version)
        new_status = self.policy.ensure_transition(action=action, current=entity.status)
        old_status = entity.status
        entity.status = new_status
        entity.status_changed_at = datetime.now(timezone.utc)
        entity.version += 1
        entity.updated_by = _actor_id(actor)
        await self.history.add(
            PrescriptionStatusHistory(
                tenant_id=tenant_id,
                prescription_order_id=entity.id,
                from_status=old_status,
                to_status=new_status,
                actor_user_id=_actor_id(actor),
                reason=body.reason,
            )
        )
        self.events.publish(
            event_type=HealthcareEventType.PRESCRIPTION_ORDER_STATUS_CHANGED,
            aggregate_type="prescription_order",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"code": entity.code, "status": new_status.value},
        )
        if new_status == PrescriptionOrderStatus.READY:
            patient = await self.patients.get(tenant_id, entity.patient_id)
            to = (patient.contact or {}).get("phone", "unknown") if patient else "unknown"
            await self.communication.send(
                tenant_id=tenant_id,
                channel="sms",
                template_key="healthcare.prescription.ready",
                to=to,
                payload={"order_id": str(entity.id), "code": entity.code},
            )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def accept(self, tenant_id: UUID, order_id: UUID, body: PrescriptionLifecycleRequest, *, actor=None):
        return await self._apply_lifecycle(
            tenant_id, order_id, PrescriptionLifecycleAction.ACCEPT, body, actor=actor
        )

    async def prepare(self, tenant_id: UUID, order_id: UUID, body: PrescriptionLifecycleRequest, *, actor=None):
        return await self._apply_lifecycle(
            tenant_id, order_id, PrescriptionLifecycleAction.PREPARE, body, actor=actor
        )

    async def mark_ready(self, tenant_id: UUID, order_id: UUID, body: PrescriptionLifecycleRequest, *, actor=None):
        return await self._apply_lifecycle(
            tenant_id, order_id, PrescriptionLifecycleAction.MARK_READY, body, actor=actor
        )

    async def complete_pickup(
        self, tenant_id: UUID, order_id: UUID, body: PrescriptionLifecycleRequest, *, actor=None
    ):
        return await self._apply_lifecycle(
            tenant_id, order_id, PrescriptionLifecycleAction.COMPLETE_PICKUP, body, actor=actor
        )

    async def cancel(self, tenant_id: UUID, order_id: UUID, body: PrescriptionLifecycleRequest, *, actor=None):
        return await self._apply_lifecycle(
            tenant_id, order_id, PrescriptionLifecycleAction.CANCEL, body, actor=actor
        )

    async def get_order(self, tenant_id: UUID, order_id: UUID) -> PrescriptionOrder:
        entity = await self.orders.get(tenant_id, order_id)
        if entity is None:
            raise NotFoundError("سفارش نسخه یافت نشد")
        return entity

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

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