import uuid

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

from api.database import get_session
from api.models.review_contract import ReviewContract, ReviewReceipt
from api.schemas.review_contract import (
    ReviewAggregateRead,
    ReviewContractProject,
    ReviewContractRead,
    ReviewReceiptRead,
    ReviewReceiptSubmit,
)
from api.services.review_contracts import (
    active_contract,
    aggregate,
    digest_document,
    normalize_contract,
    utc_now,
    validate_receipt,
)

router = APIRouter(prefix="/review-contracts", tags=["review-contracts"])


@router.post(
    "/projections",
    response_model=ReviewContractRead,
    status_code=status.HTTP_201_CREATED,
)
async def project_contract(
    body: ReviewContractProject,
    session: AsyncSession = Depends(get_session),
) -> ReviewContract:
    document, contract_digest = normalize_contract(body.contract)
    existing = (
        await session.execute(
            select(ReviewContract).where(
                ReviewContract.contract_digest == contract_digest
            )
        )
    ).scalar_one_or_none()
    if existing:
        if (
            existing.source_repo != body.source.repo
            or existing.source_path != body.source.path
            or existing.source_revision != body.source.revision
        ):
            raise HTTPException(
                409, "contract digest is already projected from a different source"
            )
        return existing
    contract_key = document["contract_key"]
    await session.execute(
        update(ReviewContract)
        .where(
            ReviewContract.contract_key == contract_key, ReviewContract.active.is_(True)
        )
        .values(active=False)
    )
    contract = ReviewContract(
        contract_key=contract_key,
        schema_version="review-contract/v1",
        contract_digest=contract_digest,
        source_repo=body.source.repo,
        source_path=body.source.path,
        source_revision=body.source.revision,
        document=document,
        active=True,
        required_for_decision=body.required_for_decision,
        decision_id=body.decision_id,
        workplan_id=body.workplan_id,
        task_id=body.task_id,
        projected_at=utc_now(),
    )
    session.add(contract)
    try:
        await session.commit()
    except IntegrityError as exc:
        await session.rollback()
        raise HTTPException(
            409, "source revision is already projected with different content"
        ) from exc
    await session.refresh(contract)
    return contract


@router.get("/", response_model=list[ReviewContractRead])
async def list_contracts(
    contract_key: str | None = Query(None),
    active: bool | None = Query(None),
    decision_id: uuid.UUID | None = Query(None),
    session: AsyncSession = Depends(get_session),
) -> list[ReviewContract]:
    query = select(ReviewContract).order_by(ReviewContract.projected_at.desc())
    if contract_key:
        query = query.where(ReviewContract.contract_key == contract_key)
    if active is not None:
        query = query.where(ReviewContract.active == active)
    if decision_id:
        query = query.where(ReviewContract.decision_id == decision_id)
    return list((await session.execute(query)).scalars())


@router.get("/{contract_key}", response_model=ReviewContractRead)
async def get_active_contract(
    contract_key: str,
    session: AsyncSession = Depends(get_session),
) -> ReviewContract:
    return await active_contract(session, contract_key)


@router.get("/{contract_key}/aggregate", response_model=ReviewAggregateRead)
async def get_aggregate(
    contract_key: str,
    session: AsyncSession = Depends(get_session),
) -> ReviewAggregateRead:
    return await aggregate(session, await active_contract(session, contract_key))


@router.get("/{contract_key}/receipts", response_model=list[ReviewReceiptRead])
async def list_receipts(
    contract_key: str,
    include_stale: bool = Query(True),
    session: AsyncSession = Depends(get_session),
) -> list[ReviewReceipt]:
    contract = await active_contract(session, contract_key)
    query = (
        select(ReviewReceipt)
        .join(ReviewContract)
        .where(
            ReviewContract.contract_key == contract_key
            if include_stale
            else ReviewReceipt.contract_id == contract.id
        )
        .order_by(ReviewReceipt.submitted_at)
    )
    return list((await session.execute(query)).scalars())


@router.post(
    "/{contract_key}/receipts",
    response_model=ReviewReceiptRead,
    status_code=status.HTTP_201_CREATED,
)
async def submit_receipt(
    contract_key: str,
    body: ReviewReceiptSubmit,
    session: AsyncSession = Depends(get_session),
) -> ReviewReceipt:
    contract = await active_contract(session, contract_key)
    document = validate_receipt(contract, body)
    receipt_digest = digest_document(document)
    existing = (
        await session.execute(
            select(ReviewReceipt).where(ReviewReceipt.receipt_digest == receipt_digest)
        )
    ).scalar_one_or_none()
    if existing:
        return existing
    receipt = ReviewReceipt(
        contract_id=contract.id,
        owner_id=body.owner_id,
        actor=body.actor,
        disposition=body.disposition,
        contract_digest=body.contract_digest,
        receipt_digest=receipt_digest,
        artifact_hashes=body.artifact_hashes,
        assertion_ids=body.assertion_ids,
        checks=body.checks,
        note=body.note,
        source_repo=body.source.repo,
        source_path=body.source.path,
        source_revision=body.source.revision,
        submitted_at=utc_now(),
        document=document,
    )
    session.add(receipt)
    try:
        await session.commit()
    except IntegrityError as exc:
        await session.rollback()
        raise HTTPException(
            409, "receipt source revision already contains different evidence"
        ) from exc
    await session.refresh(receipt)
    return receipt
