import uuid
from datetime import datetime, timezone

from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from api.database import get_session
from api.models.intake import Intake, IntakeNote, IntakeOutcome, IntakeStatus
from api.models.progress_event import ProgressEvent
from api.schemas.intake import (
    IntakeClose,
    IntakeCreate,
    IntakeNoteCreate,
    IntakeRead,
    IntakeRoute,
    IntakeUpdate,
)

router = APIRouter(prefix="/intakes", tags=["intakes"])

_ALLOWED_ROUTE_FROM = {IntakeStatus.open, IntakeStatus.vetted}
_ALLOWED_CLOSE_FROM = {IntakeStatus.open, IntakeStatus.vetted, IntakeStatus.routed}


def _reject_status(intake: Intake, allowed: set[IntakeStatus], action: str) -> None:
    if intake.status not in allowed:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=(
                f"Cannot {action} intake in status '{intake.status.value}'; "
                f"allowed from: {sorted(s.value for s in allowed)}"
            ),
        )


@router.get("/", response_model=list[IntakeRead])
async def list_intakes(
    topic_id: uuid.UUID | None = None,
    workplan_id: uuid.UUID | None = None,
    repo_id: uuid.UUID | None = None,
    status_: IntakeStatus | None = None,
    session: AsyncSession = Depends(get_session),
) -> list[Intake]:
    q = select(Intake)
    if topic_id:
        q = q.where(Intake.topic_id == topic_id)
    if workplan_id:
        q = q.where(Intake.workplan_id == workplan_id)
    if repo_id:
        q = q.where(Intake.repo_id == repo_id)
    if status_:
        q = q.where(Intake.status == status_)
    q = q.order_by(Intake.created_at)
    result = await session.execute(q)
    return list(result.scalars().all())


@router.post("/", response_model=IntakeRead, status_code=status.HTTP_201_CREATED)
async def create_intake(
    body: IntakeCreate,
    session: AsyncSession = Depends(get_session),
) -> Intake:
    intake = Intake(**body.model_dump())
    session.add(intake)
    await session.commit()
    await session.refresh(intake)
    return intake


@router.get("/{intake_id}", response_model=IntakeRead)
async def get_intake(
    intake_id: uuid.UUID,
    session: AsyncSession = Depends(get_session),
) -> Intake:
    intake = await session.get(Intake, intake_id)
    if intake is None:
        raise HTTPException(status_code=404, detail="Intake not found")
    return intake


@router.patch("/{intake_id}", response_model=IntakeRead)
async def update_intake(
    intake_id: uuid.UUID,
    body: IntakeUpdate,
    session: AsyncSession = Depends(get_session),
) -> Intake:
    intake = await session.get(Intake, intake_id)
    if intake is None:
        raise HTTPException(status_code=404, detail="Intake not found")
    for field, value in body.model_dump(exclude_unset=True).items():
        setattr(intake, field, value)
    await session.commit()
    await session.refresh(intake)
    return intake


@router.post("/{intake_id}/route", response_model=IntakeRead)
async def route_intake(
    intake_id: uuid.UUID,
    body: IntakeRoute,
    session: AsyncSession = Depends(get_session),
) -> Intake:
    """Move an intake into `routed` — eligible for the promotion transition."""
    intake = await session.get(Intake, intake_id)
    if intake is None:
        raise HTTPException(status_code=404, detail="Intake not found")
    _reject_status(intake, _ALLOWED_ROUTE_FROM, "route")

    intake.status = IntakeStatus.routed
    if body.routed_note:
        intake.routed_note = body.routed_note
    await session.commit()
    await session.refresh(intake)
    return intake


@router.post("/{intake_id}/close", response_model=IntakeRead)
async def close_intake(
    intake_id: uuid.UUID,
    body: IntakeClose,
    session: AsyncSession = Depends(get_session),
) -> Intake:
    """Close an intake with an outcome. `outcome=promoted` requires
    `promoted_to` (the canonical id of the record it became) — this is
    normally called by the promotion transition (CUST-WP-0061-T03), not by
    hand, but a manual close (declined/absorbed, or a promotion recorded
    after the fact) is supported directly."""
    intake = await session.get(Intake, intake_id)
    if intake is None:
        raise HTTPException(status_code=404, detail="Intake not found")
    _reject_status(intake, _ALLOWED_CLOSE_FROM, "close")

    intake.status = IntakeStatus.closed
    intake.outcome = body.outcome
    intake.closed_at = datetime.now(tz=timezone.utc)
    if body.promoted_to:
        intake.promoted_to = body.promoted_to
    await session.commit()
    await session.refresh(intake)

    event = ProgressEvent(
        topic_id=intake.topic_id,
        workplan_id=intake.workplan_id,
        event_type="intake_closed",
        summary=f"Intake closed ({body.outcome.value}): {intake.title}",
        detail={
            "intake_id": str(intake.id),
            "outcome": body.outcome.value,
            "promoted_to": body.promoted_to,
            "note": body.note,
        },
    )
    session.add(event)
    await session.commit()

    return intake


@router.post("/{intake_id}/notes", response_model=IntakeRead, status_code=status.HTTP_201_CREATED)
async def add_intake_note(
    intake_id: uuid.UUID,
    body: IntakeNoteCreate,
    session: AsyncSession = Depends(get_session),
) -> Intake:
    intake = await session.get(Intake, intake_id)
    if intake is None:
        raise HTTPException(status_code=404, detail="Intake not found")
    note = IntakeNote(intake_id=intake.id, author=body.author, content=body.content)
    session.add(note)
    await session.commit()
    await session.refresh(intake)
    return intake
