import uuid

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

from api.database import get_session
from api.models.task import Task, TaskStatus
from api.models.workplan_launch_request import WorkplanLaunchRequest
from api.models.workplan import Workplan
from api.models.workplan_dependency import WorkplanDependency
from api.schemas.execution import (
    ExecutionIntentRead,
    ExecutionIntentUpdate,
    ExecutionSemantics,
    LaunchRequestCreate,
    LaunchRequestRead,
    WorkplanQueueItem,
)
from api.services.execution_queue import (
    ACTIVITY_CORE_RESPONSIBILITIES,
    CONCURRENCY_MODES,
    EXECUTION_STATES,
    LAUNCH_MODES,
    STATE_HUB_RESPONSIBILITIES,
    execution_state_for_launch,
    queue_sort_key,
    workplan_blockers,
)
from api.routers.workstreams import _legacy_key, _meter_legacy_route
from api.workplan_status import CLOSED_WORKPLAN_STATUSES, normalize_workplan_status

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


@router.get("/semantics", response_model=ExecutionSemantics)
async def execution_semantics() -> ExecutionSemantics:
    return ExecutionSemantics(
        execution_states=EXECUTION_STATES,
        launch_modes=LAUNCH_MODES,
        concurrency_modes=CONCURRENCY_MODES,
        state_hub_responsibility=STATE_HUB_RESPONSIBILITIES,
        activity_core_responsibility=ACTIVITY_CORE_RESPONSIBILITIES,
    )


async def _update_execution_intent(
    *,
    workstream_id: uuid.UUID,
    body: ExecutionIntentUpdate,
    session: AsyncSession,
) -> ExecutionIntentRead:
    ws = await session.get(Workplan, workstream_id)
    if ws is None:
        raise HTTPException(status_code=404, detail="Workplan not found")

    for field, value in body.model_dump(exclude_unset=True).items():
        setattr(ws, field, value)
    await session.commit()
    await session.refresh(ws)
    return _intent_read(ws)


@router.patch("/workstreams/{workstream_id}/intent", response_model=ExecutionIntentRead)
async def update_execution_intent(
    request: Request,
    response: Response,
    workstream_id: uuid.UUID,
    body: ExecutionIntentUpdate,
    session: AsyncSession = Depends(get_session),
) -> ExecutionIntentRead:
    await _meter_legacy_route(
        session=session,
        request=request,
        response=response,
        interface_key=_legacy_key("PATCH", "/execution/workstreams/{workstream_id}/intent"),
        replacement_ref="/execution/workplans/{workplan_id}/intent",
    )
    return await _update_execution_intent(workstream_id=workstream_id, body=body, session=session)


@router.patch("/workplans/{workplan_id}/intent", response_model=ExecutionIntentRead)
async def update_workplan_execution_intent(
    workplan_id: uuid.UUID,
    body: ExecutionIntentUpdate,
    session: AsyncSession = Depends(get_session),
) -> ExecutionIntentRead:
    return await _update_execution_intent(workstream_id=workplan_id, body=body, session=session)


@router.get("/workplan-stack", response_model=list[WorkplanQueueItem])
async def workplan_stack(
    include_manual: bool = Query(True),
    include_blocked: bool = Query(True),
    session: AsyncSession = Depends(get_session),
) -> list[WorkplanQueueItem]:
    result = await session.execute(select(Workplan))
    workstreams = [
        ws for ws in result.scalars().all()
        if normalize_workplan_status(ws.status) not in CLOSED_WORKPLAN_STATUSES
    ]
    ws_by_id = {ws.id: ws for ws in workstreams}
    ws_status = {ws.id: normalize_workplan_status(ws.status) for ws in workstreams}

    dep_result = await session.execute(select(WorkplanDependency))
    ws_deps: dict[uuid.UUID, list[uuid.UUID]] = {}
    task_deps: dict[uuid.UUID, list[uuid.UUID]] = {}
    for dep in dep_result.scalars().all():
        if dep.to_workplan_id is not None:
            ws_deps.setdefault(dep.from_workplan_id, []).append(dep.to_workplan_id)
        if dep.to_task_id is not None:
            task_deps.setdefault(dep.from_workplan_id, []).append(dep.to_task_id)

    task_ids = [task_id for ids in task_deps.values() for task_id in ids]
    task_status: dict[uuid.UUID, str] = {}
    if task_ids:
        task_result = await session.execute(select(Task).where(Task.id.in_(task_ids)))
        task_status = {task.id: _task_status(task.status) for task in task_result.scalars().all()}

    items: list[WorkplanQueueItem] = []
    for ws in workstreams:
        if not include_manual and ws.execution_state == "manual":
            continue
        lifecycle_status = normalize_workplan_status(ws.status)
        blocked_ws = [
            blocker for blocker in workplan_blockers(ws.id, ws_deps, ws_status)
            if blocker in ws_by_id or blocker in ws_status
        ]
        blocked_tasks = [
            task_id for task_id in task_deps.get(ws.id, [])
            if task_status.get(task_id) not in {"done", "cancel"}
        ]
        eligible = lifecycle_status != "blocked" and not blocked_ws and not blocked_tasks
        if not include_blocked and not eligible:
            continue
        sort_key = queue_sort_key(ws, eligible=eligible)
        items.append(WorkplanQueueItem(
            workplan_id=ws.id,
            slug=ws.slug,
            title=ws.title,
            status=lifecycle_status,
            repo_id=ws.repo_id,
            planning_priority=ws.planning_priority,
            planning_order=ws.planning_order,
            execution_state=ws.execution_state,
            launch_mode=ws.launch_mode,
            concurrency_mode=ws.concurrency_mode,
            queue_rank=ws.queue_rank,
            execution_group=ws.execution_group,
            scheduled_for=ws.scheduled_for,
            eligible=eligible,
            blocked_by_workplan_ids=blocked_ws,
            blocked_by_task_ids=blocked_tasks,
            sort_key=sort_key,
        ))
    return sorted(items, key=lambda item: item.sort_key)


@router.post(
    "/launch-requests",
    response_model=LaunchRequestRead,
    status_code=status.HTTP_201_CREATED,
)
async def create_launch_request(
    body: LaunchRequestCreate,
    session: AsyncSession = Depends(get_session),
) -> WorkplanLaunchRequest:
    ws = await session.get(Workplan, body.workplan_id)
    if ws is None:
        raise HTTPException(status_code=404, detail="Workplan not found")

    launch_request = WorkplanLaunchRequest(
        workplan_id=ws.id,
        requested_by=body.requested_by,
        requested_actor=body.requested_actor,
        launch_mode=body.launch_mode,
        concurrency_mode=body.concurrency_mode,
        priority=body.priority or ws.planning_priority,
        repo_id=body.repo_id or ws.repo_id,
        branch_preference=body.branch_preference,
        immediate_pickup=body.immediate_pickup,
        notes=body.notes,
        request_metadata=body.request_metadata,
    )
    ws.launch_mode = body.launch_mode
    ws.concurrency_mode = body.concurrency_mode
    ws.execution_state = execution_state_for_launch(body.launch_mode, body.immediate_pickup)
    session.add(launch_request)
    await session.commit()
    await session.refresh(launch_request)
    return launch_request


@router.get("/launch-requests", response_model=list[LaunchRequestRead])
async def list_launch_requests(
    workstream_id: uuid.UUID | None = None,
    request_status: str | None = None,
    session: AsyncSession = Depends(get_session),
) -> list[WorkplanLaunchRequest]:
    q = select(WorkplanLaunchRequest).order_by(WorkplanLaunchRequest.created_at.desc())
    if workstream_id:
        q = q.where(WorkplanLaunchRequest.workplan_id == workstream_id)
    if request_status:
        q = q.where(WorkplanLaunchRequest.status == request_status)
    result = await session.execute(q)
    return list(result.scalars().all())


def _intent_read(ws: Workplan) -> ExecutionIntentRead:
    return ExecutionIntentRead(
        workplan_id=ws.id,
        execution_state=ws.execution_state,
        launch_mode=ws.launch_mode,
        concurrency_mode=ws.concurrency_mode,
        queue_rank=ws.queue_rank,
        execution_group=ws.execution_group,
        scheduled_for=ws.scheduled_for,
    )


def _task_status(status_value: TaskStatus | str) -> str:
    if hasattr(status_value, "value"):
        return status_value.value
    return str(status_value or "").strip().lower()
