"""Template engine — versioning, approval, localization, preview, rendering."""
from __future__ import annotations

from datetime import datetime, timezone
from typing import Any
from uuid import UUID

from sqlalchemy.ext.asyncio import AsyncSession

from app.events.publisher import get_event_publisher
from app.events.types import CommunicationEventType
from app.models.foundation import MessageTemplate
from app.models.types import TemplateStatus
from app.repositories.foundation import MessageTemplateRepository
from app.services.audit_service import AuditService
from app.validators import (
    extract_template_variables,
    render_template,
    validate_template,
    validate_template_status_transition,
)
from shared.exceptions import NotFoundError


class TemplateService:
    def __init__(self, session: AsyncSession) -> None:
        self.session = session
        self.repo = MessageTemplateRepository(session)
        self.audit = AuditService(session)
        self.events = get_event_publisher()

    async def create(
        self, tenant_id: UUID, data: dict[str, Any], *, actor_id: str | None = None
    ) -> MessageTemplate:
        validate_template(data)
        variables = data.get("variables") or extract_template_variables(data["body"])
        existing = await self.repo.list_versions(
            tenant_id, data["template_key"], data.get("locale", "fa")
        )
        version = (existing[0].version + 1) if existing else 1
        entity = MessageTemplate(
            tenant_id=tenant_id,
            template_key=data["template_key"],
            name=data["name"],
            channel=data["channel"],
            locale=data.get("locale", "fa"),
            version=version,
            status=TemplateStatus.DRAFT.value,
            body=data["body"],
            subject=data.get("subject"),
            variables=variables,
            created_by=actor_id,
            updated_by=actor_id,
        )
        await self.repo.add(entity)
        await self.audit.log(
            tenant_id=tenant_id,
            entity_type="message_template",
            entity_id=entity.id,
            action="create",
            actor_id=actor_id,
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def get(self, tenant_id: UUID, template_id: UUID) -> MessageTemplate:
        entity = await self.repo.get(tenant_id, template_id)
        if entity is None:
            raise NotFoundError("Template not found")
        return entity

    async def list(self, tenant_id: UUID) -> list[MessageTemplate]:
        return list(await self.repo.list_by_tenant(tenant_id, limit=200))

    async def submit_for_approval(
        self, tenant_id: UUID, template_id: UUID, *, actor_id: str | None = None
    ) -> MessageTemplate:
        entity = await self.get(tenant_id, template_id)
        validate_template_status_transition(
            entity.status, TemplateStatus.PENDING_APPROVAL.value
        )
        entity.status = TemplateStatus.PENDING_APPROVAL.value
        entity.updated_by = actor_id
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def approve(
        self, tenant_id: UUID, template_id: UUID, *, actor_id: str | None = None
    ) -> MessageTemplate:
        entity = await self.get(tenant_id, template_id)
        validate_template_status_transition(entity.status, TemplateStatus.APPROVED.value)
        entity.status = TemplateStatus.APPROVED.value
        entity.approved_by = actor_id
        entity.approved_at = datetime.now(timezone.utc)
        entity.updated_by = actor_id
        self.events.publish(
            event_type=CommunicationEventType.TEMPLATE_APPROVED,
            aggregate_type="message_template",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"template_key": entity.template_key, "version": entity.version},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def reject(
        self,
        tenant_id: UUID,
        template_id: UUID,
        *,
        reason: str | None = None,
        actor_id: str | None = None,
    ) -> MessageTemplate:
        entity = await self.get(tenant_id, template_id)
        validate_template_status_transition(entity.status, TemplateStatus.REJECTED.value)
        entity.status = TemplateStatus.REJECTED.value
        entity.rejection_reason = reason
        entity.updated_by = actor_id
        self.events.publish(
            event_type=CommunicationEventType.TEMPLATE_REJECTED,
            aggregate_type="message_template",
            aggregate_id=entity.id,
            tenant_id=tenant_id,
            payload={"reason": reason},
        )
        await self.session.commit()
        await self.session.refresh(entity)
        return entity

    async def preview(
        self, tenant_id: UUID, template_id: UUID, variables: dict[str, Any]
    ) -> dict[str, str | None]:
        entity = await self.get(tenant_id, template_id)
        rendered = render_template(entity.body, variables)
        subject = (
            render_template(entity.subject, variables) if entity.subject else None
        )
        return {"rendered": rendered, "subject": subject}

    async def resolve_body(
        self,
        tenant_id: UUID,
        *,
        template_id: UUID | None = None,
        template_key: str | None = None,
        locale: str = "fa",
        variables: dict[str, Any] | None = None,
        fallback_body: str | None = None,
        fallback_subject: str | None = None,
    ) -> tuple[str, str | None, MessageTemplate | None]:
        template: MessageTemplate | None = None
        if template_id:
            template = await self.get(tenant_id, template_id)
        elif template_key:
            template = await self.repo.get_by_key(
                tenant_id, template_key, locale=locale, approved_only=True
            )
            if template is None:
                raise NotFoundError(f"Approved template not found: {template_key}")
        if template:
            body = render_template(template.body, variables)
            subject = (
                render_template(template.subject, variables) if template.subject else None
            )
            return body, subject, template
        if not fallback_body:
            raise NotFoundError("No template or body provided")
        return fallback_body, fallback_subject, None
