"""
═══════════════════════════════════════════════════════════════════════════════
 ARGUS — Tests unitaires pour sync_counters.py
 Couvre : SyncDelta, SyncReport, MemorySynchronizer
═══════════════════════════════════════════════════════════════════════════════
"""

import pytest
import yaml
from pathlib import Path

from sync_counters import SyncDelta, SyncReport, MemorySynchronizer


# ═══════════════════════════════════════════════════════════════════════════════
#  DATA CLASSES
# ═══════════════════════════════════════════════════════════════════════════════


class TestSyncDelta:
    """Tests pour la dataclass SyncDelta."""

    def test_is_mismatch_true(self):
        d = SyncDelta("loc", expected=10, actual=5, source="src")
        assert d.is_mismatch is True

    def test_is_mismatch_false(self):
        d = SyncDelta("loc", expected=10, actual=10, source="src")
        assert d.is_mismatch is False

    def test_is_mismatch_none_vs_none(self):
        d = SyncDelta("loc", expected=None, actual=None, source="src")
        assert d.is_mismatch is False

    def test_is_mismatch_none_vs_value(self):
        d = SyncDelta("loc", expected=None, actual="ANL-001", source="src")
        assert d.is_mismatch is True

    def test_to_dict(self):
        d = SyncDelta("a.b", expected=3, actual=2, source="file.yaml", severity="high")
        result = d.to_dict()
        assert result["location"] == "a.b"
        assert result["expected"] == 3
        assert result["actual"] == 2
        assert result["severity"] == "high"
        assert result["mismatch"] is True


class TestSyncReport:
    """Tests pour la dataclass SyncReport."""

    def test_empty_report(self):
        r = SyncReport()
        assert r.has_mismatches is False
        assert r.mismatch_count == 0

    def test_report_with_mismatches(self):
        r = SyncReport()
        r.deltas.append(SyncDelta("a", 1, 1, "s"))
        r.deltas.append(SyncDelta("b", 2, 99, "s"))
        assert r.has_mismatches is True
        assert r.mismatch_count == 1

    def test_report_to_dict(self):
        r = SyncReport()
        r.deltas.append(SyncDelta("x", 5, 3, "y"))
        d = r.to_dict()
        assert d["summary"]["status"] == "DESYNC"
        assert d["summary"]["mismatches"] == 1
        assert len(d["deltas"]) == 1

    def test_report_sync_status(self):
        r = SyncReport()
        r.deltas.append(SyncDelta("x", 5, 5, "y"))
        d = r.to_dict()
        assert d["summary"]["status"] == "SYNC"


# ═══════════════════════════════════════════════════════════════════════════════
#  MEMORYSYNCHRONIZER — Helpers
# ═══════════════════════════════════════════════════════════════════════════════


class TestSynchronizerHelpers:
    """Tests pour les méthodes utilitaires de MemorySynchronizer."""

    def test_load_yaml_valid(self, tmp_project):
        sync = MemorySynchronizer(str(tmp_project), verbose=False)
        data = sync.load_yaml(tmp_project / "memory" / "_index.yaml")
        assert data is not None
        assert "version" in data

    def test_load_yaml_missing(self, tmp_project):
        sync = MemorySynchronizer(str(tmp_project), verbose=False)
        data = sync.load_yaml(tmp_project / "absent.yaml")
        assert data is None
        assert len(sync.report.errors) == 1

    def test_count_yaml_items(self, tmp_project):
        sync = MemorySynchronizer(str(tmp_project), verbose=False)
        filepath = tmp_project / "memory" / "semantic" / "defect_patterns.yaml"
        count = sync.count_yaml_items(filepath, "patterns")
        assert count == 2

    def test_count_yaml_items_missing_key(self, tmp_project):
        sync = MemorySynchronizer(str(tmp_project), verbose=False)
        filepath = tmp_project / "memory" / "semantic" / "defect_patterns.yaml"
        count = sync.count_yaml_items(filepath, "nonexistent")
        assert count == 0

    def test_count_heuristics(self, tmp_project):
        """Doit compter les heuristics dans toutes les sections (hors metadata)."""
        sync = MemorySynchronizer(str(tmp_project), verbose=False)
        filepath = tmp_project / "memory" / "semantic" / "testing_heuristics.yaml"
        count = sync.count_heuristics(filepath)
        assert count == 3  # 2 dans selection + 1 dans coverage

    def test_count_heuristics_empty(self, tmp_project):
        empty_h = tmp_project / "memory" / "semantic" / "empty_h.yaml"
        empty_h.write_text(yaml.dump({"metadata": {}, "section": []}), encoding="utf-8")
        sync = MemorySynchronizer(str(tmp_project), verbose=False)
        assert sync.count_heuristics(empty_h) == 0

    def test_get_internal_metadata(self, tmp_project):
        sync = MemorySynchronizer(str(tmp_project), verbose=False)
        filepath = tmp_project / "memory" / "semantic" / "defect_patterns.yaml"
        total = sync.get_internal_metadata(filepath, "total_patterns")
        assert total == 2

    def test_count_files_in_dir_excludes_templates(self, tmp_project):
        sync = MemorySynchronizer(str(tmp_project), verbose=False)
        projects_dir = tmp_project / "memory" / "projects"
        count = sync.count_files_in_dir(projects_dir, exclude_templates=True)
        assert count == 1  # testproj.yaml only, not _template_project.yaml

    def test_count_files_in_dir_includes_templates(self, tmp_project):
        sync = MemorySynchronizer(str(tmp_project), verbose=False)
        projects_dir = tmp_project / "memory" / "projects"
        count = sync.count_files_in_dir(projects_dir, exclude_templates=False)
        assert count == 2

    def test_count_episodic_files_empty(self, tmp_project):
        sync = MemorySynchronizer(str(tmp_project), verbose=False)
        count, latest = sync.count_episodic_files("analyses")
        assert count == 0
        assert latest is None

    def test_count_episodic_files_with_data(self, tmp_project_with_episodes):
        sync = MemorySynchronizer(str(tmp_project_with_episodes), verbose=False)
        count, latest = sync.count_episodic_files("analyses")
        assert count == 1
        assert latest == "ANL-20260215-001"

    def test_count_projects(self, tmp_project):
        sync = MemorySynchronizer(str(tmp_project), verbose=False)
        count, ids = sync.count_projects()
        assert count == 1
        assert "testproj" in ids

    def test_get_nested(self, tmp_project):
        sync = MemorySynchronizer(str(tmp_project), verbose=False)
        data = {"a": {"b": {"c": 42}}}
        assert sync._get_nested(data, "a.b.c") == 42
        assert sync._get_nested(data, "a.b.d", "default") == "default"
        assert sync._get_nested(data, "x.y.z") is None

    def test_set_nested(self, tmp_project):
        sync = MemorySynchronizer(str(tmp_project), verbose=False)
        data = {"a": {"b": {"c": 0}}}
        sync._set_nested(data, "a.b.c", 99)
        assert data["a"]["b"]["c"] == 99

    def test_set_nested_creates_path(self, tmp_project):
        sync = MemorySynchronizer(str(tmp_project), verbose=False)
        data = {}
        sync._set_nested(data, "x.y.z", 7)
        assert data["x"]["y"]["z"] == 7


# ═══════════════════════════════════════════════════════════════════════════════
#  MEMORYSYNCHRONIZER — Analyse complète
# ═══════════════════════════════════════════════════════════════════════════════


class TestSynchronizerAnalyze:
    """Tests pour MemorySynchronizer.analyze()."""

    def test_synchronized_index(self, tmp_project):
        """Un index correctement synchronisé ne doit avoir aucun mismatch."""
        sync = MemorySynchronizer(str(tmp_project), verbose=False)
        report = sync.analyze()
        mismatches = [d for d in report.deltas if d.is_mismatch]
        assert len(mismatches) == 0, f"Mismatches: {[d.to_dict() for d in mismatches]}"

    def test_desynchronized_pattern_count(self, tmp_project):
        """Un compteur de patterns incorrect doit être détecté."""
        # Modifier l'index pour désynchroniser
        index_path = tmp_project / "memory" / "_index.yaml"
        index = yaml.safe_load(index_path.read_text(encoding="utf-8"))
        index["semantic"]["defect_patterns"]["count"] = 999
        index_path.write_text(
            yaml.dump(index, default_flow_style=False, allow_unicode=True, sort_keys=False),
            encoding="utf-8",
        )

        sync = MemorySynchronizer(str(tmp_project), verbose=False)
        report = sync.analyze()
        assert report.has_mismatches is True
        # Le mismatch doit concerner defect_patterns
        pattern_mismatches = [d for d in report.deltas if d.is_mismatch and "defect_patterns" in d.location]
        assert len(pattern_mismatches) >= 1

    def test_desynchronized_heuristic_count(self, tmp_project):
        """Un compteur d'heuristics incorrect doit être détecté."""
        index_path = tmp_project / "memory" / "_index.yaml"
        index = yaml.safe_load(index_path.read_text(encoding="utf-8"))
        index["semantic"]["testing_heuristics"]["count"] = 100
        index_path.write_text(
            yaml.dump(index, default_flow_style=False, allow_unicode=True, sort_keys=False),
            encoding="utf-8",
        )

        sync = MemorySynchronizer(str(tmp_project), verbose=False)
        report = sync.analyze()
        heu_mismatches = [d for d in report.deltas if d.is_mismatch and "testing_heuristics" in d.location]
        assert len(heu_mismatches) >= 1

    def test_desynchronized_episodic_count(self, tmp_project_with_episodes):
        """Un compteur épisodique incorrect doit être détecté."""
        index_path = tmp_project_with_episodes / "memory" / "_index.yaml"
        index = yaml.safe_load(index_path.read_text(encoding="utf-8"))
        index["episodic"]["analyses"]["count"] = 50
        index_path.write_text(
            yaml.dump(index, default_flow_style=False, allow_unicode=True, sort_keys=False),
            encoding="utf-8",
        )

        sync = MemorySynchronizer(str(tmp_project_with_episodes), verbose=False)
        report = sync.analyze()
        ep_mismatches = [d for d in report.deltas if d.is_mismatch and "analyses" in d.location]
        assert len(ep_mismatches) >= 1

    def test_desynchronized_project_count(self, tmp_project):
        """Un compteur de projets incorrect doit être détecté."""
        index_path = tmp_project / "memory" / "_index.yaml"
        index = yaml.safe_load(index_path.read_text(encoding="utf-8"))
        index["projects"]["count"] = 42
        index_path.write_text(
            yaml.dump(index, default_flow_style=False, allow_unicode=True, sort_keys=False),
            encoding="utf-8",
        )

        sync = MemorySynchronizer(str(tmp_project), verbose=False)
        report = sync.analyze()
        proj_mismatches = [d for d in report.deltas if d.is_mismatch and "projects" in d.location]
        assert len(proj_mismatches) >= 1

    def test_missing_index(self, tmp_project):
        """Un index absent doit générer une erreur."""
        (tmp_project / "memory" / "_index.yaml").unlink()
        sync = MemorySynchronizer(str(tmp_project), verbose=False)
        report = sync.analyze()
        assert len(report.errors) >= 1

    def test_analyze_with_episodes(self, tmp_project_with_episodes):
        """L'analyse avec des fichiers épisodiques fonctionne correctement."""
        sync = MemorySynchronizer(str(tmp_project_with_episodes), verbose=False)
        report = sync.analyze()
        mismatches = [d for d in report.deltas if d.is_mismatch]
        assert len(mismatches) == 0


# ═══════════════════════════════════════════════════════════════════════════════
#  MEMORYSYNCHRONIZER — Corrections
# ═══════════════════════════════════════════════════════════════════════════════


class TestSynchronizerFix:
    """Tests pour MemorySynchronizer.apply_fixes()."""

    def test_no_fix_needed(self, tmp_project):
        """Si tout est synchronisé, apply_fixes retourne True sans modifier."""
        sync = MemorySynchronizer(str(tmp_project), verbose=False)
        sync.analyze()
        assert sync.apply_fixes() is True

    def test_fix_corrects_mismatches(self, tmp_project):
        """apply_fixes doit corriger les compteurs désynchronisés."""
        # Désynchroniser
        index_path = tmp_project / "memory" / "_index.yaml"
        index = yaml.safe_load(index_path.read_text(encoding="utf-8"))
        index["semantic"]["defect_patterns"]["count"] = 999
        index["statistics"]["totals"]["patterns"] = 999
        index_path.write_text(
            yaml.dump(index, default_flow_style=False, allow_unicode=True, sort_keys=False),
            encoding="utf-8",
        )

        sync = MemorySynchronizer(str(tmp_project), verbose=False)
        report = sync.analyze()
        assert report.has_mismatches is True

        sync.apply_fixes()

        # Re-vérifier
        sync2 = MemorySynchronizer(str(tmp_project), verbose=False)
        report2 = sync2.analyze()
        pattern_mismatches = [
            d for d in report2.deltas
            if d.is_mismatch and "defect_patterns" in d.location
        ]
        assert len(pattern_mismatches) == 0


# ═══════════════════════════════════════════════════════════════════════════════
#  RAPPORT
# ═══════════════════════════════════════════════════════════════════════════════


class TestSynchronizerReport:
    """Tests pour print_report()."""

    def test_print_report_sync(self, tmp_project, capsys):
        sync = MemorySynchronizer(str(tmp_project), verbose=True)
        sync.analyze()
        sync.print_report()
        captured = capsys.readouterr()
        assert "synchronized" in captured.out.lower() or "SYNC" in captured.out

    def test_print_report_desync(self, tmp_project, capsys):
        index_path = tmp_project / "memory" / "_index.yaml"
        index = yaml.safe_load(index_path.read_text(encoding="utf-8"))
        index["semantic"]["defect_patterns"]["count"] = 999
        index_path.write_text(
            yaml.dump(index, default_flow_style=False, allow_unicode=True, sort_keys=False),
            encoding="utf-8",
        )

        sync = MemorySynchronizer(str(tmp_project), verbose=True)
        sync.analyze()
        sync.print_report()
        captured = capsys.readouterr()
        assert "DESYNCHRONIZED" in captured.out or "fix" in captured.out.lower()


# ═══════════════════════════════════════════════════════════════════════════════
#  EXTRACT ID FROM FILENAME
# ═══════════════════════════════════════════════════════════════════════════════


class TestExtractId:
    """Tests pour _extract_id_from_filename()."""

    def test_standard_analysis_filename(self, tmp_project):
        sync = MemorySynchronizer(str(tmp_project), verbose=False)
        result = sync._extract_id_from_filename(
            "2026-02-15_14-30_analysis_ANL-20260215-001.yaml", "analyses"
        )
        assert result == "ANL-20260215-001"

    def test_standard_decision_filename(self, tmp_project):
        sync = MemorySynchronizer(str(tmp_project), verbose=False)
        result = sync._extract_id_from_filename(
            "2026-03-01_10-00_decision_DEC-20260301-001.yaml", "decisions"
        )
        assert result == "DEC-20260301-001"

    def test_no_match(self, tmp_project):
        sync = MemorySynchronizer(str(tmp_project), verbose=False)
        result = sync._extract_id_from_filename("random_file.yaml", "analyses")
        assert result is None
