"""EnquiryService — governed workflow for website_enquiries."""
from __future__ import annotations

from datetime import datetime, timedelta, timezone
from typing import Optional
from uuid import UUID

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

from src.apps.public.models.website_enquiry import WebsiteEnquiry
from src.apps.site_admin.models.enquiry_audit_log import EnquiryAuditLog
from src.core.constants import ENQUIRY_MANUAL_TRANSITIONS, EnquiryTriggerSource
from src.core.exceptions import ConflictError, NotFoundError, ValidationError


def _now() -> datetime:
    return datetime.now(timezone.utc)


class EnquiryService:
    def __init__(self, db: AsyncSession) -> None:
        self.db = db

    # ── Queries ───────────────────────────────────────────────────────────────

    async def list(
        self,
        skip: int = 0,
        limit: int = 20,
        type_filter: Optional[str] = None,
        status_filter: Optional[str] = None,
        q: Optional[str] = None,
    ) -> tuple[list[WebsiteEnquiry], int]:
        conditions = []
        if type_filter:
            conditions.append(WebsiteEnquiry.type == type_filter)
        if status_filter:
            conditions.append(WebsiteEnquiry.status == status_filter)
        if q:
            term = f"%{q.lower()}%"
            conditions.append(
                or_(
                    func.lower(WebsiteEnquiry.name).like(term),
                    func.lower(WebsiteEnquiry.email).like(term),
                    func.lower(WebsiteEnquiry.organization).like(term),
                    func.lower(WebsiteEnquiry.message).like(term),
                )
            )

        base = select(WebsiteEnquiry)
        count_q = select(func.count(WebsiteEnquiry.id))
        if conditions:
            base = base.where(and_(*conditions))
            count_q = count_q.where(and_(*conditions))

        total = (await self.db.execute(count_q)).scalar_one()
        items = list(
            (await self.db.execute(
                base.order_by(WebsiteEnquiry.created_at.desc()).offset(skip).limit(limit)
            )).scalars().all()
        )
        return items, total

    async def get_by_id(self, enquiry_id: UUID) -> Optional[WebsiteEnquiry]:
        result = await self.db.execute(
            select(WebsiteEnquiry).where(WebsiteEnquiry.id == enquiry_id)
        )
        return result.scalar_one_or_none()

    async def count_new(self) -> int:
        result = await self.db.execute(
            select(func.count(WebsiteEnquiry.id)).where(WebsiteEnquiry.status == "new")
        )
        return result.scalar_one()

    # ── State Machine Guard ───────────────────────────────────────────────────

    def _assert_manual_transition(self, enquiry: WebsiteEnquiry, to_status: str) -> None:
        if enquiry.status == to_status:
            raise ConflictError(f"Enquiry is already in '{to_status}' status.")
        allowed = ENQUIRY_MANUAL_TRANSITIONS.get(enquiry.status, set())
        if to_status not in allowed:
            allowed_list = sorted(allowed) if allowed else ["none (terminal state)"]
            raise ValidationError(
                f"Cannot transition from '{enquiry.status}' to '{to_status}'. "
                f"Allowed: {allowed_list}."
            )

    # ── Audit ─────────────────────────────────────────────────────────────────

    async def _write_audit(
        self,
        enquiry: WebsiteEnquiry,
        to_status: str,
        trigger_source: str,
        actor_id: Optional[UUID] = None,
        actor_email: Optional[str] = None,
        notes: Optional[str] = None,
    ) -> None:
        log = EnquiryAuditLog(
            enquiry_id=enquiry.id,
            from_status=enquiry.status,
            to_status=to_status,
            trigger_source=trigger_source,
            actor_id=actor_id,
            actor_email=actor_email,
            notes=notes,
        )
        self.db.add(log)

    # ── Actions ───────────────────────────────────────────────────────────────

    async def mark_contacted_manual(
        self,
        enquiry_id: UUID,
        actor_id: UUID,
        actor_email: str,
    ) -> WebsiteEnquiry:
        enquiry = await self.get_by_id(enquiry_id)
        if not enquiry:
            raise NotFoundError("Enquiry not found")
        self._assert_manual_transition(enquiry, "contacted")

        await self._write_audit(enquiry, "contacted", EnquiryTriggerSource.MANUAL, actor_id, actor_email)
        enquiry.status = "contacted"
        enquiry.contacted_at = _now()
        enquiry.contact_method = "manual"
        await self.db.flush()
        await self.db.refresh(enquiry)
        return enquiry

    async def mark_contacted_email_reply(
        self,
        enquiry_id: UUID,
        actor_id: UUID,
        actor_email: str,
    ) -> WebsiteEnquiry:
        enquiry = await self.get_by_id(enquiry_id)
        if not enquiry:
            raise NotFoundError("Enquiry not found")
        self._assert_manual_transition(enquiry, "contacted")

        await self._write_audit(enquiry, "contacted", EnquiryTriggerSource.EMAIL_REPLY, actor_id, actor_email)
        enquiry.status = "contacted"
        enquiry.contacted_at = _now()
        enquiry.contact_method = "email_reply"
        await self.db.flush()
        await self.db.refresh(enquiry)
        return enquiry

    async def qualify(
        self,
        enquiry_id: UUID,
        actor_id: UUID,
        actor_email: str,
    ) -> WebsiteEnquiry:
        enquiry = await self.get_by_id(enquiry_id)
        if not enquiry:
            raise NotFoundError("Enquiry not found")
        self._assert_manual_transition(enquiry, "qualified")

        await self._write_audit(enquiry, "qualified", EnquiryTriggerSource.QUALIFY, actor_id, actor_email)
        now = _now()
        enquiry.status = "qualified"
        enquiry.qualified_at = now
        enquiry.qualified_email_sent_at = now
        await self.db.flush()
        await self.db.refresh(enquiry)
        return enquiry

    async def close_manual(
        self,
        enquiry_id: UUID,
        actor_id: UUID,
        actor_email: str,
    ) -> WebsiteEnquiry:
        """Manually close an enquiry as not interested. Only allowed from new/contacted."""
        enquiry = await self.get_by_id(enquiry_id)
        if not enquiry:
            raise NotFoundError("Enquiry not found")
        self._assert_manual_transition(enquiry, "closed")

        await self._write_audit(
            enquiry, "closed", EnquiryTriggerSource.MANUAL_CLOSE, actor_id, actor_email,
            notes="Marked not interested by admin.",
        )
        enquiry.status = "closed"
        await self.db.flush()
        await self.db.refresh(enquiry)
        return enquiry

    async def record_signup_completed(self, enquiry_id: UUID) -> Optional[WebsiteEnquiry]:
        """Record signup_completed_at without changing status. Idempotent."""
        enquiry = await self.get_by_id(enquiry_id)
        if not enquiry or enquiry.status == "closed":
            return None

        if enquiry.signup_completed_at is not None:
            return enquiry

        await self._write_audit(
            enquiry,
            enquiry.status,
            EnquiryTriggerSource.SIGNUP_COMPLETE,
            notes="Signup form submitted. signup_completed_at recorded. Status unchanged.",
        )
        enquiry.signup_completed_at = _now()
        await self.db.flush()
        await self.db.refresh(enquiry)
        return enquiry

    async def create(self, data: dict) -> WebsiteEnquiry:
        enquiry = WebsiteEnquiry(
            name=data["name"],
            email=data["email"],
            organization=data.get("organization"),
            role=data.get("role"),
            phone=data.get("phone"),
            cemetery_type=data.get("cemetery_type"),
            message=data.get("message"),
            type=data.get("type", "contact"),
            day=data.get("day"),
            time=data.get("time"),
            status="new",
            source="admin",
        )
        self.db.add(enquiry)
        await self.db.flush()
        await self.db.refresh(enquiry)
        return enquiry

    # ── System: Auto-Close ────────────────────────────────────────────────────

    async def auto_close_stale_qualified(self) -> list[UUID]:
        """Close enquiries where status=qualified, signup_completed_at IS NULL,
        and qualified_email_sent_at <= 30 days ago. Returns list of closed IDs."""
        cutoff = _now() - timedelta(days=30)

        stmt = select(WebsiteEnquiry).where(
            WebsiteEnquiry.status == "qualified",
            WebsiteEnquiry.signup_completed_at.is_(None),
            WebsiteEnquiry.qualified_email_sent_at <= cutoff,
        )
        result = await self.db.execute(stmt)
        enquiries = list(result.scalars().all())

        closed_ids = []
        for enquiry in enquiries:
            await self._write_audit(
                enquiry,
                "closed",
                EnquiryTriggerSource.SYSTEM_AUTO_CLOSE,
                notes=f"Auto-closed after 30 days without signup. Qualified at: {enquiry.qualified_email_sent_at}",
            )
            enquiry.status = "closed"
            enquiry.auto_closed_at = _now()
            closed_ids.append(enquiry.id)

        await self.db.flush()
        return closed_ids
