"""Tests for drift detection — hash-based staleness check."""

import pytest

from fliiq.runtime.knowledge.drift import (
    check_drift,
    check_drift_batch,
    compute_anchor_hash,
)
from fliiq.runtime.knowledge.models import Anchor, KnowledgeRecord, RecordType, SourceType


def _make_record(**overrides) -> KnowledgeRecord:
    defaults = dict(
        type=RecordType.DECISION,
        source=SourceType.HUMAN_IN_LOOP,
        context="test",
        decision="test",
        rationale="test",
    )
    defaults.update(overrides)
    return KnowledgeRecord(**defaults)


class TestComputeAnchorHash:
    def test_basic(self, tmp_path):
        f = tmp_path / "test.py"
        f.write_text("line1\nline2\nline3\nline4\n")
        h = compute_anchor_hash(str(f), 2, 3)
        assert isinstance(h, str)
        assert len(h) == 64  # SHA-256 hex

    def test_same_content_same_hash(self, tmp_path):
        f = tmp_path / "test.py"
        f.write_text("line1\nline2\nline3\n")
        h1 = compute_anchor_hash(str(f), 1, 3)
        h2 = compute_anchor_hash(str(f), 1, 3)
        assert h1 == h2

    def test_different_content_different_hash(self, tmp_path):
        f = tmp_path / "test.py"
        f.write_text("line1\nline2\nline3\n")
        h1 = compute_anchor_hash(str(f), 1, 2)
        h2 = compute_anchor_hash(str(f), 2, 3)
        assert h1 != h2

    def test_file_not_found(self, tmp_path):
        with pytest.raises(FileNotFoundError):
            compute_anchor_hash(str(tmp_path / "nonexistent.py"), 1, 3)


class TestCheckDrift:
    def test_unanchored_record(self):
        rec = _make_record()
        result = check_drift(rec)
        assert result["status"] == "unanchored"

    def test_anchor_without_hash(self):
        rec = _make_record(anchor=Anchor(file="test.py"))
        result = check_drift(rec)
        assert result["status"] == "unanchored"

    def test_missing_file(self, tmp_path):
        rec = _make_record(
            anchor=Anchor(file=str(tmp_path / "gone.py"), line_start=1, line_end=3, content_hash="abc123")
        )
        result = check_drift(rec)
        assert result["status"] == "missing"

    def test_current_no_drift(self, tmp_path):
        f = tmp_path / "test.py"
        f.write_text("line1\nline2\nline3\n")
        h = compute_anchor_hash(str(f), 1, 3)

        rec = _make_record(
            anchor=Anchor(file=str(f), line_start=1, line_end=3, content_hash=h)
        )
        result = check_drift(rec)
        assert result["status"] == "current"
        assert result["current_hash"] == h

    def test_drifted(self, tmp_path):
        f = tmp_path / "test.py"
        f.write_text("line1\nline2\nline3\n")
        old_hash = compute_anchor_hash(str(f), 1, 3)

        # Modify the file
        f.write_text("changed1\nchanged2\nchanged3\n")

        rec = _make_record(
            anchor=Anchor(file=str(f), line_start=1, line_end=3, content_hash=old_hash)
        )
        result = check_drift(rec)
        assert result["status"] == "drifted"
        assert result["current_hash"] != old_hash


class TestCheckDriftBatch:
    def test_batch(self, tmp_path):
        f = tmp_path / "test.py"
        f.write_text("line1\nline2\n")
        h = compute_anchor_hash(str(f), 1, 2)

        records = [
            _make_record(),  # unanchored
            _make_record(anchor=Anchor(file=str(f), line_start=1, line_end=2, content_hash=h)),  # current
            _make_record(  # missing
                anchor=Anchor(file=str(tmp_path / "gone.py"), line_start=1, line_end=1, content_hash="x"),
            ),
        ]

        results = check_drift_batch(records)
        assert len(results) == 3
        assert results[0]["status"] == "unanchored"
        assert results[1]["status"] == "current"
        assert results[2]["status"] == "missing"
        # Each result has record_id
        for r, result in zip(records, results):
            assert result["record_id"] == r.id
