"""Tests for knowledge graph validation (self-test against reality)."""
import pytest
from greymatter_core.db import SQLiteDatabase
from greymatter_core.knowledge import KnowledgeStore
from greymatter_core.validate import (
    KnowledgeValidator,
    ValidationReport,
    _extract_paths,
)


@pytest.fixture
def db():
    return SQLiteDatabase(":memory:")


@pytest.fixture
def knowledge(db):
    return KnowledgeStore(db)


@pytest.fixture
def validator(db):
    return KnowledgeValidator(db)


# --- Path extraction unit tests ---


def test_extract_paths_absolute():
    text = "The config lives at /etc/greymatter/config.yaml and logs at /var/log/gm.log"
    paths = _extract_paths(text)
    assert "/etc/greymatter/config.yaml" in paths
    assert "/var/log/gm.log" in paths


def test_extract_paths_home():
    text = "Database at ~/.greymatter/greymatter.db"
    paths = _extract_paths(text)
    assert any("greymatter/greymatter.db" in p for p in paths)


def test_extract_paths_no_paths():
    text = "This entry has no file paths at all"
    paths = _extract_paths(text)
    assert paths == []


def test_extract_paths_filters_dev_proc():
    text = "Device at /dev/null and process at /proc/1/status"
    paths = _extract_paths(text)
    assert not any("/dev/" in p for p in paths)
    assert not any("/proc/" in p for p in paths)


# --- validate_paths ---


def test_validate_paths_existing(knowledge, validator):
    """Entries referencing /tmp (always exists) should be valid."""
    knowledge.create(
        entry_type="reference",
        title="Temp dir",
        content="The temp directory is /tmp for scratch files",
    )
    report = validator.validate_paths()
    assert report.valid >= 1
    assert report.invalid == 0


def test_validate_paths_missing(knowledge, validator):
    """Entries referencing nonexistent paths should be flagged."""
    knowledge.create(
        entry_type="reference",
        title="Missing config",
        content="Critical config at /nonexistent/greymatter/config.yaml",
    )
    report = validator.validate_paths()
    assert report.invalid >= 1
    assert any("not found" in i.issue for i in report.issues)


def test_validate_paths_no_paths(knowledge, validator):
    """Entries without paths should be silently skipped."""
    knowledge.create(
        entry_type="expertise",
        title="SQLite tips",
        content="WAL mode is good for concurrent reads",
    )
    report = validator.validate_paths()
    assert report.valid == 0
    assert report.invalid == 0
    assert report.issues == []


# --- validate_stale ---


def test_validate_stale_finds_old_low_confidence(db, validator):
    """Old entries with low confidence should be flagged as stale."""
    db.execute(
        """INSERT INTO knowledge_entries
           (id, entry_type, title, content, confidence, created_at, updated_at)
           VALUES (?, ?, ?, ?, ?, ?, ?)""",
        ("kn-stale001", "observation", "Old fact", "Might be wrong",
         0.3, "2024-01-01 00:00:00", "2024-01-01 00:00:00"),
    )
    report = validator.validate_stale()
    assert report.stale >= 1
    assert any("kn-stale001" in i.entry_id for i in report.issues)


def test_validate_stale_ignores_recent(knowledge, validator):
    """Recently created low-confidence entries should not be stale."""
    knowledge.create(
        entry_type="observation",
        title="Fresh observation",
        content="Just noticed this",
    )
    report = validator.validate_stale()
    # The entry was just created, so it should not be stale
    assert not any("Fresh observation" in i.title for i in report.issues)


def test_validate_stale_ignores_high_confidence(db, validator):
    """Old entries with high confidence should not be flagged."""
    db.execute(
        """INSERT INTO knowledge_entries
           (id, entry_type, title, content, confidence, created_at, updated_at)
           VALUES (?, ?, ?, ?, ?, ?, ?)""",
        ("kn-highconf", "expertise", "Solid fact", "Verified many times",
         0.9, "2024-01-01 00:00:00", "2024-01-01 00:00:00"),
    )
    report = validator.validate_stale()
    assert not any("kn-highconf" in i.entry_id for i in report.issues)


# --- validate_all ---


def test_validate_all_combines(db, knowledge, validator):
    """validate_all merges results from all sub-validators."""
    # Add a missing-path entry
    knowledge.create(
        entry_type="reference",
        title="Bad path",
        content="File at /nonexistent/data/file.txt",
    )
    # Add a stale entry
    db.execute(
        """INSERT INTO knowledge_entries
           (id, entry_type, title, content, confidence, created_at, updated_at)
           VALUES (?, ?, ?, ?, ?, ?, ?)""",
        ("kn-combo01", "observation", "Old thing", "Old content",
         0.4, "2024-01-01 00:00:00", "2024-01-01 00:00:00"),
    )

    report = validator.validate_all()
    assert report.invalid >= 1
    assert report.stale >= 1
    assert len(report.issues) >= 2

    # to_dict works
    d = report.to_dict()
    assert "valid" in d
    assert "invalid" in d
    assert "stale" in d
    assert "issues" in d
    assert d["total_checked"] == d["valid"] + d["invalid"] + d["stale"]


# --- ValidationReport ---


def test_report_to_dict():
    report = ValidationReport(valid=5, invalid=2, stale=1)
    d = report.to_dict()
    assert d["valid"] == 5
    assert d["invalid"] == 2
    assert d["stale"] == 1
    assert d["total_checked"] == 8


def test_report_merge():
    r1 = ValidationReport(valid=3, invalid=1, stale=0)
    r2 = ValidationReport(valid=2, invalid=0, stale=2)
    r1.merge(r2)
    assert r1.valid == 5
    assert r1.invalid == 1
    assert r1.stale == 2


# --- Weekly hook ---


def test_should_run_weekly_first_time(validator):
    """First run should always return True."""
    assert validator.should_run_weekly() is True


def test_should_run_weekly_after_record(validator):
    """After recording a run, should_run_weekly returns False."""
    validator.record_run()
    assert validator.should_run_weekly() is False


def test_run_weekly_if_due(validator):
    """run_weekly_if_due returns a report on first call, None on second."""
    report = validator.run_weekly_if_due()
    assert report is not None
    assert isinstance(report, ValidationReport)

    # Second call should be None (just ran)
    report2 = validator.run_weekly_if_due()
    assert report2 is None
