"""Tests for cycle_result.json validation in loop_guard.

Covers validate_cycle_result(), _load_cycle_result(), and _is_issue_open().
"""

from __future__ import annotations

import json
import time
from pathlib import Path
from unittest.mock import patch

import pytest
import scripts.loop_guard as lg


@pytest.fixture(autouse=True)
def _isolate(tmp_path, monkeypatch):
    """Redirect loop_guard paths to tmp_path for isolation."""
    monkeypatch.setattr(lg, "CYCLE_RESULT_FILE", tmp_path / "cycle_result.json")
    monkeypatch.setattr(lg, "CYCLE_DURATION", 300)
    monkeypatch.setattr(lg, "GITEA_API", "http://test:3000/api/v1")
    monkeypatch.setattr(lg, "REPO_SLUG", "owner/repo")


def _write_cr(tmp_path, data: dict, age_seconds: float = 0) -> Path:
    """Write a cycle_result.json and optionally backdate it."""
    p = tmp_path / "cycle_result.json"
    p.write_text(json.dumps(data))
    if age_seconds:
        mtime = time.time() - age_seconds
        import os

        os.utime(p, (mtime, mtime))
    return p


# --- _load_cycle_result ---


def test_load_cycle_result_missing(tmp_path):
    assert lg._load_cycle_result() == {}


def test_load_cycle_result_valid(tmp_path):
    _write_cr(tmp_path, {"issue": 42, "type": "fix"})
    assert lg._load_cycle_result() == {"issue": 42, "type": "fix"}


def test_load_cycle_result_markdown_fenced(tmp_path):
    p = tmp_path / "cycle_result.json"
    p.write_text('```json\n{"issue": 99}\n```')
    assert lg._load_cycle_result() == {"issue": 99}


def test_load_cycle_result_malformed(tmp_path):
    p = tmp_path / "cycle_result.json"
    p.write_text("not json at all")
    assert lg._load_cycle_result() == {}


# --- _is_issue_open ---


def test_is_issue_open_true(monkeypatch):
    monkeypatch.setattr(lg, "_get_token", lambda: "tok")
    resp_data = json.dumps({"state": "open"}).encode()

    class FakeResp:
        def read(self):
            return resp_data

        def __enter__(self):
            return self

        def __exit__(self, *a):
            pass

    with patch("urllib.request.urlopen", return_value=FakeResp()):
        assert lg._is_issue_open(42) is True


def test_is_issue_open_closed(monkeypatch):
    monkeypatch.setattr(lg, "_get_token", lambda: "tok")
    resp_data = json.dumps({"state": "closed"}).encode()

    class FakeResp:
        def read(self):
            return resp_data

        def __enter__(self):
            return self

        def __exit__(self, *a):
            pass

    with patch("urllib.request.urlopen", return_value=FakeResp()):
        assert lg._is_issue_open(42) is False


def test_is_issue_open_no_token(monkeypatch):
    monkeypatch.setattr(lg, "_get_token", lambda: "")
    assert lg._is_issue_open(42) is None


def test_is_issue_open_api_error(monkeypatch):
    monkeypatch.setattr(lg, "_get_token", lambda: "tok")
    with patch("urllib.request.urlopen", side_effect=OSError("timeout")):
        assert lg._is_issue_open(42) is None


# --- validate_cycle_result ---


def test_validate_no_file(tmp_path):
    """No file → returns False, no crash."""
    assert lg.validate_cycle_result() is False


def test_validate_fresh_file_open_issue(tmp_path, monkeypatch):
    """Fresh file with open issue → kept."""
    _write_cr(tmp_path, {"issue": 10})
    monkeypatch.setattr(lg, "_is_issue_open", lambda n: True)
    assert lg.validate_cycle_result() is False
    assert (tmp_path / "cycle_result.json").exists()


def test_validate_stale_file_removed(tmp_path):
    """File older than 2× CYCLE_DURATION → removed."""
    _write_cr(tmp_path, {"issue": 10}, age_seconds=700)
    assert lg.validate_cycle_result() is True
    assert not (tmp_path / "cycle_result.json").exists()


def test_validate_fresh_file_closed_issue(tmp_path, monkeypatch):
    """Fresh file referencing closed issue → removed."""
    _write_cr(tmp_path, {"issue": 10})
    monkeypatch.setattr(lg, "_is_issue_open", lambda n: False)
    assert lg.validate_cycle_result() is True
    assert not (tmp_path / "cycle_result.json").exists()


def test_validate_api_failure_keeps_file(tmp_path, monkeypatch):
    """API failure → file kept (graceful degradation)."""
    _write_cr(tmp_path, {"issue": 10})
    monkeypatch.setattr(lg, "_is_issue_open", lambda n: None)
    assert lg.validate_cycle_result() is False
    assert (tmp_path / "cycle_result.json").exists()


def test_validate_no_issue_field(tmp_path):
    """File without issue field → kept (only age check applies)."""
    _write_cr(tmp_path, {"type": "fix"})
    assert lg.validate_cycle_result() is False
    assert (tmp_path / "cycle_result.json").exists()


def test_validate_stale_threshold_boundary(tmp_path, monkeypatch):
    """File just under threshold → kept (not stale yet)."""
    _write_cr(tmp_path, {"issue": 10}, age_seconds=599)
    monkeypatch.setattr(lg, "_is_issue_open", lambda n: True)
    assert lg.validate_cycle_result() is False
    assert (tmp_path / "cycle_result.json").exists()
