#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
═══════════════════════════════════════════════════════════════════════════════
 ARGUS — Tests End-to-End (Sommet de la Pyramide)
 Parcours utilisateur complets sur le système mémoire cognitif
═══════════════════════════════════════════════════════════════════════════════

Scénarios E2E :
  1. Memory Lifecycle       — Création → épisodes → consolidation → decay → reflection
  2. Multi-Cycle Evolution  — Patterns émergent → se renforcent → déclinent → disparaissent
  3. Memory Recovery        — État dégradé → diagnostic → réparation → état sain
  4. CLI Workflow           — Parcours via subprocess comme un utilisateur réel
  5. Real Data Validation   — Tous les outils sur la mémoire production

Philosophie (persona.knowledge.testing_pyramid.e2e):
  - 10% des tests, caractéristiques: "Lents, fragiles, critiques"
  - Parcours utilisateur critiques uniquement
  - Données réalistes, pas minimales
"""

import pytest
import yaml
import json
import subprocess
import sys
import shutil
from pathlib import Path
from datetime import datetime

# ═══════════════════════════════════════════════════════════════════════════════
#  IMPORTS — Engines
# ═══════════════════════════════════════════════════════════════════════════════

from cognitive_cycle import CognitiveCycle, CycleReport
from consolidation import ConsolidationEngine
from decay import DecayEngine
from reflection import ReflectionEngine
from sync_counters import MemorySynchronizer
from validate_references import ReferenceValidator
from validate_yaml import YAMLValidator


# ═══════════════════════════════════════════════════════════════════════════════
#  HELPERS
# ═══════════════════════════════════════════════════════════════════════════════

def _write_yaml(path: Path, data: dict):
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(
        yaml.dump(data, default_flow_style=False, allow_unicode=True, sort_keys=False),
        encoding="utf-8",
    )


def _load_yaml(path: Path) -> dict:
    return yaml.safe_load(path.read_text(encoding="utf-8"))


# ═══════════════════════════════════════════════════════════════════════════════
#  FIXTURES — Realistic project states
# ═══════════════════════════════════════════════════════════════════════════════

@pytest.fixture
def realistic_project(tmp_path):
    """
    Projet Argus réaliste avec :
    - 3 patterns (null_safety, async, security) à divers niveaux de confidence
    - 4 heuristiques (2 selection, 1 coverage, 1 automation)
    - 1 épisode consolidé + 2 non consolidés (1 analyse + 1 incident)
    - 1 projet enregistré
    - 1 association relationnelle
    - Index synchronisé
    """
    root = tmp_path / "argus_e2e"
    root.mkdir()
    memory = root / "memory"

    # ── _index.yaml ──
    _write_yaml(memory / "_index.yaml", {
        "version": "1.0.0",
        "last_updated": "2026-02-20",
        "episodic": {
            "analyses": {"count": 2, "latest": "ANL-20260220-001",
                         "path": "memory/episodic/analyses/"},
            "decisions": {"count": 0, "latest": None,
                          "path": "memory/episodic/decisions/"},
            "incidents": {"count": 1, "latest": "INC-20260218-001",
                          "path": "memory/episodic/incidents/"},
            "executions": {"count": 0, "latest": None,
                           "path": "memory/episodic/executions/"},
        },
        "semantic": {
            "defect_patterns": {
                "count": 3,
                "path": "memory/semantic/defect_patterns.yaml",
                "categories": ["null_safety", "async", "security"],
            },
            "testing_heuristics": {
                "count": 4,
                "path": "memory/semantic/testing_heuristics.yaml",
                "categories": ["selection", "coverage", "automation"],
            },
        },
        "projects": {
            "count": 1,
            "path": "memory/projects/",
            "registered": [{"id": "webapp", "name": "WebApp",
                            "file": "memory/projects/webapp.yaml"}],
        },
        "relational": {
            "path": "memory/relational/associations.yaml",
            "total_associations": 1,
            "relation_types": ["file_defects"],
        },
        "statistics": {
            "memory_initialized": "2026-01-15",
            "totals": {"episodes": 3, "patterns": 3, "heuristics": 4,
                       "projects": 1, "associations": 1},
            "activity": {
                "last_consolidation": "2026-02-10",
                "last_episode_recorded": "2026-02-20",
            },
        },
        "sequences": {
            "analysis": 2, "decision": 0, "incident": 1,
            "execution": 0, "pattern": 3, "heuristic": 4,
        },
        "next_ids": {
            "analysis": "ANL-20260301-001",
            "decision": "DEC-20260301-001",
            "incident": "INC-20260301-001",
            "execution": "EXE-20260301-001",
        },
    })

    # ── defect_patterns.yaml ──
    _write_yaml(memory / "semantic" / "defect_patterns.yaml", {
        "version": "1.0.0",
        "patterns": [
            {
                "id": "PAT-NULL-001", "name": "Null Reference",
                "category": "null_safety", "confidence": 0.75,
                "occurrences": 2, "last_seen": "2026-02-10",
                "source_episodes": ["ANL-20260210-001"],
                "description": "Accessing null values without checks",
            },
            {
                "id": "PAT-ASYNC-001", "name": "Race Condition",
                "category": "async", "confidence": 0.45,
                "occurrences": 0, "last_seen": "2026-01-20",
                "source_episodes": [],
                "description": "Concurrent access to shared state",
            },
            {
                "id": "PAT-SEC-001", "name": "SQL Injection",
                "category": "security", "confidence": 0.90,
                "occurrences": 3, "last_seen": "2026-02-15",
                "source_episodes": ["ANL-20260210-001"],
                "description": "Unescaped user input in SQL queries",
                "severity": "critical",
            },
        ],
    })

    # ── testing_heuristics.yaml ──
    _write_yaml(memory / "semantic" / "testing_heuristics.yaml", {
        "version": "1.0.0",
        "test_selection": [
            {"id": "HEU-SEL-001", "name": "Test What Changed",
             "confidence": 0.85, "last_seen": "2026-02-15", "occurrences": 5},
            {"id": "HEU-SEL-002", "name": "Critical Path Priority",
             "confidence": 0.70, "last_seen": "2026-02-01", "occurrences": 2},
        ],
        "coverage": [
            {"id": "HEU-COV-001", "name": "Branch Coverage for Complex Logic",
             "confidence": 0.80, "last_seen": "2026-02-10", "occurrences": 3},
        ],
        "automation": [
            {"id": "HEU-AUT-001", "name": "Page Object for UI Tests",
             "confidence": 0.60, "last_seen": "2026-01-25", "occurrences": 1},
        ],
    })

    # ── Épisode 1: consolidé ──
    _write_yaml(
        memory / "episodic" / "analyses" / "2026-02-10_10-00_analysis_ANL-20260210-001.yaml",
        {
            "id": "ANL-20260210-001",
            "metadata": {
                "id": "ANL-20260210-001", "type": "analysis",
                "created_at": "2026-02-10T10:00:00Z",
            },
            "project": "webapp",
            "findings": [
                {"description": "Found PAT-NULL-001 in auth module",
                 "severity": "high", "category": "null_safety"},
            ],
            "consolidation": {"consolidated": True, "consolidated_at": "2026-02-10"},
        },
    )

    # ── Épisode 2: NON consolidé (analyse récente) ──
    _write_yaml(
        memory / "episodic" / "analyses" / "2026-02-20_14-00_analysis_ANL-20260220-001.yaml",
        {
            "id": "ANL-20260220-001",
            "metadata": {
                "id": "ANL-20260220-001", "type": "analysis",
                "created_at": "2026-02-20T14:00:00Z",
            },
            "project": "webapp",
            "findings": [
                {"description": "Critical: PAT-NULL-001 again in payment module",
                 "severity": "critical", "category": "null_safety"},
                {"description": "PAT-SEC-001 injection risk in search API",
                 "severity": "high", "category": "security"},
            ],
            "consolidation": {"consolidated": False},
        },
    )

    # ── Épisode 3: NON consolidé (incident) ──
    _write_yaml(
        memory / "episodic" / "incidents" / "2026-02-18_16-00_incident_INC-20260218-001.yaml",
        {
            "id": "INC-20260218-001",
            "metadata": {
                "id": "INC-20260218-001", "type": "incident",
                "created_at": "2026-02-18T16:00:00Z",
                "severity": "P2",
            },
            "project": "webapp",
            "severity": "P2",
            "summary": "Null pointer in auth service",
            "root_cause": {
                "matched_patterns": ["PAT-NULL-001"],
                "contributing_factors": [
                    {"factor": "Missing null check", "category": "code"},
                ],
            },
            "affected_components": ["authentication"],
            "consolidation": {"consolidated": False},
        },
    )

    # ── Dossiers vides ──
    (memory / "episodic" / "decisions").mkdir(parents=True, exist_ok=True)
    (memory / "episodic" / "executions").mkdir(parents=True, exist_ok=True)
    (memory / "working").mkdir(parents=True, exist_ok=True)

    # ── Projet ──
    _write_yaml(memory / "projects" / "webapp.yaml", {
        "identity": {"project_id": "webapp", "name": "WebApp",
                     "first_seen": "2026-01-15"},
    })

    # ── Associations ──
    _write_yaml(memory / "relational" / "associations.yaml", {
        "version": "1.0.0",
        "associations": [
            {"type": "file_defects", "source": "src/auth.ts",
             "target": "PAT-NULL-001"},
        ],
    })

    return root


@pytest.fixture
def degraded_project(tmp_path):
    """
    Projet avec mémoire dégradée :
    - Index désynchronisé (compteurs faux)
    - Pattern orphelin (référence episode inexistant)
    - Épisodes non consolidés
    - Pattern très ancien (decay important)
    - Aucune association relationnelle malgré épisodes
    """
    root = tmp_path / "argus_degraded"
    root.mkdir()
    memory = root / "memory"

    # Index volontairement désynchronisé
    _write_yaml(memory / "_index.yaml", {
        "version": "1.0.0",
        "last_updated": "2026-01-01",
        "episodic": {
            "analyses": {"count": 5, "latest": "ANL-20260220-003",
                         "path": "memory/episodic/analyses/"},  # faux: 2 réels
            "decisions": {"count": 3, "latest": "DEC-20260101-001",
                          "path": "memory/episodic/decisions/"},  # faux: 0 réels
            "incidents": {"count": 7, "latest": "INC-20260101-005",
                          "path": "memory/episodic/incidents/"},  # faux: 1 réel
            "executions": {"count": 4, "latest": "EXE-20260101-001",
                           "path": "memory/episodic/executions/"},  # faux: 0 réels
        },
        "semantic": {
            "defect_patterns": {
                "count": 1,  # faux: 2 réels
                "path": "memory/semantic/defect_patterns.yaml",
                "categories": ["null_safety"],
            },
            "testing_heuristics": {
                "count": 5,  # faux: 2 réels
                "path": "memory/semantic/testing_heuristics.yaml",
                "categories": ["selection"],
            },
        },
        "projects": {
            "count": 1,
            "path": "memory/projects/",
            "registered": [{"id": "webapp", "name": "WebApp",
                            "file": "memory/projects/webapp.yaml"}],
        },
        "relational": {
            "path": "memory/relational/associations.yaml",
            "total_associations": 0,
            "relation_types": [],
        },
        "statistics": {
            "memory_initialized": "2025-06-01",
            "totals": {"episodes": 5, "patterns": 1, "heuristics": 5,
                       "projects": 1, "associations": 0},
            "activity": {
                "last_consolidation": None,
                "last_episode_recorded": "2026-01-01",
            },
        },
        "sequences": {
            "analysis": 5, "decision": 0, "incident": 0,
            "execution": 0, "pattern": 1, "heuristic": 5,
        },
    })

    # Patterns: un ancien + un récent, orphan reference
    _write_yaml(memory / "semantic" / "defect_patterns.yaml", {
        "version": "1.0.0",
        "patterns": [
            {
                "id": "PAT-OLD-001", "name": "Legacy Bug",
                "category": "null_safety", "confidence": 0.30,
                "occurrences": 0, "last_seen": "2025-06-01",
                "source_episodes": ["ANL-20250601-999"],  # orphan!
                "description": "Old pattern from legacy code",
            },
            {
                "id": "PAT-NEW-001", "name": "Fresh Pattern",
                "category": "async", "confidence": 0.60,
                "occurrences": 1, "last_seen": "2026-02-15",
                "source_episodes": [],
                "description": "Recently discovered race condition",
            },
        ],
    })

    # Heuristics
    _write_yaml(memory / "semantic" / "testing_heuristics.yaml", {
        "version": "1.0.0",
        "test_selection": [
            {"id": "HEU-SEL-001", "name": "Test Changed",
             "confidence": 0.85, "last_seen": "2026-02-10", "occurrences": 3},
            {"id": "HEU-SEL-002", "name": "Critical Path",
             "confidence": 0.25, "last_seen": "2025-08-01", "occurrences": 0},
        ],
    })

    # Épisodes non consolidés
    _write_yaml(
        memory / "episodic" / "analyses" / "2026-02-15_10-00_analysis_ANL-20260215-001.yaml",
        {
            "id": "ANL-20260215-001",
            "metadata": {"id": "ANL-20260215-001", "type": "analysis",
                         "created_at": "2026-02-15T10:00:00Z"},
            "project": "webapp",
            "findings": [{"description": "PAT-NEW-001 race in worker queue",
                          "severity": "medium"}],
            "consolidation": {"consolidated": False},
        },
    )
    _write_yaml(
        memory / "episodic" / "analyses" / "2026-02-20_09-00_analysis_ANL-20260220-001.yaml",
        {
            "id": "ANL-20260220-001",
            "metadata": {"id": "ANL-20260220-001", "type": "analysis",
                         "created_at": "2026-02-20T09:00:00Z"},
            "project": "webapp",
            "findings": [{"description": "PAT-NEW-001 again in scheduler",
                          "severity": "high"}],
            "consolidation": {"consolidated": False},
        },
    )

    # Incident non consolidé
    _write_yaml(
        memory / "episodic" / "incidents" / "2026-02-17_12-00_incident_INC-20260217-001.yaml",
        {
            "id": "INC-20260217-001",
            "metadata": {"id": "INC-20260217-001", "type": "incident",
                         "created_at": "2026-02-17T12:00:00Z", "severity": "P2"},
            "severity": "P2",
            "project": "webapp",
            "root_cause": {
                "matched_patterns": ["PAT-NEW-001"],
                "contributing_factors": [{"factor": "No mutex", "category": "code"}],
            },
            "affected_components": ["scheduler", "worker"],
            "consolidation": {"consolidated": False},
        },
    )

    # Dossiers
    (memory / "episodic" / "decisions").mkdir(parents=True, exist_ok=True)
    (memory / "episodic" / "executions").mkdir(parents=True, exist_ok=True)
    (memory / "working").mkdir(parents=True, exist_ok=True)

    # Projet + associations vides
    _write_yaml(memory / "projects" / "webapp.yaml", {
        "identity": {"project_id": "webapp", "name": "WebApp",
                     "first_seen": "2025-06-01"},
    })
    _write_yaml(memory / "relational" / "associations.yaml", {
        "version": "1.0.0", "associations": [],
    })

    return root


# ═══════════════════════════════════════════════════════════════════════════════
#  E2E SCENARIO 1 — Memory Lifecycle Complet
#  Parcours: fresh project → episodes arrive → consolidate → decay → reflect
# ═══════════════════════════════════════════════════════════════════════════════

class TestMemoryLifecycle:
    """
    Scénario E2E: cycle de vie complet de la mémoire Argus.
    Vérifie que le système mémoire évolue correctement de bout en bout .
    """

    def test_full_lifecycle_consolidation_then_decay_then_reflection(
        self, realistic_project
    ):
        """
        Parcours complet:
        1. État initial: 2 épisodes non consolidés, patterns à diverses confidences
        2. consolidation --apply → patterns renforcés, épisodes marqués
        3. decay (date lointaine) → patterns anciens perdent confiance
        4. reflection → rapporte l'état final cohérent
        """
        root = realistic_project
        ref_date = datetime(2026, 3, 1)

        # ── PHASE 1: Vérifier l'état initial ──
        patterns_before = _load_yaml(root / "memory/semantic/defect_patterns.yaml")
        pat_null = next(p for p in patterns_before["patterns"] if p["id"] == "PAT-NULL-001")
        pat_async = next(p for p in patterns_before["patterns"] if p["id"] == "PAT-ASYNC-001")
        pat_sec = next(p for p in patterns_before["patterns"] if p["id"] == "PAT-SEC-001")
        assert pat_null["confidence"] == 0.75
        assert pat_async["confidence"] == 0.45
        assert pat_sec["confidence"] == 0.90

        # ── PHASE 2: Consolidation → renforce PAT-NULL-001 et PAT-SEC-001 ──
        cycle_consol = CognitiveCycle(str(root), verbose=False, reference_date=ref_date)
        report_consol = cycle_consol.run(phases=["consolidation"], apply=True)

        assert report_consol.consolidation_report is not None
        assert report_consol.consolidation_report.has_actions
        assert len(report_consol.consolidation_report.reinforcements) > 0

        # Vérifier les patterns après consolidation
        patterns_after_consol = _load_yaml(root / "memory/semantic/defect_patterns.yaml")
        pat_null_after = next(
            p for p in patterns_after_consol["patterns"] if p["id"] == "PAT-NULL-001"
        )
        pat_sec_after = next(
            p for p in patterns_after_consol["patterns"] if p["id"] == "PAT-SEC-001"
        )
        # PAT-NULL-001 renforcé par ANL-20260220-001 + INC-20260218-001
        assert pat_null_after["confidence"] > 0.75
        # PAT-SEC-001 renforcé par ANL-20260220-001
        assert pat_sec_after["confidence"] >= 0.90  # capped at 1.0

        # Épisodes marqués consolidés
        ep2 = _load_yaml(
            root / "memory/episodic/analyses/2026-02-20_14-00_analysis_ANL-20260220-001.yaml"
        )
        assert ep2["consolidation"]["consolidated"] is True

        inc1 = _load_yaml(
            root / "memory/episodic/incidents/2026-02-18_16-00_incident_INC-20260218-001.yaml"
        )
        assert inc1["consolidation"]["consolidated"] is True

        # ── PHASE 3: Re-scan consolidation → rien à faire ──
        cycle_rescan = CognitiveCycle(str(root), verbose=False, reference_date=ref_date)
        report_rescan = cycle_rescan.run(phases=["consolidation"])
        assert report_rescan.phases_executed[0].status == "ok"

        # ── PHASE 4: Decay (date future) → PAT-ASYNC-001 devrait décliner ──
        # PAT-ASYNC-001: last_seen=2026-01-20, ref=2026-03-01 → 40 jours (< grace 60)
        # Pour voir du decay, avancer plus loin dans le temps
        far_date = datetime(2026, 6, 1)  # 132 jours depuis last_seen de PAT-ASYNC-001
        cycle_decay = CognitiveCycle(str(root), verbose=False, reference_date=far_date)
        report_decay = cycle_decay.run(phases=["decay"], apply=True)

        assert report_decay.decay_report is not None

        patterns_after_decay = _load_yaml(root / "memory/semantic/defect_patterns.yaml")
        pat_async_after = next(
            p for p in patterns_after_decay["patterns"] if p["id"] == "PAT-ASYNC-001"
        )
        # PAT-ASYNC-001 devrait avoir décliné (132j > 60j grace, taux 0.05/mois)
        assert pat_async_after["confidence"] < 0.45

        # PAT-SEC-001 (security) devrait être protégé
        pat_sec_final = next(
            p for p in patterns_after_decay["patterns"] if p["id"] == "PAT-SEC-001"
        )
        # Security patterns are protected — confidence unchanged after decay
        assert pat_sec_final["confidence"] >= 0.90

        # ── PHASE 5: Reflection → diagnostic final ──
        cycle_reflect = CognitiveCycle(str(root), verbose=False, reference_date=far_date)
        report_reflect = cycle_reflect.run(phases=["reflection"])

        assert report_reflect.reflection_report is not None
        assert report_reflect.reflection_report.stats is not None
        assert report_reflect.reflection_report.stats.patterns_count == 3

    def test_lifecycle_apply_full_cycle_idempotent(self, realistic_project):
        """Exécuter --apply deux fois de suite: la deuxième fois ne devrait rien changer."""
        root = realistic_project
        ref = datetime(2026, 3, 1)

        # Premier cycle complet apply
        cycle1 = CognitiveCycle(str(root), verbose=False, reference_date=ref)
        cycle1.run(apply=True)

        # Capturer état après cycle 1
        patterns_after_1 = _load_yaml(root / "memory/semantic/defect_patterns.yaml")

        # Deuxième cycle complet apply → idempotent
        cycle2 = CognitiveCycle(str(root), verbose=False, reference_date=ref)
        report2 = cycle2.run(apply=True)

        patterns_after_2 = _load_yaml(root / "memory/semantic/defect_patterns.yaml")

        # Les confidences ne devraient pas changer
        for p1, p2 in zip(patterns_after_1["patterns"], patterns_after_2["patterns"]):
            assert p1["confidence"] == p2["confidence"], (
                f"{p1['id']}: confidence changed from {p1['confidence']} to {p2['confidence']}"
            )

        # Consolidation devrait ne rien trouver
        assert report2.consolidation_report is not None
        assert not report2.consolidation_report.has_actions


# ═══════════════════════════════════════════════════════════════════════════════
#  E2E SCENARIO 2 — Multi-Cycle Evolution
#  Observe l'évolution de patterns sur plusieurs cycles cognitifs simulés
# ═══════════════════════════════════════════════════════════════════════════════

class TestMultiCycleEvolution:
    """
    Scénario E2E: évolution multi-cycles.
    Simule le passage du temps avec des épisodes arrivant progressivement.
    Un pattern doit émerger, se renforcer, puis décliner s'il n'est plus observé.
    """

    def test_pattern_reinforcement_over_multiple_cycles(self, realistic_project):
        """
        Cycle 1: Consolider les épisodes existants → PAT-NULL-001 renforcé
        Cycle 2: Ajouter un nouvel épisode + re-consolider → renforcé à nouveau
        Confidence doit croître strictement entre les deux cycles.
        """
        root = realistic_project

        # ── Cycle 1: Consolider l'existant ──
        cycle1 = CognitiveCycle(str(root), verbose=False,
                                reference_date=datetime(2026, 3, 1))
        cycle1.run(phases=["consolidation"], apply=True)

        patterns_c1 = _load_yaml(root / "memory/semantic/defect_patterns.yaml")
        conf_c1 = next(
            p["confidence"] for p in patterns_c1["patterns"] if p["id"] == "PAT-NULL-001"
        )
        assert conf_c1 > 0.75  # Renforcé depuis 0.75

        # ── Ajouter un nouvel épisode non consolidé ──
        _write_yaml(
            root / "memory/episodic/analyses/2026-03-05_10-00_analysis_ANL-20260305-001.yaml",
            {
                "id": "ANL-20260305-001",
                "metadata": {"id": "ANL-20260305-001", "type": "analysis",
                             "created_at": "2026-03-05T10:00:00Z"},
                "project": "webapp",
                "findings": [
                    {"description": "PAT-NULL-001 found in checkout flow",
                     "severity": "critical", "category": "null_safety"},
                ],
                "consolidation": {"consolidated": False},
            },
        )

        # ── Cycle 2: Re-consolider → encore plus de confidence ──
        cycle2 = CognitiveCycle(str(root), verbose=False,
                                reference_date=datetime(2026, 3, 10))
        cycle2.run(phases=["consolidation"], apply=True)

        patterns_c2 = _load_yaml(root / "memory/semantic/defect_patterns.yaml")
        conf_c2 = next(
            p["confidence"] for p in patterns_c2["patterns"] if p["id"] == "PAT-NULL-001"
        )
        assert conf_c2 > conf_c1  # Croissance stricte

    def test_pattern_decay_without_reinforcement(self, realistic_project):
        """
        PAT-ASYNC-001 (last_seen=2026-01-20, confidence=0.45) sans renforcement.
        Après 6 mois sans activité, la confidence doit chuter significativement.
        """
        root = realistic_project

        # Consolider d'abord (PAT-ASYNC-001 n'est référencé nulle part)
        cycle_c = CognitiveCycle(str(root), verbose=False,
                                 reference_date=datetime(2026, 3, 1))
        cycle_c.run(phases=["consolidation"], apply=True)

        # Decay à date lointaine
        far_date = datetime(2026, 9, 1)  # 8 mois après last_seen
        cycle_d = CognitiveCycle(str(root), verbose=False, reference_date=far_date)
        report = cycle_d.run(phases=["decay"], apply=True)

        patterns = _load_yaml(root / "memory/semantic/defect_patterns.yaml")
        pat_async = next(
            p for p in patterns["patterns"] if p["id"] == "PAT-ASYNC-001"
        )
        # 224 days / 30 * 0.05 = ~0.37 decay → 0.45 - 0.37 = ~0.08
        assert pat_async["confidence"] < 0.20  # En dessous du seuil de removal

    def test_security_pattern_protected_across_cycles(self, realistic_project):
        """
        PAT-SEC-001 (category=security) ne doit jamais décroître,
        même après de longs cycles sans renforcement.
        """
        root = realistic_project
        patterns_before = _load_yaml(root / "memory/semantic/defect_patterns.yaml")
        sec_conf_before = next(
            p["confidence"] for p in patterns_before["patterns"] if p["id"] == "PAT-SEC-001"
        )

        # Cycle complet très loin dans le temps
        far_date = datetime(2028, 1, 1)
        cycle = CognitiveCycle(str(root), verbose=False, reference_date=far_date)
        cycle.run(apply=True)

        patterns_after = _load_yaml(root / "memory/semantic/defect_patterns.yaml")
        sec_conf_after = next(
            p["confidence"] for p in patterns_after["patterns"] if p["id"] == "PAT-SEC-001"
        )
        # Security pattern protégé — pas de decay
        assert sec_conf_after >= sec_conf_before


# ═══════════════════════════════════════════════════════════════════════════════
#  E2E SCENARIO 3 — Memory Recovery (Diagnostic + Réparation)
#  Parcours: mémoire dégradée → diagnostic → correction → état sain
# ═══════════════════════════════════════════════════════════════════════════════

class TestMemoryRecovery:
    """
    Scénario E2E: restauration d'une mémoire dégradée.
    Simule un utilisateur qui constate des problèmes et utilise
    les outils d'Argus pour diagnostiquer et réparer.
    """

    def test_diagnose_degraded_state(self, degraded_project):
        """
        Phase diagnostic: le cycle cognitif détecte tous les problèmes.
        - Consolidation: épisodes non consolidés
        - Decay: patterns anciens
        - Reflection: incohérences d'index, orphelins
        """
        ref = datetime(2026, 3, 1)
        cycle = CognitiveCycle(str(degraded_project), verbose=False, reference_date=ref)
        report = cycle.run()

        # Consolidation détecte du travail
        assert report.phases_executed[0].status == "actions_needed"
        # Decay détecte des patterns à faire décliner
        assert report.phases_executed[1].status == "actions_needed"
        # Reflection détecte des problèmes
        assert report.phases_executed[2].status == "issues_found"
        # Globalement pas sain
        assert report.overall_status != "HEALTHY"

    def test_sync_counters_detects_mismatches(self, degraded_project):
        """Le synchroniseur d'index détecte les compteurs désynchronisés."""
        sync = MemorySynchronizer(str(degraded_project), verbose=False)
        report = sync.analyze()

        assert report.has_mismatches
        assert report.mismatch_count > 0

    def test_sync_counters_fixes_mismatches(self, degraded_project):
        """Après correction, les compteurs medium-severity sont synchronisés."""
        sync = MemorySynchronizer(str(degraded_project), verbose=False)
        sync.analyze()
        success = sync.apply_fixes()
        assert success

        # Re-analyser: les compteurs d'index sont corrigés
        sync2 = MemorySynchronizer(str(degraded_project), verbose=False)
        report2 = sync2.analyze()
        medium_mismatches = [
            d for d in report2.deltas
            if d.is_mismatch and d.severity == "medium"
        ]
        assert len(medium_mismatches) == 0, (
            f"Medium mismatches remain: {[(d.location, d.expected, d.actual) for d in medium_mismatches]}"
        )

    def test_full_recovery_workflow(self, degraded_project):
        """
        Parcours complet de récupération:
        1. sync_counters --apply  → corrige l'index
        2. cognitive_cycle --apply → consolide + decay
        3. reflection → vérifie que l'état est amélioré
        """
        root = degraded_project
        ref = datetime(2026, 3, 1)

        # ── STEP 1: Réparer l'index ──
        sync = MemorySynchronizer(str(root), verbose=False)
        sync.analyze()
        sync.apply_fixes()

        # Vérifier que l'index est corrigé (rechargement)
        index = _load_yaml(root / "memory/_index.yaml")
        # Les compteurs devraient refléter les données réelles
        # 2 patterns, 2 heuristics, 2 analyses, 1 incident
        assert index["semantic"]["defect_patterns"]["count"] == 2
        assert index["semantic"]["testing_heuristics"]["count"] == 2

        # ── STEP 2: Cycle cognitif (consolide + decay) ──
        cycle = CognitiveCycle(str(root), verbose=False, reference_date=ref)
        report = cycle.run(phases=["consolidation", "decay"], apply=True)

        # Consolidation a trouvé et traité du travail
        assert report.consolidation_report is not None

        # PAT-NEW-001 devrait être renforcé (référencé dans 3 épisodes)
        patterns = _load_yaml(root / "memory/semantic/defect_patterns.yaml")
        pat_new = next(p for p in patterns["patterns"] if p["id"] == "PAT-NEW-001")
        assert pat_new["confidence"] > 0.60  # Renforcé depuis 0.60

        # PAT-OLD-001 devrait avoir subi du decay
        # last_seen=2025-06-01, ref=2026-03-01 → 273 jours
        pat_old = next(p for p in patterns["patterns"] if p["id"] == "PAT-OLD-001")
        assert pat_old["confidence"] < 0.30  # Décliné depuis 0.30

        # ── STEP 3: Reflection post-repair ──
        cycle_reflect = CognitiveCycle(str(root), verbose=False, reference_date=ref)
        report_reflect = cycle_reflect.run(phases=["reflection"])

        # La reflection détecte toujours les orphelins (c'est normal)
        # mais la mémoire est objectivement en meilleur état
        assert report_reflect.reflection_report is not None
        stats = report_reflect.reflection_report.stats
        assert stats.episodes_unconsolidated == 0  # Tout consolidé

    def test_yaml_validation_on_degraded_project(self, degraded_project):
        """Tous les fichiers YAML de la mémoire dégradée sont syntaxiquement valides."""
        validator = YAMLValidator(str(degraded_project), verbose=False)
        result = validator.validate_directory(degraded_project / "memory")
        assert result is True


# ═══════════════════════════════════════════════════════════════════════════════
#  E2E SCENARIO 4 — CLI Workflow
#  Parcours complet via subprocess, tel qu'un utilisateur réel
# ═══════════════════════════════════════════════════════════════════════════════

class TestCLIWorkflow:
    """
    Scénario E2E: workflow via ligne de commande.
    Teste l'interface CLI de bout en bout.
    """

    def test_cli_scan_then_apply_then_rescan(self, realistic_project):
        """
        1. Scan → détecte des actions (exit 1)
        2. Apply → applique les corrections
        3. Re-scan consolidation → rien à faire (exit 0 pour cette phase)
        """
        root = realistic_project
        tests_dir = str(Path(__file__).parent)

        # Step 1: Scan
        r1 = subprocess.run(
            [sys.executable, "cognitive_cycle.py",
             "--base-dir", str(root), "--quiet",
             "--reference-date", "2026-03-01"],
            capture_output=True, text=True, cwd=tests_dir,
        )
        assert r1.returncode == 1  # actions needed

        # Step 2: Apply
        r2 = subprocess.run(
            [sys.executable, "cognitive_cycle.py",
             "--base-dir", str(root), "--apply",
             "--quiet", "--reference-date", "2026-03-01"],
            capture_output=True, text=True, cwd=tests_dir,
        )
        # Apply returns 1 because it reports what it did (or reflection issues)
        # The important thing is it doesn't crash (exit 2)
        assert r2.returncode != 2

        # Step 3: Re-scan consolidation only
        r3 = subprocess.run(
            [sys.executable, "cognitive_cycle.py",
             "--base-dir", str(root), "--phase", "consolidation",
             "--quiet", "--reference-date", "2026-03-01"],
            capture_output=True, text=True, cwd=tests_dir,
        )
        assert r3.returncode == 0  # Nothing to consolidate

    def test_cli_json_output_full_cycle(self, realistic_project):
        """CLI --json produit un rapport JSON valide et complet."""
        tests_dir = str(Path(__file__).parent)
        result = subprocess.run(
            [sys.executable, "cognitive_cycle.py",
             "--base-dir", str(realistic_project), "--json",
             "--reference-date", "2026-03-01"],
            capture_output=True, text=True, cwd=tests_dir,
        )
        parsed = json.loads(result.stdout)

        # Structure attendue
        assert "overall_status" in parsed
        assert "phases" in parsed
        assert len(parsed["phases"]) == 3
        assert parsed["phases"][0]["phase"] == "consolidation"
        assert parsed["phases"][1]["phase"] == "decay"
        assert parsed["phases"][2]["phase"] == "reflection"

        # Détails inclus
        assert "consolidation_detail" in parsed
        assert "decay_detail" in parsed
        assert "reflection_detail" in parsed

    def test_cli_save_session_creates_file(self, realistic_project):
        """CLI --save-session crée un fichier dans memory/working/."""
        tests_dir = str(Path(__file__).parent)
        result = subprocess.run(
            [sys.executable, "cognitive_cycle.py",
             "--base-dir", str(realistic_project),
             "--quiet", "--save-session",
             "--reference-date", "2026-03-01"],
            capture_output=True, text=True, cwd=tests_dir,
        )
        assert result.returncode != 2

        # Vérifier qu'un fichier session a été créé
        working_dir = realistic_project / "memory" / "working"
        session_files = list(working_dir.glob("ses_*.yaml"))
        assert len(session_files) >= 1

        # Le fichier est un YAML valide
        session = _load_yaml(session_files[0])
        assert "session_id" in session
        assert session["status"] == "active"

    def test_cli_phase_isolation(self, realistic_project):
        """Chaque phase CLI isolée ne touche que son domaine."""
        tests_dir = str(Path(__file__).parent)

        # Capturer état initial
        patterns_before = _load_yaml(
            realistic_project / "memory/semantic/defect_patterns.yaml"
        )

        # Exécuter reflection seule (lecture seule)
        result = subprocess.run(
            [sys.executable, "cognitive_cycle.py",
             "--base-dir", str(realistic_project),
             "--phase", "reflection", "--json",
             "--reference-date", "2026-03-01"],
            capture_output=True, text=True, cwd=tests_dir,
        )
        parsed = json.loads(result.stdout)
        assert len(parsed["phases"]) == 1
        assert parsed["phases"][0]["phase"] == "reflection"

        # Les patterns n'ont pas changé (reflection = lecture seule)
        patterns_after = _load_yaml(
            realistic_project / "memory/semantic/defect_patterns.yaml"
        )
        for pb, pa in zip(patterns_before["patterns"], patterns_after["patterns"]):
            assert pb["confidence"] == pa["confidence"]


# ═══════════════════════════════════════════════════════════════════════════════
#  E2E SCENARIO 5 — Real Data Validation
#  Exécute les outils de validation sur la mémoire production
# ═══════════════════════════════════════════════════════════════════════════════

class TestRealDataValidation:
    """
    Scénario E2E: validation de la mémoire production réelle d'Argus.
    Ces tests vérifient que les données actuelles du repository sont cohérentes.
    Ils détectent les régressions de données (pas de code).
    """

    REAL_BASE = Path(__file__).resolve().parent.parent

    def test_real_yaml_syntax_valid(self):
        """Tous les fichiers YAML du repository sont syntaxiquement corrects."""
        validator = YAMLValidator(str(self.REAL_BASE), verbose=False)
        result = validator.validate_directory(self.REAL_BASE / "memory")
        assert result is True, f"YAML errors found: {validator.errors}"

    def test_real_index_synchronized(self):
        """Les compteurs de _index.yaml correspondent aux données réelles."""
        sync = MemorySynchronizer(str(self.REAL_BASE), verbose=False)
        report = sync.analyze()
        mismatches = [d for d in report.deltas if d.is_mismatch]
        assert len(mismatches) == 0, (
            f"Index desynchronized: {[(d.location, d.expected, d.actual) for d in mismatches]}"
        )

    def test_real_references_valid(self):
        """Toutes les cross-références sont valides (pas d'orphelins)."""
        validator = ReferenceValidator(str(self.REAL_BASE), verbose=False)
        report = validator.run()
        assert not report.has_orphans, (
            f"Orphan references: {[(o.reference.target_id, o.reference.source_file) for o in report.orphans]}"
        )

    def test_real_cognitive_cycle_scan(self):
        """Le cycle cognitif scan ne crashe pas sur les données réelles."""
        cycle = CognitiveCycle(str(self.REAL_BASE), verbose=False,
                               reference_date=datetime(2026, 3, 8))
        report = cycle.run()

        assert len(report.phases_executed) == 3
        for p in report.phases_executed:
            assert p.status != "error", f"Phase {p.phase} errored: {p.error}"

    def test_real_reflection_no_errors(self):
        """Reflection sur données réelles: pas d'erreurs critiques."""
        engine = ReflectionEngine(str(self.REAL_BASE), verbose=False,
                                  reference_date=datetime(2026, 3, 8))
        report = engine.reflect()
        assert report.error_count == 0, (
            f"Reflection errors: {[c.message for c in report.checks if c.status == 'error']}"
        )


# ═══════════════════════════════════════════════════════════════════════════════
#  E2E SCENARIO 6 — Session Working Memory
#  Parcours: cycle → session enregistrée → session contient les bonnes données
# ═══════════════════════════════════════════════════════════════════════════════

class TestSessionWorkflow:
    """
    Scénario E2E: la session de travail capture fidèlement l'activité du cycle.
    """

    def test_session_captures_full_cycle_activity(self, realistic_project):
        """
        Exécuter un cycle complet, puis sauvegarder la session.
        La session doit contenir:
        - Les patterns consultés (consolidation reinforcements)
        - Les candidats de nouveaux patterns
        - Les triggers de consolidation
        """
        root = realistic_project
        cycle = CognitiveCycle(str(root), verbose=False,
                               reference_date=datetime(2026, 3, 1))
        cycle.run(apply=True)

        # Sauvegarder et relire la session
        filepath = cycle.save_session("SES-20260301-E2E")
        session = _load_yaml(filepath)

        # Structure de la session
        assert session["session_id"] == "SES-20260301-E2E"
        assert session["status"] == "active"
        assert session["context"]["trigger"] == "scheduled"

        # Patterns consultés (PAT-NULL-001 a été renforcé)
        patterns_consulted = session["knowledge_activated"]["patterns_consulted"]
        assert isinstance(patterns_consulted, list)
        assert len(patterns_consulted) > 0  # Au moins PAT-NULL-001

        # La session est un YAML valide
        validator = YAMLValidator(str(root), verbose=False)
        assert validator.validate_file(filepath)

    def test_session_not_polluted_by_reflection_only(self, realistic_project):
        """
        Si seule reflection est exécutée (lecture seule),
        la session devrait avoir des listes de patterns vides.
        """
        root = realistic_project
        cycle = CognitiveCycle(str(root), verbose=False,
                               reference_date=datetime(2026, 3, 1))
        cycle.run(phases=["reflection"])

        record = cycle.create_session_record("SES-REFLECT-ONLY")
        # Pas de consolidation → pas de patterns consultés
        assert record["knowledge_activated"]["patterns_consulted"] == []
        assert record["archival"]["consolidation_triggers"] == []
