"""Wedding-level settings, including the invitation messages (task 4.2, design D9).

There is one wedding per deployment, so these routes address it without an id — an id in
the path would imply a choice the system does not offer and would need authorization rules
that do not exist.
"""

import uuid
from datetime import datetime

from fastapi import APIRouter, Depends, HTTPException, Request, status
from pydantic import BaseModel, EmailStr, Field
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.db import get_session
from app.models import Wedding
from app.models.enums import Locale
from app.services import audit, greeting, scope
from app.services.auth import CurrentAdmin, require
from app.services.policy import Action

router = APIRouter(tags=["admin"])


class WeddingRead(BaseModel):
    id: uuid.UUID
    bride_name: str
    groom_name: str
    slug: str
    default_locale: Locale
    timezone: str
    host_contact_phone: str | None
    #: Where a guest's reply goes. Never the `From` address — see the model (D9).
    host_email: str | None
    #: `{locale: {invitation_type: message}}`. Only customised entries appear; anything
    #: absent renders its built-in default, so this map being empty is the normal state.
    invitation_messages: dict[str, dict[str, str]]
    created_at: datetime


class InvitationMessagesWrite(BaseModel):
    """The whole map, not a patch of one key.

    Sending the map entire means clearing a message is expressible — with a partial update,
    an absent key and an intentionally emptied one are the same request.
    """

    messages: dict[str, dict[str, str]] = Field(default_factory=dict)


async def _load(session: AsyncSession) -> Wedding:
    wedding = await session.scalar(select(Wedding).limit(1))
    if wedding is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="No wedding exists yet")
    return wedding


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("/admin/wedding", response_model=WeddingRead)
async def read_wedding(
    admin: CurrentAdmin = Depends(require(Action.VIEW_DASHBOARD)),
    session: AsyncSession = Depends(get_session),
) -> Wedding:
    return await _load(session)


class WeddingSettingsWrite(BaseModel):
    """The settings a host may change from the admin. Currently just the reply address.

    `EmailStr` rather than a loose string: this value goes into a `Reply-To` header, and a
    malformed one is rejected by the provider at send time — which would surface as a failed
    invitation hours later instead of as a validation error while they were typing it.
    """

    host_email: EmailStr | None = None


@router.patch("/admin/wedding", response_model=WeddingRead)
async def update_wedding_settings(
    payload: WeddingSettingsWrite,
    request: Request,
    admin: CurrentAdmin = Depends(require(Action.EDIT_CONTENT)),
    session: AsyncSession = Depends(get_session),
) -> Wedding:
    # System-wide: this address becomes the Reply-To on every event's invitations, not just
    # the caller's (task 8.7). A Host edits their own event instead.
    scope.require_system_wide(admin)

    wedding = await _load(session)
    before = wedding.host_email
    # An emptied field clears the reply address, after which replies go to the platform
    # sender again — the same state every wedding was in before this existed.
    wedding.host_email = str(payload.host_email) if payload.host_email else None

    audit.record(
        session,
        action=audit.Actions.WEDDING_UPDATE,
        admin_user_id=admin.id,
        actor_email=admin.email,
        entity_type="wedding",
        entity_id=wedding.id,
        before={"host_email": before},
        after={"host_email": wedding.host_email},
        ip=_client_ip(request),
    )
    return wedding


@router.put("/admin/wedding/invitation-messages", response_model=WeddingRead)
async def write_invitation_messages(
    payload: InvitationMessagesWrite,
    request: Request,
    admin: CurrentAdmin = Depends(require(Action.EDIT_CONTENT)),
    session: AsyncSession = Depends(get_session),
) -> Wedding:
    """Replace the customer-editable greeting sentences.

    Wedding-wide, so Super Admin only: this is the fallback every event inherits when it has
    no wording of its own. A Host has `PUT /admin/events/{id}` for their own greetings, which
    is the surface they actually want (task 8.7).

    Validation happens here rather than in the Pydantic model so the 422 carries the message
    the host needs — which field, how long it actually is, and why the limit exists.
    """
    scope.require_system_wide(admin)

    wedding = await _load(session)
    try:
        cleaned = greeting.validate_messages(payload.messages)
    except greeting.MessageTooLongError as exc:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)
        ) from exc

    before = dict(wedding.invitation_messages or {})
    wedding.invitation_messages = cleaned

    audit.record(
        session,
        action=audit.Actions.WEDDING_UPDATE,
        admin_user_id=admin.id,
        actor_email=admin.email,
        entity_type="wedding",
        entity_id=wedding.id,
        before={"invitation_messages": before},
        after={"invitation_messages": cleaned},
        ip=_client_ip(request),
    )
    return wedding
