import json
from datetime import datetime, timezone
from pathlib import Path

LOG_FILE = Path.home() / ".taskr" / "taskr.log"
METRICS_FILE = Path.home() / ".taskr" / "metrics.json"

_TRACKED_OUTCOMES = {
    "add": "added",
    "done": "completed",
    "delete": "deleted",
}


def _empty_cmd() -> dict:
    return {"count": 0, "total_ms": 0.0, "min_ms": None, "max_ms": None, "durations_ms": []}


def _load_metrics() -> dict:
    if not METRICS_FILE.exists():
        return {"commands": {}, "tasks": {"added": 0, "completed": 0, "deleted": 0}}
    data = json.loads(METRICS_FILE.read_text())
    # Migrate old schema where command values were plain ints
    for cmd, val in list(data.get("commands", {}).items()):
        if isinstance(val, int):
            data["commands"][cmd] = {**_empty_cmd(), "count": val}
    return data


def _save_metrics(metrics: dict) -> None:
    METRICS_FILE.parent.mkdir(parents=True, exist_ok=True)
    METRICS_FILE.write_text(json.dumps(metrics, indent=2))


def _percentile(sorted_vals: list[float], p: float) -> float:
    if not sorted_vals:
        return 0.0
    idx = (len(sorted_vals) - 1) * p / 100
    lo = int(idx)
    hi = min(lo + 1, len(sorted_vals) - 1)
    return sorted_vals[lo] + (sorted_vals[hi] - sorted_vals[lo]) * (idx - lo)


def record(command: str, outcome: str, duration_ms: float, **extra) -> None:
    """Append one structured JSON log line and update the metrics file."""
    entry = {
        "ts": datetime.now(timezone.utc).isoformat(),
        "command": command,
        "outcome": outcome,
        "duration_ms": round(duration_ms, 2),
        **extra,
    }

    LOG_FILE.parent.mkdir(parents=True, exist_ok=True)
    with LOG_FILE.open("a") as f:
        f.write(json.dumps(entry) + "\n")

    metrics = _load_metrics()

    if command not in metrics["commands"]:
        metrics["commands"][command] = _empty_cmd()

    cmd = metrics["commands"][command]
    d = round(duration_ms, 2)
    cmd["count"] += 1
    cmd["total_ms"] = round(cmd["total_ms"] + duration_ms, 4)
    cmd["durations_ms"].append(d)
    if cmd["min_ms"] is None or d < cmd["min_ms"]:
        cmd["min_ms"] = d
    if cmd["max_ms"] is None or d > cmd["max_ms"]:
        cmd["max_ms"] = d

    if outcome == "ok" and command in _TRACKED_OUTCOMES:
        metrics["tasks"][_TRACKED_OUTCOMES[command]] += 1

    _save_metrics(metrics)


def load_metrics() -> dict:
    return _load_metrics()


def duration_buckets(command: str, bins: int = 10) -> list[dict]:
    """Return evenly-spaced histogram buckets for a command's recorded durations."""
    metrics = _load_metrics()
    data = metrics.get("commands", {}).get(command)
    if not data:
        return []
    durations = data.get("durations_ms", [])
    if len(durations) < 2:
        return []
    lo, hi = min(durations), max(durations)
    if lo == hi:
        return [{"lo": lo, "hi": hi, "count": len(durations)}]
    step = (hi - lo) / bins
    result = []
    for i in range(bins):
        b_lo = lo + i * step
        b_hi = lo + (i + 1) * step
        if i < bins - 1:
            count = sum(1 for d in durations if b_lo <= d < b_hi)
        else:
            count = sum(1 for d in durations if b_lo <= d <= b_hi)
        result.append({"lo": round(b_lo, 2), "hi": round(b_hi, 2), "count": count})
    return result


def command_stats() -> dict[str, dict]:
    """Return computed duration stats (min, mean, p95, p99, max) per command."""
    metrics = _load_metrics()
    result = {}
    for cmd_name, data in metrics["commands"].items():
        count = data.get("count", 0)
        durations = sorted(data.get("durations_ms", []))
        if not durations:
            continue
        mean = data["total_ms"] / count if count else 0.0
        result[cmd_name] = {
            "count": count,
            "min_ms": data["min_ms"],
            "mean_ms": round(mean, 2),
            "p95_ms": round(_percentile(durations, 95), 2),
            "p99_ms": round(_percentile(durations, 99), 2),
            "max_ms": data["max_ms"],
        }
    return result
