#!/usr/bin/env python3
"""Simulate placement for AI-ish homelab workloads."""

from __future__ import annotations

import argparse
import csv
from pathlib import Path


REPO_ROOT = Path(__file__).resolve().parents[1]
SCHED_DIR = REPO_ROOT / "infra" / "ai-scheduler"
NODES_FILE = SCHED_DIR / "nodes.tsv"
WORKLOADS_FILE = SCHED_DIR / "workloads.tsv"


def read_tsv(path: Path) -> list[dict[str, str]]:
    with path.open(encoding="utf-8", newline="") as handle:
        return list(csv.DictReader(handle, delimiter="\t"))


def as_bool(value: str) -> bool:
    return value.strip().lower() in {"true", "yes", "1"}


def score_node(workload: dict[str, str], node: dict[str, str]) -> tuple[int, list[str], list[str]]:
    score = 100
    reasons: list[str] = []
    blockers: list[str] = []
    cpu_needed = int(workload["cpu_m"])
    mem_needed = int(workload["memory_mib"])
    cpu_have = int(node["cpu_m"])
    mem_have = int(node["memory_mib"])

    if cpu_have < cpu_needed:
        blockers.append(f"cpu {cpu_have}m < {cpu_needed}m")
    else:
        reasons.append("cpu fits")
        score += min((cpu_have - cpu_needed) // 250, 10)
    if mem_have < mem_needed:
        blockers.append(f"memory {mem_have}Mi < {mem_needed}Mi")
    else:
        reasons.append("memory fits")
        score += min((mem_have - mem_needed) // 512, 10)
    if as_bool(workload["requires_tailnet"]) and not as_bool(node["tailnet"]):
        blockers.append("tailnet required")
    elif as_bool(node["tailnet"]):
        reasons.append("tailnet available")
        score += 8
    if workload["prefer_arch"] != "any" and workload["prefer_arch"] != node["arch"]:
        score -= 15
        reasons.append(f"non-preferred arch {node['arch']}")
    else:
        reasons.append(f"arch {node['arch']} ok")
    if as_bool(node["control_plane"]) and not as_bool(workload["allow_control_plane"]):
        score -= 30
        reasons.append("control-plane penalty")
    if node["power_class"] == "low":
        score += 5
        reasons.append("low-power node")
    return score, reasons, blockers


def recommend(workload_name: str) -> int:
    workloads = {row["workload"]: row for row in read_tsv(WORKLOADS_FILE)}
    nodes = read_tsv(NODES_FILE)
    if workload_name not in workloads:
        print(f"Unknown workload: {workload_name}")
        print("Known workloads:")
        for name in workloads:
            print(f"  {name}")
        return 2
    workload = workloads[workload_name]
    rows = []
    for node in nodes:
        score, reasons, blockers = score_node(workload, node)
        rows.append((score, node["node"], reasons, blockers))
    rows.sort(reverse=True)

    print("AI Workload Scheduler")
    print("=====================")
    print(f"workload: {workload_name}")
    print()
    for score, node_name, reasons, blockers in rows:
        status = "blocked" if blockers else "candidate"
        print(f"{status:9} {node_name:18} score={score}")
        if blockers:
            print(f"          blockers: {', '.join(blockers)}")
        print(f"          reasons: {', '.join(reasons)}")
    best = next((row for row in rows if not row[3]), None)
    if best:
        print()
        print(f"Recommendation: {best[1]}")
        return 0
    print()
    print("Recommendation: add capacity or relax workload requirements")
    return 1


def simulate() -> int:
    failures = 0
    for workload in read_tsv(WORKLOADS_FILE):
        print()
        rc = recommend(workload["workload"])
        failures += 1 if rc else 0
    return 1 if failures else 0


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    subparsers = parser.add_subparsers(dest="command", required=True)
    subparsers.add_parser("simulate")
    rec = subparsers.add_parser("recommend")
    rec.add_argument("workload")
    args = parser.parse_args()
    if args.command == "simulate":
        return simulate()
    if args.command == "recommend":
        return recommend(args.workload)
    return 2


if __name__ == "__main__":
    raise SystemExit(main())
