"""Connector application services — Phase 12.7.

Dispatches are simulated locally via mock provider implementations. No
httpx calls and no imports from other services — the mocks only prove out
the request/response contract shape until real connector SDKs land.
"""
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.connectors import ConnectorDispatch, ConnectorRegistration
from app.models.types import AuditAction, ConnectorStatus, DispatchStatus
from app.providers.mocks import (
    MockAccountingProvider,
    MockCommunicationProvider,
    MockCRMProvider,
    MockDeliveryProvider,
    MockLoyaltyProvider,
    MockWebsiteBuilderProvider,
)
from app.repositories.connectors import (
    ConnectorDispatchRepository,
    ConnectorRegistrationRepository,
)
from app.repositories.foundation import VenueRepository
from app.schemas.connectors import ConnectorDispatchCreate, ConnectorRegistrationCreate
from app.services.audit_service import AuditService
from app.validators import validate_code, validate_connector_status, validate_non_empty
from shared.exceptions import AppError, NotFoundError
from shared.security import CurrentUser

# One dispatcher per connector kind — all are local no-op mocks; hospitality
# never calls real provider SDKs directly.
_MOCK_DISPATCHERS = {
    "delivery": MockDeliveryProvider(),
    "accounting": MockAccountingProvider(),
    "crm": MockCRMProvider(),
    "loyalty": MockLoyaltyProvider(),
    "communication": MockCommunicationProvider(),
    "website": MockWebsiteBuilderProvider(),
}


class _ConnectorsBase:
    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 ConnectorRegistrationService(_ConnectorsBase):
    def __init__(self, session: AsyncSession, publisher: InMemoryEventPublisher | None = None) -> None:
        super().__init__(session, publisher)
        self.repo = ConnectorRegistrationRepository(session)

    async def create(
        self,
        tenant_id: UUID,
        body: ConnectorRegistrationCreate,
        *,
        actor: CurrentUser | None = None,
    ) -> ConnectorRegistration:
        if body.venue_id is not None:
            await self._require_venue(tenant_id, body.venue_id)
        code = validate_code(body.code)
        if await self.repo.get_by_code(tenant_id, code) is not None:
            raise AppError(
                "کد رابط اتصال تکراری است",
                status_code=409,
                error_code="connector_registration_code_exists",
            )
        actor_id = actor.user_id if actor else None
        entity = ConnectorRegistration(
            tenant_id=tenant_id,
            venue_id=body.venue_id,
            kind=body.kind,
            code=code,
            name=validate_non_empty(body.name, field="name"),
            status=validate_connector_status(body.status),
            external_ref=body.external_ref,
            config=body.config,
            created_by=actor_id,
            updated_by=actor_id,
        )
        await self.repo.add(entity)
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="connector_registration",
            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.CONNECTOR_REGISTRATION_CREATED,
            aggregate_type="connector_registration",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"code": entity.code, "kind": entity.kind.value},
        )
        return entity

    async def get(self, tenant_id: UUID, entity_id: UUID) -> ConnectorRegistration:
        entity = await self.repo.get(tenant_id, entity_id)
        if entity is None:
            raise NotFoundError(
                "رابط اتصال یافت نشد", error_code="connector_registration_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 ConnectorDispatchService(_ConnectorsBase):
    def __init__(self, session: AsyncSession, publisher: InMemoryEventPublisher | None = None) -> None:
        super().__init__(session, publisher)
        self.repo = ConnectorDispatchRepository(session)
        self.registrations = ConnectorRegistrationRepository(session)

    async def create(
        self,
        tenant_id: UUID,
        body: ConnectorDispatchCreate,
        *,
        actor: CurrentUser | None = None,
    ) -> ConnectorDispatch:
        if body.venue_id is not None:
            await self._require_venue(tenant_id, body.venue_id)
        registration = await self.registrations.get(tenant_id, body.registration_id)
        if registration is None:
            raise NotFoundError(
                "رابط اتصال یافت نشد", error_code="connector_registration_not_found"
            )
        event_type = validate_non_empty(body.event_type, field="event_type")
        actor_id = actor.user_id if actor else None

        if registration.status != ConnectorStatus.ACTIVE:
            status = DispatchStatus.FAILED
            response = {"ok": False, "reason": "registration_not_active"}
        else:
            dispatcher = _MOCK_DISPATCHERS.get(registration.kind.value)
            response = {"ok": True, "provider": "mock", "kind": registration.kind.value}
            if dispatcher is not None:
                if hasattr(dispatcher, "send"):
                    await dispatcher.send(
                        tenant_id=tenant_id,
                        channel="default",
                        template_key=event_type,
                        to=body.payload_ref or "",
                        payload={"event_type": event_type},
                    )
                elif hasattr(dispatcher, "create_receivable_ref"):
                    response = await dispatcher.create_receivable_ref(
                        tenant_id=tenant_id,
                        reference=body.payload_ref or event_type,
                        payload={"event_type": event_type},
                    )
                elif hasattr(dispatcher, "create_delivery_ref"):
                    response = await dispatcher.create_delivery_ref(
                        tenant_id=tenant_id,
                        reference=body.payload_ref or event_type,
                        payload={"event_type": event_type},
                    )
                elif hasattr(dispatcher, "resolve_contact"):
                    response = await dispatcher.resolve_contact(
                        tenant_id=tenant_id, contact_ref=body.payload_ref or event_type
                    )
                elif hasattr(dispatcher, "resolve_member"):
                    response = await dispatcher.resolve_member(
                        tenant_id=tenant_id, member_ref=body.payload_ref or event_type
                    )
                elif hasattr(dispatcher, "resolve_site"):
                    response = await dispatcher.resolve_site(
                        tenant_id=tenant_id, site_ref=body.payload_ref or event_type
                    )
            status = DispatchStatus.SENT

        entity = ConnectorDispatch(
            tenant_id=tenant_id,
            venue_id=body.venue_id,
            registration_id=body.registration_id,
            direction=body.direction,
            event_type=event_type,
            payload_ref=body.payload_ref,
            status=status,
            response_json=response,
            created_by=actor_id,
            updated_by=actor_id,
        )
        await self.repo.add(entity)
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="connector_dispatch",
            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.CONNECTOR_DISPATCH_CREATED,
            aggregate_type="connector_dispatch",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"status": entity.status.value, "event_type": entity.event_type},
        )
        return entity

    async def get(self, tenant_id: UUID, entity_id: UUID) -> ConnectorDispatch:
        entity = await self.repo.get(tenant_id, entity_id)
        if entity is None:
            raise NotFoundError(
                "ارسال رابط اتصال یافت نشد", error_code="connector_dispatch_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)
