import json
import pytest
from taskr import telemetry


@pytest.fixture(autouse=True)
def isolated_telemetry(tmp_path, monkeypatch):
    monkeypatch.setattr(telemetry, "LOG_FILE", tmp_path / "taskr.log")
    monkeypatch.setattr(telemetry, "METRICS_FILE", tmp_path / "metrics.json")


def _read_log(tmp_path) -> list[dict]:
    log_file = tmp_path / "taskr.log"
    if not log_file.exists():
        return []
    return [json.loads(line) for line in log_file.read_text().splitlines()]


# --- log entry ---

def test_record_writes_log_entry(tmp_path):
    telemetry.record("add", "ok", 1.5, task_id=1)
    entries = _read_log(tmp_path)
    assert len(entries) == 1
    entry = entries[0]
    assert entry["command"] == "add"
    assert entry["outcome"] == "ok"
    assert entry["duration_ms"] == 1.5
    assert entry["task_id"] == 1
    assert "ts" in entry


def test_record_appends_multiple_entries(tmp_path):
    telemetry.record("add", "ok", 1.0)
    telemetry.record("list", "ok", 0.5)
    telemetry.record("done", "ok", 1.2)
    assert len(_read_log(tmp_path)) == 3


def test_duration_rounding_in_log(tmp_path):
    telemetry.record("list", "ok", 1.23456789)
    entry = _read_log(tmp_path)[0]
    assert entry["duration_ms"] == 1.23


# --- command counts ---

def test_record_updates_command_counts():
    telemetry.record("add", "ok", 1.0)
    telemetry.record("add", "ok", 1.0)
    telemetry.record("list", "ok", 0.5)
    metrics = telemetry.load_metrics()
    assert metrics["commands"]["add"]["count"] == 2
    assert metrics["commands"]["list"]["count"] == 1


def test_not_found_outcome_does_not_increment_task_totals():
    telemetry.record("done", "not_found", 1.0)
    metrics = telemetry.load_metrics()
    assert metrics["tasks"]["completed"] == 0
    assert metrics["commands"]["done"]["count"] == 1


# --- task totals ---

def test_record_tracks_task_added():
    telemetry.record("add", "ok", 1.0)
    assert telemetry.load_metrics()["tasks"]["added"] == 1


def test_record_tracks_task_completed():
    telemetry.record("done", "ok", 1.0)
    assert telemetry.load_metrics()["tasks"]["completed"] == 1


def test_record_tracks_task_deleted():
    telemetry.record("delete", "ok", 1.0)
    assert telemetry.load_metrics()["tasks"]["deleted"] == 1


def test_load_metrics_returns_defaults_when_no_file():
    metrics = telemetry.load_metrics()
    assert metrics["commands"] == {}
    assert metrics["tasks"] == {"added": 0, "completed": 0, "deleted": 0}


# --- duration tracking ---

def test_record_tracks_min_max():
    telemetry.record("add", "ok", 10.0)
    telemetry.record("add", "ok", 2.0)
    telemetry.record("add", "ok", 7.0)
    cmd = telemetry.load_metrics()["commands"]["add"]
    assert cmd["min_ms"] == 2.0
    assert cmd["max_ms"] == 10.0


def test_record_accumulates_total_ms():
    telemetry.record("list", "ok", 3.0)
    telemetry.record("list", "ok", 5.0)
    cmd = telemetry.load_metrics()["commands"]["list"]
    assert cmd["total_ms"] == pytest.approx(8.0, abs=0.01)


def test_record_appends_durations():
    telemetry.record("add", "ok", 1.0)
    telemetry.record("add", "ok", 2.0)
    cmd = telemetry.load_metrics()["commands"]["add"]
    assert cmd["durations_ms"] == [1.0, 2.0]


# --- command_stats ---

def test_command_stats_mean():
    telemetry.record("add", "ok", 2.0)
    telemetry.record("add", "ok", 4.0)
    stats = telemetry.command_stats()
    assert stats["add"]["mean_ms"] == pytest.approx(3.0, abs=0.01)


def test_command_stats_min_max():
    for d in [5.0, 1.0, 9.0, 3.0]:
        telemetry.record("list", "ok", d)
    stats = telemetry.command_stats()
    assert stats["list"]["min_ms"] == 1.0
    assert stats["list"]["max_ms"] == 9.0


def test_command_stats_percentiles():
    # 10 values: 1..10 ms — p95 should be near 9.55, p99 near 9.91
    for i in range(1, 11):
        telemetry.record("done", "ok", float(i))
    stats = telemetry.command_stats()
    assert stats["done"]["p95_ms"] == pytest.approx(9.55, abs=0.1)
    assert stats["done"]["p99_ms"] == pytest.approx(9.91, abs=0.1)


def test_command_stats_empty_when_no_data():
    assert telemetry.command_stats() == {}


# --- duration_buckets ---

def test_duration_buckets_empty_when_no_data():
    assert telemetry.duration_buckets("add") == []


def test_duration_buckets_empty_when_single_sample():
    telemetry.record("add", "ok", 5.0)
    assert telemetry.duration_buckets("add") == []


def test_duration_buckets_single_bucket_when_all_same():
    telemetry.record("add", "ok", 5.0)
    telemetry.record("add", "ok", 5.0)
    buckets = telemetry.duration_buckets("add")
    assert len(buckets) == 1
    assert buckets[0]["count"] == 2


def test_duration_buckets_total_count_matches_sample_size():
    for d in [1.0, 5.0, 10.0, 15.0, 20.0, 25.0]:
        telemetry.record("list", "ok", d)
    buckets = telemetry.duration_buckets("list")
    assert sum(b["count"] for b in buckets) == 6


def test_duration_buckets_respects_bins_param():
    for d in [1.0, 5.0, 10.0, 15.0, 20.0, 25.0]:
        telemetry.record("done", "ok", d)
    assert len(telemetry.duration_buckets("done", bins=5)) == 5
    assert len(telemetry.duration_buckets("done", bins=10)) == 10


def test_duration_buckets_covers_full_range():
    for d in [2.0, 11.0, 20.0]:
        telemetry.record("add", "ok", d)
    buckets = telemetry.duration_buckets("add")
    assert buckets[0]["lo"] == pytest.approx(2.0, abs=0.1)
    assert buckets[-1]["hi"] == pytest.approx(20.0, abs=0.1)


def test_duration_buckets_max_value_included_in_last_bucket():
    for d in [1.0, 2.0, 3.0, 10.0]:
        telemetry.record("delete", "ok", d)
    buckets = telemetry.duration_buckets("delete")
    assert sum(b["count"] for b in buckets) == 4


# --- schema migration ---

def test_migrates_old_int_schema(tmp_path, monkeypatch):
    metrics_file = tmp_path / "metrics.json"
    # Write old-style metrics where commands were plain ints
    metrics_file.write_text(json.dumps({
        "commands": {"add": 5, "list": 3},
        "tasks": {"added": 5, "completed": 0, "deleted": 0},
    }))
    monkeypatch.setattr(telemetry, "METRICS_FILE", metrics_file)
    metrics = telemetry.load_metrics()
    assert metrics["commands"]["add"]["count"] == 5
    assert metrics["commands"]["list"]["count"] == 3
    assert metrics["commands"]["add"]["durations_ms"] == []
