import uuid
from datetime import datetime, timezone

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

from api.database import get_session
from api.models.agent_message import AgentMessage
from api.models.interface_change import InterfaceChange
from api.models.managed_repo import ManagedRepo
from api.models.progress_event import ProgressEvent
from api.schemas.interface_change import (
    InterfaceChangeCreate,
    InterfaceChangePatch,
    InterfaceChangeRead,
)

router = APIRouter(prefix="/interface-changes", tags=["interface-changes"])

_VALID_INTERFACE_TYPES = {"rest_api", "mcp_tool", "cli", "schema", "capability"}
_VALID_CHANGE_TYPES = {"breaking", "additive", "deprecation", "removal"}


@router.post("/", response_model=InterfaceChangeRead, status_code=status.HTTP_201_CREATED)
async def create_interface_change(
    body: InterfaceChangeCreate,
    session: AsyncSession = Depends(get_session),
) -> InterfaceChangeRead:
    if body.interface_type not in _VALID_INTERFACE_TYPES:
        raise HTTPException(status_code=422, detail=f"interface_type must be one of {sorted(_VALID_INTERFACE_TYPES)}")
    if body.change_type not in _VALID_CHANGE_TYPES:
        raise HTTPException(status_code=422, detail=f"change_type must be one of {sorted(_VALID_CHANGE_TYPES)}")

    repo = await _repo_by_slug(body.repo_slug, session)
    change = InterfaceChange(
        repo_id=repo.id,
        interface_type=body.interface_type,
        change_type=body.change_type,
        title=body.title,
        description=body.description,
        affected_paths=body.affected_paths,
        affected_repo_slugs=body.affected_repo_slugs,
        planned_for=body.planned_for,
        author=body.author,
        status="draft",
    )
    session.add(change)
    await session.commit()
    await session.refresh(change)
    return InterfaceChangeRead.from_orm_with_slug(change)


@router.get("/", response_model=list[InterfaceChangeRead])
async def list_interface_changes(
    repo_slug: str | None = Query(None),
    status: str | None = Query(None),
    change_type: str | None = Query(None),
    affected_repo: str | None = Query(None, description="Return changes that affect this repo slug"),
    session: AsyncSession = Depends(get_session),
) -> list[InterfaceChangeRead]:
    q = select(InterfaceChange).order_by(InterfaceChange.created_at.desc())
    if repo_slug:
        repo = await _repo_by_slug(repo_slug, session)
        q = q.where(InterfaceChange.repo_id == repo.id)
    if status:
        q = q.where(InterfaceChange.status == status)
    if change_type:
        q = q.where(InterfaceChange.change_type == change_type)
    if affected_repo:
        q = q.where(InterfaceChange.affected_repo_slugs.contains([affected_repo]))
    result = await session.execute(q)
    return [InterfaceChangeRead.from_orm_with_slug(c) for c in result.scalars().all()]


@router.get("/{change_id}", response_model=InterfaceChangeRead)
async def get_interface_change(
    change_id: uuid.UUID,
    session: AsyncSession = Depends(get_session),
) -> InterfaceChangeRead:
    change = await _get_or_404(change_id, session)
    return InterfaceChangeRead.from_orm_with_slug(change)


@router.patch("/{change_id}", response_model=InterfaceChangeRead)
async def patch_interface_change(
    change_id: uuid.UUID,
    body: InterfaceChangePatch,
    session: AsyncSession = Depends(get_session),
) -> InterfaceChangeRead:
    change = await _get_or_404(change_id, session)
    if change.status != "draft":
        raise HTTPException(
            status_code=409,
            detail=f"Cannot edit a change with status '{change.status}'. Only draft records are mutable.",
        )
    for field, value in body.model_dump(exclude_unset=True).items():
        setattr(change, field, value)
    await session.commit()
    await session.refresh(change)
    return InterfaceChangeRead.from_orm_with_slug(change)


@router.post("/{change_id}/publish", response_model=InterfaceChangeRead)
async def publish_interface_change(
    change_id: uuid.UUID,
    session: AsyncSession = Depends(get_session),
) -> InterfaceChangeRead:
    change = await _get_or_404(change_id, session)
    if change.status != "draft":
        raise HTTPException(
            status_code=409,
            detail=f"Cannot publish a change with status '{change.status}'. Must be 'draft'.",
        )
    now = datetime.now(tz=timezone.utc)
    change.status = "published"
    change.published_at = now

    # Send inbox notifications to agents of affected repos
    affected = change.affected_repo_slugs or []
    for slug in affected:
        paths_summary = ", ".join(change.affected_paths[:5]) if change.affected_paths else "see description"
        if len(change.affected_paths) > 5:
            paths_summary += f" (+{len(change.affected_paths) - 5} more)"
        msg = AgentMessage(
            from_agent=change.repo.slug,
            to_agent=slug,
            subject=f"[{change.change_type.upper()}] {change.title}",
            body=(
                f"**Interface change published by `{change.repo.slug}`**\n\n"
                f"- Type: `{change.interface_type}` / `{change.change_type}`\n"
                f"- Affected paths: {paths_summary}\n\n"
                f"{change.description}\n\n"
                f"Change ID: `{change.id}` — resolve with "
                f"`POST /interface-changes/{change.id}/resolve` once adapted."
            ),
        )
        session.add(msg)

    # Progress event on the originating repo
    session.add(ProgressEvent(
        event_type="milestone",
        summary=f"Interface change published: {change.title}",
        detail={
            "change_id": str(change.id),
            "change_type": change.change_type,
            "interface_type": change.interface_type,
            "affected_repos": affected,
            "notifications_sent": len(affected),
        },
        author=change.author,
    ))

    await session.commit()
    await session.refresh(change)
    return InterfaceChangeRead.from_orm_with_slug(change)


@router.post("/{change_id}/resolve", response_model=InterfaceChangeRead)
async def resolve_interface_change(
    change_id: uuid.UUID,
    session: AsyncSession = Depends(get_session),
) -> InterfaceChangeRead:
    change = await _get_or_404(change_id, session)
    if change.status != "published":
        raise HTTPException(
            status_code=409,
            detail=f"Cannot resolve a change with status '{change.status}'. Must be 'published'.",
        )
    change.status = "resolved"
    change.resolved_at = datetime.now(tz=timezone.utc)
    await session.commit()
    await session.refresh(change)
    return InterfaceChangeRead.from_orm_with_slug(change)


async def _repo_by_slug(slug: str, session: AsyncSession) -> ManagedRepo:
    result = await session.execute(select(ManagedRepo).where(ManagedRepo.slug == slug))
    repo = result.scalar_one_or_none()
    if repo is None:
        raise HTTPException(status_code=404, detail=f"Repo '{slug}' not found")
    return repo


async def _get_or_404(change_id: uuid.UUID, session: AsyncSession) -> InterfaceChange:
    result = await session.execute(
        select(InterfaceChange).where(InterfaceChange.id == change_id)
    )
    change = result.scalar_one_or_none()
    if change is None:
        raise HTTPException(status_code=404, detail=f"InterfaceChange '{change_id}' not found")
    return change
