"""Delivery integration application services — Phase 13.7."""
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.delivery_integration import (
    DeliveryIntegrationRegistration,
    DeliveryJobIntent,
    DeliveryJobStatusSnapshot,
)
from app.models.types import DeliveryJobStatus, PrescriptionOrderStatus
from app.providers.mocks import MockCommunicationProvider, MockDeliveryProvider
from app.repositories.delivery_integration import (
    DeliveryIntegrationRepository,
    DeliveryJobIntentRepository,
    DeliveryJobStatusSnapshotRepository,
)
from app.repositories.pharmacy import PharmacyRepository, PrescriptionOrderRepository
from app.schemas.delivery_integration import (
    DeliveryIntegrationCreate,
    DeliveryJobIntentCreate,
    DeliveryStatusWebhook,
)
from app.services.foundation import _actor_id
from app.validators import validate_code
from shared.exceptions import AppError, NotFoundError
from shared.security import CurrentUser


class DeliveryIntegrationService:
    def __init__(
        self,
        session: AsyncSession,
        *,
        delivery: MockDeliveryProvider | None = None,
        communication: MockCommunicationProvider | None = None,
    ) -> None:
        self.session = session
        self.registrations = DeliveryIntegrationRepository(session)
        self.intents = DeliveryJobIntentRepository(session)
        self.snapshots = DeliveryJobStatusSnapshotRepository(session)
        self.orders = PrescriptionOrderRepository(session)
        self.pharmacies = PharmacyRepository(session)
        self.delivery = delivery or MockDeliveryProvider()
        self.communication = communication or MockCommunicationProvider()
        self.events = get_event_publisher()

    async def register(
        self, tenant_id: UUID, body: DeliveryIntegrationCreate, *, actor: CurrentUser | None = None
    ) -> DeliveryIntegrationRegistration:
        code = validate_code(body.code)
        if await self.registrations.get_by_code(tenant_id, code):
            raise AppError("کد تکراری است", error_code="duplicate_code", status_code=409)
        entity = DeliveryIntegrationRegistration(
            tenant_id=tenant_id,
            clinic_id=body.clinic_id,
            pharmacy_id=body.pharmacy_id,
            code=code,
            name=body.name,
            delivery_merchant_ref=body.delivery_merchant_ref,
            webhook_secret_ref=body.webhook_secret_ref,
            status=body.status,
            config=body.config,
            created_by=_actor_id(actor),
            updated_by=_actor_id(actor),
        )
        await self.registrations.add(entity)
        self.events.publish(
            event_type=HealthcareEventType.DELIVERY_INTEGRATION_REGISTERED,
            aggregate_type="delivery_integration_registration",
            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 create_intent_for_order(
        self,
        tenant_id: UUID,
        prescription_order_id: UUID,
        body: DeliveryJobIntentCreate,
        *,
        actor: CurrentUser | None = None,
    ) -> DeliveryJobIntent:
        existing = await self.intents.get_by_idempotency(tenant_id, body.idempotency_key)
        if existing:
            return existing
        order = await self.orders.get(tenant_id, prescription_order_id)
        if order is None:
            raise NotFoundError("سفارش نسخه یافت نشد")
        if order.status != PrescriptionOrderStatus.READY:
            raise AppError(
                "سفارش باید آماده باشد",
                status_code=409,
                error_code="prescription_not_ready",
            )
        registration = await self.registrations.get(tenant_id, body.registration_id)
        if registration is None:
            raise NotFoundError("ثبت‌نام Delivery یافت نشد")
        code = validate_code(body.code)
        if await self.intents.get_by_code(tenant_id, code):
            raise AppError("کد تکراری است", error_code="duplicate_code", status_code=409)
        delivery_result = await self.delivery.request_delivery(
            tenant_id=tenant_id,
            request={
                "prescription_order_id": str(prescription_order_id),
                "merchant_ref": registration.delivery_merchant_ref,
                "pickup_address": body.pickup_address,
                "dropoff_address": body.dropoff_address,
            },
        )
        entity = DeliveryJobIntent(
            tenant_id=tenant_id,
            registration_id=body.registration_id,
            prescription_order_id=prescription_order_id,
            code=code,
            delivery_job_ref=delivery_result["delivery_ref"],
            status=DeliveryJobStatus.QUEUED,
            idempotency_key=body.idempotency_key,
            pickup_address=body.pickup_address,
            dropoff_address=body.dropoff_address,
            metadata_json=body.metadata_json,
            created_by=_actor_id(actor),
            updated_by=_actor_id(actor),
        )
        await self.intents.add(entity)
        await self.snapshots.add(
            DeliveryJobStatusSnapshot(
                tenant_id=tenant_id,
                delivery_job_intent_id=entity.id,
                status=DeliveryJobStatus.QUEUED,
                external_status="queued",
                recorded_at=datetime.now(timezone.utc),
                payload=delivery_result,
            )
        )
        self.events.publish(
            event_type=HealthcareEventType.DELIVERY_JOB_INTENT_CREATED,
            aggregate_type="delivery_job_intent",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"code": entity.code, "delivery_ref": entity.delivery_job_ref},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def apply_status_webhook(
        self, tenant_id: UUID, body: DeliveryStatusWebhook
    ) -> DeliveryJobIntent:
        intent = await self.intents.get_by_delivery_ref(tenant_id, body.delivery_job_ref)
        if intent is None:
            raise NotFoundError("Delivery job intent یافت نشد")
        intent.status = body.status
        intent.version += 1
        await self.snapshots.add(
            DeliveryJobStatusSnapshot(
                tenant_id=tenant_id,
                delivery_job_intent_id=intent.id,
                status=body.status,
                external_status=body.external_status,
                recorded_at=datetime.now(timezone.utc),
                payload=body.payload,
            )
        )
        self.events.publish(
            event_type=HealthcareEventType.DELIVERY_JOB_STATUS_UPDATED,
            aggregate_type="delivery_job_intent",
            aggregate_id=intent.id,
            tenant_id=tenant_id,
            payload={"status": body.status.value, "delivery_ref": body.delivery_job_ref},
        )
        if body.status == DeliveryJobStatus.DELIVERED:
            order = await self.orders.get(tenant_id, intent.prescription_order_id)
            if order:
                patient_phone = "unknown"
                await self.communication.send(
                    tenant_id=tenant_id,
                    channel="sms",
                    template_key="healthcare.delivery.delivered",
                    to=patient_phone,
                    payload={"delivery_ref": body.delivery_job_ref},
                )
        await self.session.commit()
        await self.session.refresh(intent)
        return intent

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

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