"""Additional edge-case coverage for output helpers."""

from __future__ import annotations

import contextlib
import os
import re
import time
from collections.abc import Iterator
from pathlib import Path
from typing import Any

import pytest

from llmdebug import _json as json
from llmdebug.config import SnapshotConfig
from llmdebug.output import get_latest_snapshot, write_bundle


def test_timestamp_format() -> None:
    from llmdebug.output import _timestamp

    stamp = _timestamp()
    assert re.fullmatch(r"\d{8}T\d{6}Z", stamp)


def test_directory_lock_timeout_branch(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    from llmdebug import output

    class _FakeLock:
        def __init__(self, _path: Path, timeout: float) -> None:
            self.timeout = timeout
            self.is_locked = False

        def acquire(self) -> None:
            raise output.Timeout(str(self.timeout))

        def release(self) -> None:
            self.is_locked = False

    monkeypatch.setattr(output, "FileLock", _FakeLock)

    with output._directory_lock(tmp_path, timeout=0.01) as acquired:
        assert acquired is False


def test_write_bundle_warns_when_lock_not_acquired(
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
    capsys: pytest.CaptureFixture[str],
) -> None:
    from llmdebug import output

    @contextlib.contextmanager
    def _never_lock(_out_dir: Path, _timeout: float) -> Iterator[bool]:
        yield False

    monkeypatch.setattr(output, "_directory_lock", _never_lock)
    cfg = SnapshotConfig(out_dir=str(tmp_path), output_format="json")
    path = write_bundle({"name": "lock-timeout", "frames": []}, cfg)

    assert path.exists()
    assert "lock timeout" in capsys.readouterr().err


@pytest.mark.skipif(os.name == "nt", reason="POSIX-only symlink behavior")
def test_update_latest_symlink_posix_success(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    from llmdebug import output

    target = tmp_path / "snapshot.json"
    target.write_text("{}", encoding="utf-8")

    monkeypatch.setattr(output.sys, "platform", "darwin")
    output._update_latest_symlink(tmp_path, target.name, ".json")

    latest = tmp_path / "latest.json"
    assert latest.exists()
    assert latest.is_symlink()
    assert latest.resolve() == target.resolve()


@pytest.mark.skipif(os.name == "nt", reason="POSIX-only symlink behavior")
def test_update_latest_symlink_posix_failure_cleans_temp(
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
    capsys: pytest.CaptureFixture[str],
) -> None:
    from llmdebug import output

    target = tmp_path / "snapshot.json"
    target.write_text("{}", encoding="utf-8")

    monkeypatch.setattr(output.sys, "platform", "darwin")
    real_rename = output.Path.rename

    def _failing_rename(self: Path, dst: Path) -> Path:
        if self.name.startswith(".latest.json.tmp."):
            raise OSError("rename failed")
        return real_rename(self, dst)

    monkeypatch.setattr(output.Path, "rename", _failing_rename)
    output._update_latest_symlink(tmp_path, target.name, ".json")

    assert "could not update latest.json" in capsys.readouterr().err
    assert list(tmp_path.glob(".latest.json.tmp.*")) == []


def test_snapshot_index_entry_handles_missing_path_stat() -> None:
    from llmdebug.output import _snapshot_index_entry

    missing = Path("/tmp/llmdebug-this-file-does-not-exist.json")
    payload = {
        "name": "entry-test",
        "exception": {"type": "ValueError", "message": "boom"},
        "frames": [{"file": "app.py", "line": 12}],
    }
    entry = _snapshot_index_entry(missing, payload)
    assert entry["mtime"] == 0.0
    assert entry["name"] == "entry-test"
    assert entry["exc_type"] == "ValueError"
    assert entry["file"] == "app.py"


def test_get_latest_snapshot_falls_back_from_invalid_toon_to_json(
    tmp_path: Path,
) -> None:
    latest_json = tmp_path / "latest.json"
    latest_json.write_text(
        json.dumps({"name": "json-fallback", "exception": {"type": "E"}, "frames": []}),
        encoding="utf-8",
    )
    latest_toon = tmp_path / "latest.toon"
    latest_toon.write_text("not-toon", encoding="utf-8")
    now = time.time()
    os.utime(latest_json, (now - 10, now - 10))
    os.utime(latest_toon, (now + 10, now + 10))

    snapshot = get_latest_snapshot(str(tmp_path))
    assert snapshot is not None
    assert snapshot["name"] == "json-fallback"


def test_get_latest_snapshot_warns_when_latest_json_is_not_dict(
    tmp_path: Path,
    capsys: pytest.CaptureFixture[str],
) -> None:
    (tmp_path / "latest.json").write_text(json.dumps(["bad", "payload"]), encoding="utf-8")

    assert get_latest_snapshot(str(tmp_path)) is None
    assert "contains list, expected dict" in capsys.readouterr().err


def test_get_latest_snapshot_warns_on_compact_expansion_failure(
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
    capsys: pytest.CaptureFixture[str],
) -> None:
    import llmdebug.compact_keys as compact_keys

    (tmp_path / "latest.json").write_text(
        json.dumps({"__llmdebug_compact__": 1, "_exc": {"_t": "ValueError"}}),
        encoding="utf-8",
    )

    monkeypatch.setattr(
        compact_keys, "expand_keys", lambda _data: (_ for _ in ()).throw(ValueError("bad"))
    )

    assert get_latest_snapshot(str(tmp_path)) is None
    assert "compact key expansion failed" in capsys.readouterr().err


def test_get_latest_snapshot_warns_on_corrupted_json(
    tmp_path: Path,
    capsys: pytest.CaptureFixture[str],
) -> None:
    (tmp_path / "latest.json").write_text("{not-json", encoding="utf-8")

    assert get_latest_snapshot(str(tmp_path)) is None
    assert "is corrupted" in capsys.readouterr().err


def test_cleanup_filesystem_snapshots_tolerates_disappearing_file(
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    from llmdebug import output

    for i in range(4):
        (tmp_path / f"snapshot_{i}.json").write_text(
            json.dumps({"name": f"snapshot_{i}", "frames": []}),
            encoding="utf-8",
        )

    real_stat = output.Path.stat
    vanished_once = {"value": False}

    def _flaky_stat(self: Path, *args: object, **kwargs: object):
        if self.name == "snapshot_1.json" and not vanished_once["value"]:
            vanished_once["value"] = True
            self.unlink(missing_ok=True)
            raise FileNotFoundError("snapshot disappeared")
        return real_stat(self, *args, **kwargs)

    monkeypatch.setattr(output.Path, "stat", _flaky_stat)
    output._cleanup_filesystem_snapshots(tmp_path, max_snapshots=2)
    monkeypatch.setattr(output.Path, "stat", real_stat)

    remaining = sorted(path.name for path in tmp_path.glob("snapshot_*.json"))
    assert remaining == ["snapshot_2.json", "snapshot_3.json"]
