"""AI content hook application services — Phase 11.10."""
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 ExperienceEventType
from app.models.ai_hooks import (
    ExperienceAiContentDispatch,
    ExperienceAiContentHookRegistration,
)
from app.models.types import AiContentDispatchStatus, AiContentHookStatus, AuditAction
from app.policies.ai_hooks import AiContentDispatchPolicy, AiContentHookRegistrationPolicy
from app.providers.mocks import MockAIProvider
from app.repositories.ai_hooks import (
    AiContentDispatchRepository,
    AiContentHookRegistrationRepository,
)
from app.repositories.foundation import ExperienceWorkspaceRepository
from app.repositories.sites import ExperiencePageRepository, ExperienceSiteRepository
from app.services.audit_service import AuditService
from app.validators import ensure_optimistic_version
from app.validators.ai_hooks import validate_config, validate_metadata, validate_payload
from shared.exceptions import AppError, NotFoundError
from shared.security import CurrentUser

_MOCK_AI = MockAIProvider()


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 ExperienceAiHooksService:
    def __init__(self, session: AsyncSession) -> None:
        self.session = session
        self.registrations = AiContentHookRegistrationRepository(session)
        self.dispatches = AiContentDispatchRepository(session)
        self.workspaces = ExperienceWorkspaceRepository(session)
        self.sites = ExperienceSiteRepository(session)
        self.pages = ExperiencePageRepository(session)
        self.audit = AuditService(session)
        self.events = get_event_publisher()
        self.policies = {
            "ai_content_hook_registration": AiContentHookRegistrationPolicy(),
            "ai_content_dispatch": AiContentDispatchPolicy(),
        }

    async def _scope(self, tenant_id, workspace_id, site_id=None, page_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("سایت یافت نشد")
        if page_id is not None:
            page = await self.pages.get(tenant_id, page_id)
            if page is None:
                raise NotFoundError("صفحه یافت نشد")
            if site_id is not None and page.site_id != site_id:
                raise AppError(
                    "صفحه متعلق به سایت اعلام‌شده نیست",
                    error_code="page_site_mismatch",
                    status_code=422,
                )

    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

    async def create_hook_registration(self, tenant_id, body, *, actor=None):
        await self._scope(tenant_id, body.workspace_id, body.site_id, body.page_id)
        if await self.registrations.get_by_code(tenant_id, body.code) is not None:
            raise AppError(
                "کد ثبت هوک تکراری است",
                status_code=409,
                error_code="ai_hook_code_exists",
            )
        entity = ExperienceAiContentHookRegistration(
            tenant_id=tenant_id,
            workspace_id=body.workspace_id,
            site_id=body.site_id,
            page_id=body.page_id,
            code=body.code,
            name=body.name,
            task_kind=body.task_kind,
            external_ai_ref=body.external_ai_ref,
            status=body.status,
            config_json=validate_config(body.config_json),
            metadata_json=validate_metadata(body.metadata_json),
            created_by=_actor(actor),
            updated_by=_actor(actor),
        )
        await self.registrations.add(entity)
        return await self._finish(
            entity,
            "ai_content_hook_registration",
            AuditAction.CREATE,
            ExperienceEventType.AI_CONTENT_HOOK_REGISTRATION_CREATED,
            actor,
        )

    async def get_hook_registration(self, tenant_id, entity_id):
        return await self._get(
            self.registrations, tenant_id, entity_id, "ai content hook registration"
        )

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

    async def update_hook_registration(self, tenant_id, entity_id, body, *, actor=None):
        entity = await self._get(
            self.registrations, tenant_id, entity_id, "ai content hook registration"
        )
        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 "config_json" in data:
            data["config_json"] = validate_config(data["config_json"])
        if data.get("status") is not None:
            self.policies["ai_content_hook_registration"].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,
            "ai_content_hook_registration",
            AuditAction.UPDATE,
            ExperienceEventType.AI_CONTENT_HOOK_REGISTRATION_UPDATED,
            actor,
            data,
        )

    async def delete_hook_registration(self, tenant_id, entity_id, *, actor=None):
        entity = await self._get(
            self.registrations, tenant_id, entity_id, "ai content hook registration"
        )
        await self.registrations.soft_delete(entity, deleted_by=_actor(actor))
        entity.updated_by = _actor(actor)
        entity.version += 1
        return await self._finish(
            entity,
            "ai_content_hook_registration",
            AuditAction.DELETE,
            ExperienceEventType.AI_CONTENT_HOOK_REGISTRATION_DELETED,
            actor,
        )

    async def create_dispatch(self, tenant_id, body, *, actor=None):
        await self._scope(tenant_id, body.workspace_id, body.site_id, body.page_id)
        registration = await self.get_hook_registration(tenant_id, body.registration_id)
        if registration.workspace_id != body.workspace_id:
            raise AppError(
                "ثبت هوک متعلق به فضای کاری نیست",
                error_code="registration_workspace_mismatch",
                status_code=422,
            )
        entity = ExperienceAiContentDispatch(
            tenant_id=tenant_id,
            registration_id=body.registration_id,
            workspace_id=body.workspace_id,
            site_id=body.site_id,
            page_id=body.page_id,
            task=body.task,
            context_ref=body.context_ref,
            payload_json=validate_payload(body.payload_json),
            status=AiContentDispatchStatus.PENDING,
            created_by=_actor(actor),
            updated_by=_actor(actor),
        )
        await self.dispatches.add(entity)
        return await self._finish(
            entity,
            "ai_content_dispatch",
            AuditAction.CREATE,
            ExperienceEventType.AI_CONTENT_DISPATCH_CREATED,
            actor,
        )

    async def get_dispatch(self, tenant_id, entity_id):
        return await self._get(self.dispatches, tenant_id, entity_id, "ai content dispatch")

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

    async def update_dispatch(self, tenant_id, entity_id, body, *, actor=None):
        entity = await self._get(self.dispatches, tenant_id, entity_id, "ai content dispatch")
        ensure_optimistic_version(entity, body.version)
        data = body.model_dump(exclude_unset=True, exclude={"version"})
        if "payload_json" in data:
            data["payload_json"] = validate_payload(data["payload_json"])
        if data.get("status") is not None:
            self.policies["ai_content_dispatch"].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,
            "ai_content_dispatch",
            AuditAction.UPDATE,
            ExperienceEventType.AI_CONTENT_DISPATCH_UPDATED,
            actor,
            data,
        )

    async def delete_dispatch(self, tenant_id, entity_id, *, actor=None):
        entity = await self._get(self.dispatches, tenant_id, entity_id, "ai content dispatch")
        await self.dispatches.soft_delete(entity, deleted_by=_actor(actor))
        entity.updated_by = _actor(actor)
        entity.version += 1
        return await self._finish(
            entity,
            "ai_content_dispatch",
            AuditAction.DELETE,
            ExperienceEventType.AI_CONTENT_DISPATCH_DELETED,
            actor,
        )

    async def generate_dispatch(self, tenant_id, entity_id, *, actor=None):
        dispatch = await self.get_dispatch(tenant_id, entity_id)
        if dispatch.status != AiContentDispatchStatus.PENDING:
            raise AppError(
                "تولید محتوا قبلاً پردازش شده است",
                error_code="dispatch_not_pending",
                status_code=409,
            )
        registration = await self.get_hook_registration(tenant_id, dispatch.registration_id)
        if registration.status != AiContentHookStatus.ACTIVE:
            raise AppError(
                "ثبت هوک فعال نیست",
                error_code="registration_not_active",
                status_code=409,
            )
        try:
            response = await _MOCK_AI.generate_content(
                tenant_id=tenant_id,
                task=dispatch.task,
                context={
                    "task_kind": registration.task_kind.value,
                    "context_ref": dispatch.context_ref,
                    "payload": dispatch.payload_json or {},
                },
            )
            dispatch.status = AiContentDispatchStatus.GENERATED
            dispatch.generated_content_ref = response.get("generated_content_ref")
            dispatch.response_json = response
        except Exception as exc:  # noqa: BLE001 — mock boundary only
            dispatch.status = AiContentDispatchStatus.FAILED
            dispatch.response_json = {"ok": False, "reason": str(exc)}
        dispatch.updated_by = _actor(actor)
        dispatch.version += 1
        event = (
            ExperienceEventType.AI_CONTENT_DISPATCH_GENERATED
            if dispatch.status == AiContentDispatchStatus.GENERATED
            else ExperienceEventType.AI_CONTENT_DISPATCH_UPDATED
        )
        return await self._finish(
            dispatch,
            "ai_content_dispatch",
            AuditAction.UPDATE,
            event,
            actor,
            {"status": dispatch.status.value},
        )
