"""Workflows router: CRUD, manual run, and the signed inbound webhook (Section 8)."""

from __future__ import annotations

import uuid

from fastapi import APIRouter, Depends, HTTPException, Request, status
from sqlalchemy.orm import Session, sessionmaker

from legate.api.deps import Principal, require
from legate.api.schemas import (
    RunCreateResponse,
    RunOut,
    WorkflowCreate,
    WorkflowCreateResponse,
    WorkflowOut,
    WorkflowRunRequest,
    WorkflowUpdate,
)
from legate.audit.service import record as audit_record
from legate.billing import service as billing
from legate.config import get_settings
from legate.db.models.workspace import Workspace
from legate.db.session import get_db, get_session_factory
from legate.observability import metrics
from legate.observability.logging import get_logger
from legate.policy.rbac import Permission
from legate.repositories import workflows as workflows_repo
from legate.security.ratelimit import enforce_run_rate
from legate.workflow.service import launch_workflow_run, start_workflow_run
from legate.workflow.webhooks import mint_webhook_credentials, verify_signature

logger = get_logger(__name__)
router = APIRouter(tags=["workflows"])

_SIGNATURE_HEADER = "x-legate-signature"


@router.get("/workflows", response_model=list[WorkflowOut])
def list_workflows(
    principal: Principal = Depends(require(Permission.VIEW)),
    db: Session = Depends(get_db),
) -> list[WorkflowOut]:
    rows = workflows_repo.list_for_workspace(db, workspace_id=principal.workspace_id)
    return [WorkflowOut.model_validate(w) for w in rows]


@router.post(
    "/workflows", response_model=WorkflowCreateResponse, status_code=status.HTTP_201_CREATED
)
def create_workflow(
    body: WorkflowCreate,
    principal: Principal = Depends(require(Permission.MANAGE_BUILD)),
    db: Session = Depends(get_db),
) -> WorkflowCreateResponse:
    workspace = db.get(Workspace, principal.workspace_id)
    if workspace is not None:
        billing.enforce_resource(db, workspace=workspace, resource="workflows")

    token = secret = None
    if body.trigger_type == "webhook":
        token, secret = mint_webhook_credentials()

    workflow = workflows_repo.create(
        db,
        workspace_id=principal.workspace_id,
        name=body.name,
        description=body.description,
        definition_json=body.definition,
        trigger_type=body.trigger_type,
        trigger_config=body.trigger_config,
        webhook_token=token,
        webhook_secret=secret,
        created_by=principal.user.id if principal.user else None,
    )
    audit_record(
        db,
        workspace_id=principal.workspace_id,
        actor=principal.actor,
        action="workflow.create",
        target=str(workflow.id),
        metadata={"name": workflow.name, "trigger": workflow.trigger_type},
    )
    db.commit()
    db.refresh(workflow)

    webhook_url = None
    if token:
        webhook_url = f"{get_settings().api_base_url.rstrip('/')}/hooks/{token}"
    return WorkflowCreateResponse(
        workflow=WorkflowOut.model_validate(workflow),
        webhook_url=webhook_url,
        webhook_secret=secret,
    )


@router.get("/workflows/{workflow_id}", response_model=WorkflowOut)
def get_workflow(
    workflow_id: uuid.UUID,
    principal: Principal = Depends(require(Permission.VIEW)),
    db: Session = Depends(get_db),
) -> WorkflowOut:
    workflow = workflows_repo.get(db, workspace_id=principal.workspace_id, workflow_id=workflow_id)
    if workflow is None:
        raise HTTPException(status.HTTP_404_NOT_FOUND, detail="workflow not found")
    return WorkflowOut.model_validate(workflow)


@router.patch("/workflows/{workflow_id}", response_model=WorkflowOut)
def update_workflow(
    workflow_id: uuid.UUID,
    body: WorkflowUpdate,
    principal: Principal = Depends(require(Permission.MANAGE_BUILD)),
    db: Session = Depends(get_db),
) -> WorkflowOut:
    workflow = workflows_repo.get(db, workspace_id=principal.workspace_id, workflow_id=workflow_id)
    if workflow is None:
        raise HTTPException(status.HTTP_404_NOT_FOUND, detail="workflow not found")

    updates = body.model_dump(exclude_unset=True)
    if "definition" in updates and updates["definition"] is not None:
        workflow.definition_json = updates.pop("definition")
    for field_name, value in updates.items():
        setattr(workflow, field_name, value)

    audit_record(
        db,
        workspace_id=principal.workspace_id,
        actor=principal.actor,
        action="workflow.update",
        target=str(workflow.id),
    )
    db.commit()
    db.refresh(workflow)
    return WorkflowOut.model_validate(workflow)


@router.delete("/workflows/{workflow_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_workflow(
    workflow_id: uuid.UUID,
    principal: Principal = Depends(require(Permission.MANAGE_BUILD)),
    db: Session = Depends(get_db),
) -> None:
    workflow = workflows_repo.get(db, workspace_id=principal.workspace_id, workflow_id=workflow_id)
    if workflow is None:
        raise HTTPException(status.HTTP_404_NOT_FOUND, detail="workflow not found")
    workflows_repo.delete(db, workflow)
    audit_record(
        db,
        workspace_id=principal.workspace_id,
        actor=principal.actor,
        action="workflow.delete",
        target=str(workflow_id),
    )
    db.commit()


@router.post(
    "/workflows/{workflow_id}/run",
    response_model=RunCreateResponse,
    status_code=status.HTTP_201_CREATED,
)
async def run_workflow(
    workflow_id: uuid.UUID,
    body: WorkflowRunRequest,
    principal: Principal = Depends(require(Permission.RUN)),
    db: Session = Depends(get_db),
    session_factory: sessionmaker = Depends(get_session_factory),
) -> RunCreateResponse:
    workflow = workflows_repo.get(db, workspace_id=principal.workspace_id, workflow_id=workflow_id)
    if workflow is None:
        raise HTTPException(status.HTTP_404_NOT_FOUND, detail="workflow not found")

    enforce_run_rate(principal.workspace_id)
    workspace = db.get(Workspace, principal.workspace_id)
    if workspace is not None:
        billing.enforce_run_quota(db, workspace=workspace)

    run = start_workflow_run(
        db, workflow, body.input, created_by=principal.user.id if principal.user else None
    )
    billing.record_usage(db, workspace_id=principal.workspace_id, metric="runs")
    metrics.record_run_started("workflow")
    audit_record(
        db,
        workspace_id=principal.workspace_id,
        actor=principal.actor,
        action="workflow.run",
        target=str(run.id),
        metadata={"workflow_id": str(workflow.id)},
    )
    db.commit()
    run_id = run.id

    await launch_workflow_run(run_id, session_factory)

    db.refresh(run)
    return RunCreateResponse(run=RunOut.model_validate(run), stream_url=f"/runs/{run_id}/stream")


@router.post("/hooks/{webhook_token}", status_code=status.HTTP_202_ACCEPTED)
async def inbound_webhook(
    webhook_token: str,
    request: Request,
    db: Session = Depends(get_db),
    session_factory: sessionmaker = Depends(get_session_factory),
) -> dict:
    workflow = workflows_repo.get_by_webhook_token(db, token=webhook_token)
    if workflow is None or not workflow.is_active or workflow.trigger_type != "webhook":
        raise HTTPException(status.HTTP_404_NOT_FOUND, detail="webhook not found")

    raw_body = await request.body()
    signature = request.headers.get(_SIGNATURE_HEADER)
    if not verify_signature(workflow.webhook_secret or "", raw_body, signature):
        raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail="invalid webhook signature")

    try:
        parsed = await request.json() if raw_body else {}
    except (ValueError, TypeError):
        parsed = {}
    payload = {
        "body": parsed,
        "headers": {k: v for k, v in request.headers.items() if k.lower() != _SIGNATURE_HEADER},
    }

    run = start_workflow_run(db, workflow, payload)
    audit_record(
        db,
        workspace_id=workflow.workspace_id,
        actor="webhook",
        action="workflow.trigger.webhook",
        target=str(run.id),
        metadata={"workflow_id": str(workflow.id)},
    )
    db.commit()
    run_id = run.id

    await launch_workflow_run(run_id, session_factory)

    db.refresh(run)
    return {"run_id": str(run_id), "status": run.status.value}
