"""Analytics application services — Phase 12.8.

Snapshots are computed via simple local `COUNT(*)` queries against this
service's own tables only — no external analytics engine, no cross-service
reads.
"""
from __future__ import annotations

from uuid import UUID

from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession

from app.events.publisher import InMemoryEventPublisher, get_event_publisher
from app.events.types import HospitalityEventType
from app.models.analytics import AnalyticsReportDefinition, AnalyticsSnapshot
from app.models.connectors import ConnectorDispatch, ConnectorRegistration
from app.models.foundation import Menu, MenuItem, Venue
from app.models.kitchen import KitchenTicket
from app.models.pos_lite import PosRegister, PosTicket
from app.models.table_service import Reservation
from app.models.types import AuditAction, LifecycleStatus
from app.repositories.analytics import (
    AnalyticsReportDefinitionRepository,
    AnalyticsSnapshotRepository,
)
from app.repositories.foundation import VenueRepository
from app.schemas.analytics import AnalyticsReportDefinitionCreate, AnalyticsSnapshotRefresh
from app.services.audit_service import AuditService
from app.validators import validate_code, validate_non_empty
from shared.exceptions import AppError, NotFoundError
from shared.security import CurrentUser

# metric_key -> ORM model counted locally (venue/tenant scoped COUNT(*) only)
METRIC_TABLE_MAP: dict[str, type] = {
    "venues_count": Venue,
    "menus_count": Menu,
    "menu_items_count": MenuItem,
    "pos_registers_count": PosRegister,
    "pos_tickets_count": PosTicket,
    "reservations_count": Reservation,
    "kitchen_tickets_count": KitchenTicket,
    "connector_registrations_count": ConnectorRegistration,
    "connector_dispatches_count": ConnectorDispatch,
}


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

    async def create(
        self,
        tenant_id: UUID,
        body: AnalyticsReportDefinitionCreate,
        *,
        actor: CurrentUser | None = None,
    ) -> AnalyticsReportDefinition:
        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="analytics_report_code_exists",
            )
        if not body.metric_keys:
            raise AppError(
                "حداقل یک metric_key الزامی است",
                status_code=422,
                error_code="analytics_metric_keys_required",
            )
        unknown = [key for key in body.metric_keys if key not in METRIC_TABLE_MAP]
        if unknown:
            raise AppError(
                "metric_key نامعتبر است",
                status_code=422,
                error_code="invalid_metric_key",
                details={"unknown": unknown},
            )
        actor_id = actor.user_id if actor else None
        entity = AnalyticsReportDefinition(
            tenant_id=tenant_id,
            venue_id=body.venue_id,
            code=code,
            name=validate_non_empty(body.name, field="name"),
            metric_keys=body.metric_keys,
            filters=body.filters,
            status=LifecycleStatus.ACTIVE,
            created_by=actor_id,
            updated_by=actor_id,
        )
        await self.repo.add(entity)
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="analytics_report_definition",
            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.ANALYTICS_REPORT_DEFINITION_CREATED,
            aggregate_type="analytics_report_definition",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"code": entity.code},
        )
        return entity

    async def get(self, tenant_id: UUID, entity_id: UUID) -> AnalyticsReportDefinition:
        entity = await self.repo.get(tenant_id, entity_id)
        if entity is None:
            raise NotFoundError(
                "گزارش تحلیلی یافت نشد", error_code="analytics_report_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 AnalyticsSnapshotService(_AnalyticsBase):
    def __init__(self, session: AsyncSession, publisher: InMemoryEventPublisher | None = None) -> None:
        super().__init__(session, publisher)
        self.repo = AnalyticsSnapshotRepository(session)
        self.reports = AnalyticsReportDefinitionRepository(session)

    async def _count(self, tenant_id: UUID, model: type) -> int:
        stmt = select(func.count()).select_from(model).where(
            model.tenant_id == tenant_id,  # type: ignore[attr-defined]
            model.is_deleted.is_(False),  # type: ignore[attr-defined]
        )
        result = await self.session.execute(stmt)
        return int(result.scalar_one())

    async def refresh(
        self,
        tenant_id: UUID,
        body: AnalyticsSnapshotRefresh,
        *,
        actor: CurrentUser | None = None,
    ) -> AnalyticsSnapshot:
        if body.venue_id is not None:
            await self._require_venue(tenant_id, body.venue_id)
        report = await self.reports.get(tenant_id, body.report_id)
        if report is None:
            raise NotFoundError(
                "گزارش تحلیلی یافت نشد", error_code="analytics_report_not_found"
            )
        period_start = validate_non_empty(body.period_start, field="period_start")
        period_end = validate_non_empty(body.period_end, field="period_end")

        metrics: dict[str, int] = {}
        for key in report.metric_keys:
            model = METRIC_TABLE_MAP.get(key)
            if model is None:
                continue
            metrics[key] = await self._count(tenant_id, model)

        actor_id = actor.user_id if actor else None
        entity = AnalyticsSnapshot(
            tenant_id=tenant_id,
            venue_id=body.venue_id,
            report_id=report.id,
            period_start=period_start,
            period_end=period_end,
            metrics=metrics,
            status=LifecycleStatus.ACTIVE,
            created_by=actor_id,
            updated_by=actor_id,
        )
        await self.repo.add(entity)
        await self.audit.record(
            tenant_id=tenant_id,
            entity_type="analytics_snapshot",
            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.ANALYTICS_SNAPSHOT_REFRESHED,
            aggregate_type="analytics_snapshot",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"report_id": str(entity.report_id), "metric_count": len(metrics)},
        )
        return entity

    async def get(self, tenant_id: UUID, entity_id: UUID) -> AnalyticsSnapshot:
        entity = await self.repo.get(tenant_id, entity_id)
        if entity is None:
            raise NotFoundError(
                "عکس‌فوری تحلیلی یافت نشد", error_code="analytics_snapshot_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)
