"""Opt-out, scoped to one event (task 3.6, spec event-management, design D11).

A guest record belongs to exactly one event, so `do_not_contact` set here silences that
event and nothing else. That is a consent exposure the design accepted deliberately: a
person who unsubscribes from Walima keeps receiving Mehedi mail. The mitigation is honesty
in the copy — every response here names the event the opt-out covers, and the footer that
carries the link says "only".

GET previews, POST acts. The split is the same one cancellation uses and exists for the
same reason: a WhatsApp or email link scanner issues a GET, and a scanner must not be able
to opt somebody out of their own invitation.
"""

import logging

from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
from pydantic import BaseModel
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from app.db import get_session
from app.models import Invitation
from app.services import audit
from app.services.tokens import mask_token

logger = logging.getLogger(__name__)
router = APIRouter(tags=["invitation"])


class UnsubscribeRead(BaseModel):
    """What the confirmation page needs in order to state what it is about to do.

    The event titles are the point of this payload — a page that says "stop receiving
    emails" without naming which list is exactly the misleading copy the spec forbids.
    """

    guest_name: str
    event_title_en: str
    event_title_bn: str
    #: True when this record is already opted out, so the page can say so rather than
    #: offering a button that changes nothing.
    already_unsubscribed: bool


class UnsubscribeRequest(BaseModel):
    """Explicit confirmation, so a prefetch or scanner cannot carry the intent."""

    confirm: bool = False


def _not_found() -> HTTPException:
    return HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Invitation not found")


async def _load(session: AsyncSession, token: str) -> Invitation:
    invitation = await session.scalar(
        select(Invitation)
        .where(Invitation.token == token)
        .options(selectinload(Invitation.guest), selectinload(Invitation.event))
    )
    if invitation is None or invitation.guest is None:
        logger.info("unsubscribe miss for token %s", mask_token(token))
        raise _not_found()
    return invitation


def _client_ip(request: Request) -> str | None:
    fwd = request.headers.get("x-forwarded-for")
    return fwd.split(",")[0].strip() if fwd else (request.client.host if request.client else None)


@router.get("/unsubscribe/{token}", response_model=UnsubscribeRead)
async def read_unsubscribe(
    token: str,
    response: Response,
    session: AsyncSession = Depends(get_session),
) -> UnsubscribeRead:
    """Read-only preview. Unlike the invitation route this does not mark the invitation
    opened — following an unsubscribe link is not engagement with the invitation."""
    response.headers["Cache-Control"] = "private, no-store"
    response.headers["Referrer-Policy"] = "no-referrer"

    invitation = await _load(session, token)
    guest = invitation.guest
    assert guest is not None  # _load rejects a guestless invitation
    return UnsubscribeRead(
        guest_name=guest.full_name,
        event_title_en=invitation.event.title_en,
        event_title_bn=invitation.event.title_bn,
        already_unsubscribed=guest.do_not_contact,
    )


@router.post("/unsubscribe/{token}", response_model=UnsubscribeRead)
async def unsubscribe(
    token: str,
    payload: UnsubscribeRequest,
    request: Request,
    session: AsyncSession = Depends(get_session),
) -> UnsubscribeRead:
    """Suppress further messages for this event only.

    The invitation link keeps working (spec messaging FR-6.11): opting out of reminders is
    not the same as withdrawing from the wedding, and a guest who later wants to change
    their RSVP must still be able to.
    """
    if not payload.confirm:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Confirmation is required to unsubscribe",
        )

    invitation = await _load(session, token)
    guest = invitation.guest
    assert guest is not None

    if not guest.do_not_contact:
        guest.do_not_contact = True
        audit.record(
            session,
            action=audit.Actions.GUEST_UNSUBSCRIBE,
            entity_type="guest",
            entity_id=guest.id,
            after={
                "event_id": str(invitation.event_id),
                "event_title_en": invitation.event.title_en,
            },
            ip=_client_ip(request),
        )

    return UnsubscribeRead(
        guest_name=guest.full_name,
        event_title_en=invitation.event.title_en,
        event_title_bn=invitation.event.title_bn,
        already_unsubscribed=True,
    )
