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.flow_defs import assertion_result_to_dict, evaluate_transition, flow_result_to_dict
from api.models.contribution import Contribution, ContributionStatus, ContributionType
from api.schemas.contribution import ContributionCreate, ContributionRead, ContributionStatusPatch

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

@router.get("/", response_model=list[ContributionRead])
async def list_contributions(
    type: ContributionType | None = Query(None),
    status: ContributionStatus | None = Query(None),
    target_repo: str | None = Query(None),
    session: AsyncSession = Depends(get_session),
) -> list[Contribution]:
    q = select(Contribution).order_by(Contribution.created_at.desc())
    if type is not None:
        q = q.where(Contribution.type == type)
    if status is not None:
        q = q.where(Contribution.status == status)
    if target_repo:
        q = q.where(Contribution.target_repo == target_repo)
    result = await session.execute(q)
    return list(result.scalars().all())


@router.post("/", response_model=ContributionRead, status_code=status.HTTP_201_CREATED)
async def create_contribution(
    body: ContributionCreate,
    session: AsyncSession = Depends(get_session),
) -> Contribution:
    contrib = Contribution(
        type=body.type,
        target_org=body.target_org,
        target_repo=body.target_repo,
        slug=body.slug,
        title=body.title,
        body_path=body.body_path,
        related_topic_id=body.related_topic_id,
        related_workplan_id=body.related_workplan_id,
        notes=body.notes,
        status=ContributionStatus.draft,
    )
    session.add(contrib)
    await session.commit()
    await session.refresh(contrib)
    return contrib


@router.get("/{contribution_id}", response_model=ContributionRead)
async def get_contribution(
    contribution_id: uuid.UUID,
    session: AsyncSession = Depends(get_session),
) -> Contribution:
    return await _get_or_404(contribution_id, session)


@router.patch("/{contribution_id}/status", response_model=ContributionRead)
async def patch_contribution_status(
    contribution_id: uuid.UUID,
    body: ContributionStatusPatch,
    session: AsyncSession = Depends(get_session),
) -> Contribution:
    contrib = await _get_or_404(contribution_id, session)
    current = _status_value(contrib.status)
    target = _status_value(body.status)
    can_reach, failures, flow_result = evaluate_transition(
        "contribution",
        current,
        target,
    )
    if not can_reach:
        raise HTTPException(
            status_code=422,
            detail={
                "message": f"Cannot transition from '{current}' to '{target}'.",
                "current_workstation": current,
                "target_workstation": target,
                "blocking_assertions": [
                    assertion_result_to_dict(item) for item in failures
                ],
                "flow_result": flow_result_to_dict(flow_result),
            },
        )
    contrib.status = body.status
    if body.notes:
        contrib.notes = body.notes
    now = datetime.now(tz=timezone.utc)
    if body.status == ContributionStatus.submitted:
        contrib.submitted_at = now
    elif body.status in (
        ContributionStatus.accepted, ContributionStatus.rejected,
        ContributionStatus.merged, ContributionStatus.withdrawn,
    ):
        contrib.resolved_at = now
    await session.commit()
    await session.refresh(contrib)
    return contrib


@router.delete("/{contribution_id}", status_code=status.HTTP_204_NO_CONTENT)
async def withdraw_contribution(
    contribution_id: uuid.UUID,
    session: AsyncSession = Depends(get_session),
) -> None:
    """Soft-delete: sets status to 'withdrawn'."""
    contrib = await _get_or_404(contribution_id, session)
    if contrib.status == ContributionStatus.withdrawn:
        return  # idempotent
    if contrib.status in (ContributionStatus.merged, ContributionStatus.rejected):
        raise HTTPException(
            status_code=409,
            detail=f"Cannot withdraw a contribution with status '{contrib.status}'.",
        )
    contrib.status = ContributionStatus.withdrawn
    contrib.resolved_at = datetime.now(tz=timezone.utc)
    await session.commit()


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


def _status_value(status: ContributionStatus | str) -> str:
    return status.value if isinstance(status, ContributionStatus) else str(status)
