from __future__ import annotations

import json
from pathlib import Path
from typing import Any

import yaml

from one_run.constants import JSON_SCHEMA_VERSION
from one_run.event_manager import emit_event
from one_run.run_dir import RunPaths
from one_run.status_store import read_metadata, read_runtime, read_status, write_json_atomic


def append_event(
    paths: RunPaths,
    *,
    run_id: str,
    event: str,
    state: str | None = None,
    message: str | None = None,
) -> None:
    emit_event(
        paths,
        run_id=run_id,
        event=event,
        state=state,
        message=message,
    )


def build_summary_payload(paths: RunPaths, run_id: str) -> dict:
    metadata = read_metadata(paths.metadata) if paths.metadata.exists() else None
    status = read_status(paths.status).model_dump(mode="json") if paths.status.exists() else None
    runtime = read_runtime(paths.runtime).model_dump(mode="json") if paths.runtime.exists() else None
    return {
        "schema_version": JSON_SCHEMA_VERSION,
        "run_id": run_id,
        "metadata": metadata,
        "status": status,
        "runtime": runtime,
        "paths": {
            "run_dir": str(paths.run_dir),
            "config_snapshot": str(paths.config_snapshot),
            "metadata": str(paths.metadata),
            "status": str(paths.status),
            "runtime": str(paths.runtime),
            "events": str(paths.events),
            "stdout_log": str(paths.stdout_log),
            "stderr_log": str(paths.stderr_log),
            "artifacts_dir": str(paths.artifacts_dir),
            "metrics_dir": str(paths.metrics_dir),
        },
    }


def _extract_command(paths: RunPaths) -> str | None:
    if not paths.config_snapshot.exists():
        return None
    try:
        payload = yaml.safe_load(paths.config_snapshot.read_text(encoding="utf-8"))
    except yaml.YAMLError:
        return None
    if not isinstance(payload, dict):
        return None
    entrypoint = payload.get("entrypoint")
    if not isinstance(entrypoint, dict):
        return None
    command = entrypoint.get("command")
    return str(command) if isinstance(command, str) else None


def _artifact_paths(artifacts_dir: Path) -> list[str]:
    if not artifacts_dir.exists():
        return []
    files = [p for p in artifacts_dir.rglob("*") if p.is_file()]
    return sorted(str(p.relative_to(artifacts_dir)) for p in files)


def _flatten_scalars(value: Any, *, prefix: str = "") -> dict[str, str | int | float | bool]:
    if isinstance(value, dict):
        out: dict[str, str | int | float | bool] = {}
        for key, item in value.items():
            key_str = str(key)
            next_prefix = f"{prefix}.{key_str}" if prefix else key_str
            out.update(_flatten_scalars(item, prefix=next_prefix))
        return out
    if isinstance(value, (str, int, float, bool)):
        return {prefix: value} if prefix else {}
    return {}


def _metrics_key_values(metrics_dir: Path) -> dict[str, str | int | float | bool]:
    if not metrics_dir.exists():
        return {}
    json_files = sorted(p for p in metrics_dir.rglob("*.json") if p.is_file())
    if not json_files:
        return {}
    preferred = metrics_dir / "final.json"
    source = preferred if preferred in json_files else json_files[0]
    try:
        payload = json.loads(source.read_text(encoding="utf-8"))
    except json.JSONDecodeError:
        return {}
    return _flatten_scalars(payload)


def build_summary_min_payload(paths: RunPaths, run_id: str) -> dict:
    metadata = read_metadata(paths.metadata) if paths.metadata.exists() else {}
    status = read_status(paths.status).model_dump(mode="json") if paths.status.exists() else {}
    runtime = read_runtime(paths.runtime).model_dump(mode="json") if paths.runtime.exists() else {}
    return {
        "run_id": run_id,
        "state": status.get("state"),
        "exit_code": status.get("exit_code"),
        "start": status.get("started_at"),
        "end": status.get("ended_at"),
        "duration": runtime.get("duration_sec"),
        "command": _extract_command(paths),
        "tags": metadata.get("tags") or [],
        "artifacts": _artifact_paths(paths.artifacts_dir),
        "metrics_key_values": _metrics_key_values(paths.metrics_dir),
    }


def write_summary(paths: RunPaths, run_id: str) -> None:
    write_json_atomic(paths.summary, build_summary_payload(paths, run_id))
    write_json_atomic(paths.summary_min, build_summary_min_payload(paths, run_id))
