"""Turning a run into rows and back.

Pure functions, no database. That is deliberate: this is where the bugs live —
a field added to the dataclass and forgotten here loses data silently on the
next restart — and keeping it separate means it can be tested exhaustively
without a server.

The round-trip test is the one that matters: anything that survives
``row_to_run(run_to_row(x))`` unchanged cannot be quietly dropped.
"""

from __future__ import annotations

from datetime import datetime
from typing import Any

from app.workflow.state import RunState, StepRecord, StepState, WorkflowRun


def run_to_row(run: WorkflowRun) -> dict[str, Any]:
    """The run's own columns, without its steps."""
    return {
        "id": run.id,
        "workflow": run.workflow,
        "state": run.state.value,
        "error": run.error,
        "context": run.context,
        "version": run.version,
        "created_at": run.created_at,
        "updated_at": run.updated_at,
        "archived_at": run.archived_at,
    }


def step_to_row(run_id: str, position: int, step: StepRecord) -> dict[str, Any]:
    return {
        "run_id": run_id,
        "position": position,
        "name": step.name,
        "tool": step.tool,
        "state": step.state.value,
        "attempts": step.attempts,
        "output": step.output,
        "proposed": step.proposed,
        "amendments": step.amendments,
        "approved": step.approved,
        "approved_by": step.approved_by,
        "error": step.error,
        "confidence": step.confidence,
        "duration_ms": step.duration_ms,
        "started_at": step.started_at,
        "finished_at": step.finished_at,
    }


def row_to_run(row: dict[str, Any], step_rows: list[dict[str, Any]]) -> WorkflowRun:
    """Rebuild a run from its rows.

    Step rows are ordered by ``position`` here rather than trusting the query,
    because "resume from the first step not done" depends on that order and a
    missing ORDER BY produces a run that restarts halfway through.
    """
    steps = [row_to_step(r) for r in sorted(step_rows, key=lambda r: r["position"])]

    return WorkflowRun(
        workflow=row["workflow"],
        context=dict(row["context"] or {}),
        steps=steps,
        state=RunState(row["state"]),
        error=row["error"],
        id=row["id"],
        created_at=_as_datetime(row["created_at"]),
        updated_at=_as_datetime(row["updated_at"]),
        version=int(row["version"]),
        archived_at=_as_datetime(row["archived_at"]) if row.get("archived_at") else None,
    )


def row_to_step(row: dict[str, Any]) -> StepRecord:
    return StepRecord(
        name=row["name"],
        tool=row["tool"],
        state=StepState(row["state"]),
        attempts=int(row["attempts"]),
        output=dict(row["output"] or {}),
        error=row["error"],
        confidence=row["confidence"],
        duration_ms=row["duration_ms"],
        started_at=_as_datetime(row["started_at"]) if row.get("started_at") else None,
        finished_at=_as_datetime(row["finished_at"]) if row.get("finished_at") else None,
        proposed=dict(row["proposed"] or {}),
        approved=bool(row["approved"]),
        approved_by=row["approved_by"],
        amendments=dict(row["amendments"] or {}),
    )


def _as_datetime(value: Any) -> datetime:
    """Accept what a driver returns, or an ISO string.

    psycopg gives datetimes; a JSON fixture or a different driver gives strings.
    Handling both keeps this testable without a database, which is the whole
    point of the module being pure.
    """
    if isinstance(value, datetime):
        return value

    return datetime.fromisoformat(str(value))
