# FILE: src/apps/public/services/plot_inquiry_service.py
from __future__ import annotations

import secrets
from datetime import datetime, timezone

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from src.apps.plots.models.plot import Plot
from src.apps.sales.models.contact_inquiry import ContactInquiry
from src.apps.sales.services.inquiry_service import InquiryService
from src.core.constants import PlotStatus
from src.core.exceptions import ValidationError

_ALPHANUM = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"  # unambiguous chars only


class PlotInquiryService:
    @staticmethod
    def _generate_reference_id() -> str:
        """Non-sequential reference so inquiry volume can't be enumerated (PRD A04/SEC-11)."""
        year = datetime.now(timezone.utc).year
        suffix = "".join(secrets.choice(_ALPHANUM) for _ in range(6))
        return f"INQ-PLOT-{year}-{suffix}"

    @staticmethod
    def _compose_message(body) -> str | None:
        """Fold the plot-inquiry-only fields (relationship, preferred contact
        time) into the message body. ContactInquiry — the shared Sales inbox
        table — has no dedicated columns for them, but staff still need to see
        them on the inbox card, so we surface them inline in the message."""
        parts: list[str] = []
        if body.relationship:
            parts.append(f"Relationship: {body.relationship}")
        if body.preferred_contact_time:
            parts.append(f"Preferred contact time: {body.preferred_contact_time}")
        if body.message:
            parts.append(body.message)
        return "\n".join(parts) if parts else None

    @staticmethod
    async def create(
        db: AsyncSession, tenant_id, body, ip_address=None, user_agent=None
    ) -> ContactInquiry:
        """Persist a public plot-availability inquiry.

        Writes to `contact_inquiries` (source_type="plot_availability") — the
        same table the tenant admin Sales > Inbox reads — so the enquiry is
        actually delivered to staff. (It previously wrote to an isolated
        `plot_inquiries` table that nothing read, so inquiries never appeared.)
        """
        plot = None
        if body.plot_id is not None:
            result = await db.execute(
                select(Plot)
                .options(selectinload(Plot.plot_type), selectinload(Plot.section))
                .where(Plot.id == body.plot_id, Plot.tenant_id == tenant_id)
            )
            plot = result.scalar_one_or_none()
            if plot is None:
                raise ValidationError("plot_id does not belong to this cemetery")

        # Enrich the inbox card with section + price straight off the plot when
        # we have it, so staff see them without the client having to send them.
        section_name = None
        listed_price = None
        if plot is not None:
            listed_price = plot.price_override
            if listed_price is None and plot.plot_type is not None:
                listed_price = plot.plot_type.default_price
            if plot.section is not None:
                section_name = plot.section.name

        inq = ContactInquiry(
            tenant_id=tenant_id,
            reference_id=PlotInquiryService._generate_reference_id(),
            source_type="plot_availability",
            sender_name=body.sender_name,
            sender_email=str(body.sender_email) if body.sender_email else None,
            sender_phone=body.sender_phone,
            message=PlotInquiryService._compose_message(body),
            plot_number=body.plot_ref,
            section_name=section_name,
            listed_price=listed_price,
            ip_address=ip_address,
            user_agent=user_agent,
            status="new",
        )
        db.add(inq)
        # A fresh enquiry puts the plot on hold while it's worked through the
        # pipeline — only from vacant, so it never demotes a plot that's already
        # reserved/occupied/on hold for another lead.
        if plot is not None and plot.status == PlotStatus.VACANT.value:
            plot.status = PlotStatus.HOLD.value
        await db.flush()
        # Bust the cached unread count so the Sales > Inbox badge updates.
        await InquiryService._invalidate_new_count_cache(tenant_id)
        return inq
