"""Analytics application services — Phase 11.10."""
from datetime import datetime, timezone
from uuid import UUID

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

from app.events.publisher import get_event_publisher
from app.events.types import ExperienceEventType
from app.models.analytics import (
    ExperienceAnalyticsReportDefinition,
    ExperienceAnalyticsSnapshot,
)
from app.models.bundles import ExperienceBundleDefinition, ExperienceTenantBundle
from app.models.components import ExperienceComponent
from app.models.forms import ExperienceForm, FormSubmission, ExperienceSurvey
from app.models.foundation import ExperienceWorkspace
from app.models.integrations import (
    ExperienceConsumerConnectorRegistration,
    ExperienceNotifyDispatch,
    ExperienceWidgetDefinition,
    ExperienceWidgetInstance,
)
from app.models.publishing import PublishRelease
from app.models.sites import ExperiencePage, ExperienceSite
from app.models.templates import ExperienceTemplate
from app.models.types import AnalyticsSnapshotStatus, AuditAction
from app.policies.analytics import AnalyticsReportDefinitionPolicy, AnalyticsSnapshotPolicy
from app.repositories.analytics import (
    AnalyticsReportDefinitionRepository,
    AnalyticsSnapshotRepository,
)
from app.repositories.foundation import ExperienceWorkspaceRepository
from app.repositories.sites import ExperienceSiteRepository
from app.services.audit_service import AuditService
from app.validators import ensure_optimistic_version
from app.validators.analytics import validate_metadata, validate_metric_keys
from shared.exceptions import AppError, NotFoundError
from shared.security import CurrentUser

METRIC_TABLE_MAP: dict[str, type] = {
    "workspaces_count": ExperienceWorkspace,
    "sites_count": ExperienceSite,
    "pages_count": ExperiencePage,
    "components_count": ExperienceComponent,
    "templates_count": ExperienceTemplate,
    "forms_count": ExperienceForm,
    "form_submissions_count": FormSubmission,
    "surveys_count": ExperienceSurvey,
    "publish_releases_count": PublishRelease,
    "widget_definitions_count": ExperienceWidgetDefinition,
    "widget_instances_count": ExperienceWidgetInstance,
    "consumer_connector_registrations_count": ExperienceConsumerConnectorRegistration,
    "notify_dispatches_count": ExperienceNotifyDispatch,
    "bundle_definitions_count": ExperienceBundleDefinition,
    "tenant_bundles_count": ExperienceTenantBundle,
}


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


def _jsonable(value):
    if isinstance(value, dict):
        return {key: _jsonable(item) for key, item in value.items()}
    if isinstance(value, (list, tuple)):
        return [_jsonable(item) for item in value]
    if isinstance(value, (UUID, datetime)):
        return value.isoformat() if isinstance(value, datetime) else str(value)
    if hasattr(value, "value"):
        return value.value
    return value


class ExperienceAnalyticsService:
    def __init__(self, session: AsyncSession) -> None:
        self.session = session
        self.reports = AnalyticsReportDefinitionRepository(session)
        self.snapshots = AnalyticsSnapshotRepository(session)
        self.workspaces = ExperienceWorkspaceRepository(session)
        self.sites = ExperienceSiteRepository(session)
        self.audit = AuditService(session)
        self.events = get_event_publisher()
        self.policies = {
            "analytics_report_definition": AnalyticsReportDefinitionPolicy(),
            "analytics_snapshot": AnalyticsSnapshotPolicy(),
        }

    async def _scope(self, tenant_id, workspace_id, site_id=None):
        if await self.workspaces.get(tenant_id, workspace_id) is None:
            raise NotFoundError("فضای کاری یافت نشد")
        if site_id is not None:
            site = await self.sites.get(tenant_id, site_id)
            if site is None or site.workspace_id != workspace_id:
                raise NotFoundError("سایت یافت نشد")

    async def _finish(self, entity, kind, action, event, actor, changes=None):
        await self.audit.record(
            tenant_id=entity.tenant_id,
            entity_type=kind,
            entity_id=entity.id,
            action=action,
            actor_user_id=_actor(actor),
            changes=_jsonable(changes or {}),
        )
        self.events.publish(
            event_type=event,
            aggregate_type=kind,
            aggregate_id=entity.id,
            tenant_id=entity.tenant_id,
            payload={"status": getattr(entity.status, "value", entity.status)},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def _get(self, repo, tenant_id, entity_id, label):
        entity = await repo.get(tenant_id, entity_id)
        if entity is None:
            raise NotFoundError(f"{label} یافت نشد")
        return entity

    async def _list(self, repo, tenant_id, *, offset, limit, spec):
        items, total = await repo.list_filtered(tenant_id, offset=offset, limit=limit, spec=spec)
        return list(items), total

    def _validate_metric_keys(self, metric_keys: list[str]) -> None:
        validate_metric_keys(metric_keys)
        unknown = [key for key in 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},
            )

    async def create_report_definition(self, tenant_id, body, *, actor=None):
        await self._scope(tenant_id, body.workspace_id, body.site_id)
        self._validate_metric_keys(body.metric_keys)
        if await self.reports.get_by_code(tenant_id, body.code) is not None:
            raise AppError(
                "کد گزارش تکراری است",
                status_code=409,
                error_code="analytics_report_code_exists",
            )
        entity = ExperienceAnalyticsReportDefinition(
            tenant_id=tenant_id,
            workspace_id=body.workspace_id,
            site_id=body.site_id,
            code=body.code,
            name=body.name,
            metric_keys=body.metric_keys,
            filters=body.filters,
            status=body.status,
            metadata_json=validate_metadata(body.metadata_json),
            created_by=_actor(actor),
            updated_by=_actor(actor),
        )
        await self.reports.add(entity)
        return await self._finish(
            entity,
            "analytics_report_definition",
            AuditAction.CREATE,
            ExperienceEventType.ANALYTICS_REPORT_DEFINITION_CREATED,
            actor,
        )

    async def get_report_definition(self, tenant_id, entity_id):
        return await self._get(
            self.reports, tenant_id, entity_id, "analytics report definition"
        )

    async def list_report_definitions(self, tenant_id, *, offset, limit, spec=None):
        return await self._list(self.reports, tenant_id, offset=offset, limit=limit, spec=spec)

    async def update_report_definition(self, tenant_id, entity_id, body, *, actor=None):
        entity = await self._get(
            self.reports, tenant_id, entity_id, "analytics report definition"
        )
        ensure_optimistic_version(entity, body.version)
        data = body.model_dump(exclude_unset=True, exclude={"version"})
        if "metadata_json" in data:
            data["metadata_json"] = validate_metadata(data["metadata_json"])
        if "metric_keys" in data and data["metric_keys"] is not None:
            self._validate_metric_keys(data["metric_keys"])
        if data.get("status") is not None:
            self.policies["analytics_report_definition"].assert_status_change(
                current=entity.status, target=data["status"]
            )
        for field, value in data.items():
            setattr(entity, field, value)
        entity.updated_by = _actor(actor)
        entity.version += 1
        return await self._finish(
            entity,
            "analytics_report_definition",
            AuditAction.UPDATE,
            ExperienceEventType.ANALYTICS_REPORT_DEFINITION_UPDATED,
            actor,
            data,
        )

    async def delete_report_definition(self, tenant_id, entity_id, *, actor=None):
        entity = await self._get(
            self.reports, tenant_id, entity_id, "analytics report definition"
        )
        await self.reports.soft_delete(entity, deleted_by=_actor(actor))
        entity.updated_by = _actor(actor)
        entity.version += 1
        return await self._finish(
            entity,
            "analytics_report_definition",
            AuditAction.DELETE,
            ExperienceEventType.ANALYTICS_REPORT_DEFINITION_DELETED,
            actor,
        )

    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_snapshot(self, tenant_id, body, *, actor=None):
        report = await self.get_report_definition(tenant_id, body.report_id)
        workspace_id = body.workspace_id or report.workspace_id
        await self._scope(tenant_id, workspace_id, body.site_id)
        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)
        entity = ExperienceAnalyticsSnapshot(
            tenant_id=tenant_id,
            report_id=report.id,
            workspace_id=workspace_id,
            site_id=body.site_id,
            period_start=body.period_start,
            period_end=body.period_end,
            metrics=metrics,
            status=AnalyticsSnapshotStatus.ACTIVE,
            created_by=_actor(actor),
            updated_by=_actor(actor),
        )
        await self.snapshots.add(entity)
        return await self._finish(
            entity,
            "analytics_snapshot",
            AuditAction.CREATE,
            ExperienceEventType.ANALYTICS_SNAPSHOT_REFRESHED,
            actor,
            {"report_id": str(report.id), "metric_count": len(metrics)},
        )

    async def get_snapshot(self, tenant_id, entity_id):
        return await self._get(self.snapshots, tenant_id, entity_id, "analytics snapshot")

    async def list_snapshots(self, tenant_id, *, offset, limit, spec=None):
        return await self._list(self.snapshots, tenant_id, offset=offset, limit=limit, spec=spec)

    async def update_snapshot(self, tenant_id, entity_id, body, *, actor=None):
        entity = await self._get(self.snapshots, tenant_id, entity_id, "analytics snapshot")
        ensure_optimistic_version(entity, body.version)
        data = body.model_dump(exclude_unset=True, exclude={"version"})
        if data.get("status") is not None:
            self.policies["analytics_snapshot"].assert_status_change(
                current=entity.status, target=data["status"]
            )
        for field, value in data.items():
            setattr(entity, field, value)
        entity.updated_by = _actor(actor)
        entity.version += 1
        return await self._finish(
            entity,
            "analytics_snapshot",
            AuditAction.UPDATE,
            ExperienceEventType.ANALYTICS_SNAPSHOT_UPDATED,
            actor,
            data,
        )

    async def delete_snapshot(self, tenant_id, entity_id, *, actor=None):
        entity = await self._get(self.snapshots, tenant_id, entity_id, "analytics snapshot")
        await self.snapshots.soft_delete(entity, deleted_by=_actor(actor))
        entity.updated_by = _actor(actor)
        entity.version += 1
        return await self._finish(
            entity,
            "analytics_snapshot",
            AuditAction.DELETE,
            ExperienceEventType.ANALYTICS_SNAPSHOT_DELETED,
            actor,
        )
