#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
═══════════════════════════════════════════════════════════════════════════════
 ARGUS MCP SERVER
 "Celui aux cent yeux qui voit ce que d'autres ne voient pas"

 Expose la mémoire et les capacités d'analyse d'Argus via le
 Model Context Protocol (MCP). Compatible VS Code, Claude Desktop,
 Cursor, et tout client MCP.

 Transport : stdio (standard pour les intégrations IDE)

 Tools exposés :
   1. query_memory     — Interrogation de la mémoire sémantique/épisodique
   2. get_risk_analysis — Analyse de risque sur un diff ou fichier
   3. get_testing_strategy — Recommandation de stratégie de test
   4. record_analysis   — Enregistrement d'une analyse en mémoire épisodique

 Lancement :
   python -m mcp_server.server
═══════════════════════════════════════════════════════════════════════════════
"""

import json
import re
import sys
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

import yaml
from mcp.server.fastmcp import FastMCP

from . import memory_reader as mem
from . import data_dir

# ═══════════════════════════════════════════════════════════════════════════════
#  SECURITY LIMITS
# ═══════════════════════════════════════════════════════════════════════════════

MAX_CODE_INPUT_SIZE = 500_000   # 500 KB max pour code_or_diff
MAX_FINDINGS_COUNT = 50         # Max findings par analyse
MAX_DAILY_ANALYSES = 100        # Max analyses enregistrées par jour

# ═══════════════════════════════════════════════════════════════════════════════
#  SERVER INIT
# ═══════════════════════════════════════════════════════════════════════════════

mcp = FastMCP(
    "Argus QA Orchestrator",
    instructions=(
        "Argus est un orchestrateur QA spécialisé en test et qualité logicielle. "
        "Il maintient une mémoire structurée (patterns de défauts, heuristiques de test, "
        "analyses épisodiques, profils de contexte) et fournit des recommandations "
        "actionnables basées sur cette knowledge base."
    ),
)


# ═══════════════════════════════════════════════════════════════════════════════
#  TOOL 1 — query_memory
# ═══════════════════════════════════════════════════════════════════════════════

@mcp.tool()
def query_memory(
    section: str,
    query: str | None = None,
    category: str | None = None,
    min_confidence: float | None = None,
) -> str:
    """Interroge la mémoire d'Argus (patterns, heuristiques, analyses, projets, stats).

    Args:
        section: Section à interroger. Valeurs possibles :
            - "patterns" : Defect patterns (46 patterns, 12 catégories)
            - "heuristics" : Heuristiques de test (62 heuristiques)
            - "analyses" : Historique des analyses réalisées
            - "decisions" : Décisions architecturales documentées
            - "incidents" : Incidents et échappés enregistrés
            - "executions" : Résultats d'exécutions de tests
            - "projects" : Profils de projets analysés
            - "profiles" : Profils de contexte applicatif (fintech, e-commerce, etc.)
            - "associations" : Graphe relationnel entre entités
            - "stats" : Résumé statistique global de la mémoire
            - "index" : Index de la mémoire complète
        query: Texte de recherche (filtre par nom, description, catégorie). Optionnel.
        category: Filtre par catégorie (ex: "security", "api", "performance"). Optionnel.
        min_confidence: Seuil minimum de confidence (0.0 - 1.0). Optionnel.

    Returns:
        Résultats au format JSON.
    """
    section = section.lower().strip()

    loaders: dict[str, Any] = {
        "patterns": lambda: mem.search_patterns(query, category, min_confidence),
        "heuristics": lambda: mem.search_heuristics(query, category, min_confidence),
        "analyses": mem.load_analyses,
        "decisions": mem.load_decisions,
        "incidents": mem.load_incidents,
        "executions": mem.load_executions,
        "projects": mem.load_projects,
        "profiles": mem.load_context_profiles,
        "associations": mem.load_associations,
        "stats": mem.get_memory_stats,
        "index": mem.load_index,
    }

    if section not in loaders:
        return json.dumps({
            "error": f"Section '{section}' inconnue.",
            "available_sections": list(loaders.keys()),
        }, ensure_ascii=False, indent=2)

    data = loaders[section]()
    result_count = len(data) if isinstance(data, list) else 1

    return json.dumps({
        "section": section,
        "query": query,
        "category": category,
        "min_confidence": min_confidence,
        "result_count": result_count,
        "results": data,
    }, ensure_ascii=False, indent=2, default=str)


# ═══════════════════════════════════════════════════════════════════════════════
#  TOOL 2 — get_risk_analysis
# ═══════════════════════════════════════════════════════════════════════════════

@mcp.tool()
def get_risk_analysis(
    code_or_diff: str,
    context_profile: str = "CTX-GENERIC",
    file_path: str | None = None,
) -> str:
    """Analyse de risque sur un extrait de code ou un diff.

    Croise le code fourni avec les defect patterns et heuristiques d'Argus
    pour identifier les risques potentiels et recommander des actions.

    Args:
        code_or_diff: Code source ou diff (format unified diff) à analyser.
        context_profile: Profil de contexte applicatif (CTX-GENERIC, CTX-FINTECH,
            CTX-ECOMMERCE, CTX-HEALTH, CTX-SAAS, CTX-IOT). Par défaut CTX-GENERIC.
        file_path: Chemin du fichier analysé (pour contexte). Optionnel.

    Returns:
        Rapport de risque structuré au format JSON.
    """
    # Validation taille input (SEC-001/SEC-002 — ReDoS + OOM)
    if len(code_or_diff) > MAX_CODE_INPUT_SIZE:
        return json.dumps({
            "error": f"Input trop volumineux ({len(code_or_diff)} caractères). "
                     f"Maximum autorisé : {MAX_CODE_INPUT_SIZE}.",
        }, ensure_ascii=False, indent=2)

    patterns = mem.load_patterns()
    profiles = mem.load_context_profiles()

    # Charger le profil de contexte
    profile = next(
        (p for p in profiles if p.get("id") == context_profile),
        next((p for p in profiles if p.get("id") == "CTX-GENERIC"), {}),
    )
    risk_multipliers = profile.get("risk_multipliers", {})

    # Analyser le code contre chaque pattern
    matches = []
    code_lower = code_or_diff.lower()

    for pattern in patterns:
        signature = pattern.get("signature", {})
        description = str(signature.get("description", "")).lower()
        indicators = signature.get("indicators", [])
        code_patterns = signature.get("code_patterns", [])
        prevention = pattern.get("prevention_strategy", [])

        # Score de correspondance multi-signal
        match_score = 0
        match_reasons = []

        # Signal 1 : indicateurs textuels (correspondance partielle par mots-clés)
        for indicator in indicators:
            if isinstance(indicator, str):
                # Extraire les mots significatifs (>3 chars) de l'indicateur
                keywords = [w for w in re.findall(r'[a-z_\.]+', indicator.lower()) if len(w) > 3]
                matched_kw = [kw for kw in keywords if kw in code_lower]
                if len(matched_kw) >= 2 or (len(matched_kw) == 1 and len(matched_kw[0]) > 6):
                    match_score += 2
                    match_reasons.append(f"indicator: {indicator}")

        # Signal 2 : code patterns descriptifs (même approche)
        for cp in code_patterns:
            if isinstance(cp, str):
                keywords = [w for w in re.findall(r'[a-z_\.]+', cp.lower()) if len(w) > 3]
                matched_kw = [kw for kw in keywords if kw in code_lower]
                if len(matched_kw) >= 2 or (len(matched_kw) == 1 and len(matched_kw[0]) > 6):
                    match_score += 2
                    match_reasons.append(f"code_pattern: {cp}")

        # Signal 3 : termes techniques reconnus dans le code
        tech_signals = _get_technical_signals(pattern.get("id", ""), pattern.get("category", ""))
        for signal_re, weight in tech_signals:
            if re.search(signal_re, code_lower):
                match_score += weight
                match_reasons.append(f"tech_signal: {signal_re}")

        # Signal 4 : description du pattern vs code
        desc_keywords = [w for w in re.findall(r'[a-z_]+', description) if len(w) > 4]
        desc_matches = sum(1 for kw in desc_keywords if kw in code_lower)
        if desc_matches >= 3:
            match_score += 1
            match_reasons.append(f"description_match: {desc_matches} keywords")

        if match_score >= 2:
            cat = pattern.get("category", "")
            multiplier = risk_multipliers.get(cat, 1.0)
            confidence = pattern.get("confidence", 0.5)
            base_severity = _severity_to_score(pattern.get("severity_typical", "medium"))

            matches.append({
                "pattern_id": pattern.get("id"),
                "pattern_name": pattern.get("name"),
                "category": cat,
                "severity": pattern.get("severity_typical"),
                "match_reasons": match_reasons,
                "match_score": match_score,
                "confidence": confidence,
                "risk_score": round(base_severity * multiplier * confidence, 2),
                "detection_strategy": pattern.get("detection_strategy", {}),
                "prevention": pattern.get("prevention_strategy", []),
            })

    # Tri par risk_score décroissant
    matches.sort(key=lambda m: m["risk_score"], reverse=True)

    # Déterminer le niveau de risque global
    max_score = matches[0]["risk_score"] if matches else 0
    risk_level = (
        "critical" if max_score >= 15 else
        "high" if max_score >= 8 else
        "medium" if max_score >= 3 else
        "low"
    )

    return json.dumps({
        "analysis_type": "risk_analysis",
        "file_path": file_path,
        "context_profile": context_profile,
        "risk_level": risk_level,
        "max_risk_score": max_score,
        "patterns_matched": len(matches),
        "patterns_checked": len(patterns),
        "mandatory_checks": profile.get("mandatory_checks", []),
        "findings": matches,
    }, ensure_ascii=False, indent=2, default=str)


def _severity_to_score(severity: str) -> int:
    """Convertit un niveau de sévérité en score numérique."""
    mapping = {"critical": 10, "high": 7, "medium": 5, "low": 2}
    return mapping.get(severity, 5)


def _get_technical_signals(pattern_id: str, category: str) -> list[tuple[str, int]]:
    """Retourne des regex techniques spécifiques pour détecter un pattern dans du code.

    Chaque signal est un tuple (regex_pattern, weight).
    """
    signals: dict[str, list[tuple[str, int]]] = {
        # SQL/NoSQL Injection
        "PAT-SEC-001": [
            (r'f["\']select\b', 3),
            (r'f["\']insert\b', 3),
            (r'f["\']update\b', 3),
            (r'f["\']delete\b', 3),
            (r'\.format\([^)]{0,200}\).*?(?:select|insert|update|delete)', 3),
            (r'execute\s*\([^)]{0,200}[+%]', 2),
            (r'\$where', 2),
            (r'string\s+concatenat', 2),
        ],
        # Sensitive Data Exposure
        "PAT-SEC-002": [
            (r'password\s*=\s*["\'][^"\']+["\']', 3),
            (r'api[_-]?key\s*=\s*["\'][^"\']+["\']', 3),
            (r'secret\s*=\s*["\'][^"\']+["\']', 3),
            (r'console\.log\([^)]{0,200}password', 2),
            (r'print\([^)]{0,200}password', 2),
            (r'\.env\b[^\n]{0,100}(?:key|secret|token)', 2),
        ],
        # XSS
        "PAT-SEC-003": [
            (r'innerhtml\s*=', 3),
            (r'dangerouslysetinnerhtml', 3),
            (r'document\.write\s*\(', 2),
            (r'v-html\s*=', 2),
            (r'\{.*\|.*safe\b', 2),
        ],
        # Race Condition
        "PAT-ASYNC-001": [
            (r'(?:shared|global)\s+.*\b(?:state|data|counter)\b', 2),
            (r'threading\b.*(?:lock|mutex)', 2),
            (r'async\b.*(?:shared|global)', 2),
        ],
        # Null Safety
        "PAT-NULL-001": [
            (r'\.\w+\s*\.\w+\s*\.\w+', 2),  # deep chaining
            (r'(?:result|response|data)\[\w+\]\[\w+\]', 2),
            (r'\.get\(.*\)\.', 1),
        ],
    }

    # Signaux génériques par catégorie
    category_signals: dict[str, list[tuple[str, int]]] = {
        "security": [
            (r'(?:eval|exec)\s*\(', 2),
            (r'(?:pickle|marshal)\.loads?\(', 2),
            (r'subprocess\s*\.\s*(?:call|run|popen)\s*\([^)]{0,200}shell\s*=\s*true', 3),
        ],
        "performance": [
            (r'for\s+.*\bfor\s+', 2),  # nested loops
            (r'select\s+\*\s+from', 2),
            (r'n\s*\+\s*1\s*quer', 2),
        ],
        "api": [
            (r'(?:get|post|put|delete|patch)\s*\(', 1),
            (r'fetch\s*\(', 1),
            (r'axios\b', 1),
        ],
    }

    result = signals.get(pattern_id, [])
    result.extend(category_signals.get(category, []))
    return result


# ═══════════════════════════════════════════════════════════════════════════════
#  TOOL 3 — get_testing_strategy
# ═══════════════════════════════════════════════════════════════════════════════

@mcp.tool()
def get_testing_strategy(
    change_description: str,
    change_type: str = "feature",
    context_profile: str = "CTX-GENERIC",
    components_affected: list[str] | None = None,
) -> str:
    """Recommande une stratégie de test adaptée au changement décrit.

    Croise le changement avec les heuristiques de test, les patterns de défauts
    connus et le profil de contexte pour proposer des types de tests prioritaires.

    Args:
        change_description: Description du changement (feature, bugfix, refactoring, etc.).
        change_type: Type de changement parmi : "feature", "bugfix", "refactoring",
            "performance", "security", "dependency_update", "config". Par défaut "feature".
        context_profile: Profil de contexte applicatif (CTX-GENERIC, CTX-FINTECH, etc.).
        components_affected: Liste des composants impactés (ex: ["auth", "payment", "api"]).

    Returns:
        Stratégie de test recommandée au format JSON.
    """
    heuristics = mem.load_heuristics()
    patterns = mem.load_patterns()
    profiles = mem.load_context_profiles()

    profile = next(
        (p for p in profiles if p.get("id") == context_profile),
        next((p for p in profiles if p.get("id") == "CTX-GENERIC"), {}),
    )

    desc_lower = change_description.lower()
    components = [c.lower() for c in (components_affected or [])]

    # Sélectionner les heuristiques pertinentes
    applicable_heuristics = []
    for h in heuristics:
        condition = str(h.get("condition", "")).lower()
        action = str(h.get("action", "")).lower()

        relevance_score = 0

        # Match sur le type de changement
        if change_type.lower() in condition:
            relevance_score += 3
        # Match sur la description
        for word in desc_lower.split():
            if len(word) > 3 and word in condition:
                relevance_score += 1
        # Match sur les composants
        for comp in components:
            if comp in condition or comp in action:
                relevance_score += 2

        if relevance_score > 0:
            applicable_heuristics.append({
                "id": h.get("id"),
                "name": h.get("name"),
                "category": h.get("category"),
                "action": h.get("action"),
                "rationale": h.get("rationale"),
                "priority": h.get("priority"),
                "confidence": h.get("confidence"),
                "relevance_score": relevance_score,
            })

    applicable_heuristics.sort(key=lambda h: h["relevance_score"], reverse=True)

    # Identifier les patterns de risque pertinents
    risk_patterns = []
    for p in patterns:
        desc = str(p.get("signature", {}).get("description", "")).lower()
        cat = p.get("category", "").lower()

        is_relevant = (
            any(word in desc for word in desc_lower.split() if len(word) > 3)
            or any(comp in cat or comp in desc for comp in components)
        )
        if is_relevant:
            risk_patterns.append({
                "id": p.get("id"),
                "name": p.get("name"),
                "category": p.get("category"),
                "severity": p.get("severity_typical"),
                "confidence": p.get("confidence"),
            })

    # Construire la recommandation de stratégie
    test_types = _recommend_test_types(change_type, components, profile)

    return json.dumps({
        "strategy_type": "testing_strategy",
        "change_type": change_type,
        "change_description": change_description,
        "context_profile": context_profile,
        "test_types_recommended": test_types,
        "applicable_heuristics": applicable_heuristics[:10],
        "risk_patterns": risk_patterns[:10],
        "mandatory_checks": profile.get("mandatory_checks", []),
        "recommended_heuristics_from_profile": profile.get("recommended_heuristics", []),
        "priority_patterns_from_profile": profile.get("priority_patterns", []),
    }, ensure_ascii=False, indent=2, default=str)


def _recommend_test_types(
    change_type: str,
    components: list[str],
    profile: dict,
) -> list[dict]:
    """Recommande les types de test par priorité."""
    recommendations = []

    # Stratégie de base selon le type de changement
    type_strategies = {
        "feature": [
            {"type": "unit", "priority": "P0", "reason": "Logique métier du nouveau feature"},
            {"type": "integration", "priority": "P1", "reason": "Interactions avec composants existants"},
            {"type": "e2e", "priority": "P2", "reason": "Parcours utilisateur impacté"},
        ],
        "bugfix": [
            {"type": "unit", "priority": "P0", "reason": "Reproduire le bug + confirmer le fix"},
            {"type": "regression", "priority": "P0", "reason": "Vérifier qu'aucune régression n'est introduite"},
        ],
        "refactoring": [
            {"type": "unit", "priority": "P0", "reason": "Tous les tests existants doivent passer"},
            {"type": "integration", "priority": "P1", "reason": "Vérifier les contrats d'interface"},
        ],
        "performance": [
            {"type": "performance", "priority": "P0", "reason": "Mesurer l'impact performance"},
            {"type": "unit", "priority": "P1", "reason": "Tests unitaires pour la logique modifiée"},
        ],
        "security": [
            {"type": "security", "priority": "P0", "reason": "Validation sécurité du changement"},
            {"type": "unit", "priority": "P0", "reason": "Tests unitaires pour la logique de sécurité"},
            {"type": "integration", "priority": "P1", "reason": "Vérifier l'intégration des contrôles de sécurité"},
        ],
        "dependency_update": [
            {"type": "integration", "priority": "P0", "reason": "Vérifier la compatibilité de la dépendance"},
            {"type": "regression", "priority": "P0", "reason": "Suite de régression complète"},
        ],
        "config": [
            {"type": "integration", "priority": "P0", "reason": "Vérifier le chargement de la config"},
            {"type": "e2e", "priority": "P1", "reason": "Smoke test environnement"},
        ],
    }

    recommendations = type_strategies.get(change_type, type_strategies["feature"])

    # Ajouts selon les composants critiques
    critical_components = {"auth", "authentication", "payment", "billing", "data", "database"}
    if any(comp in critical_components for comp in components):
        recommendations.append({
            "type": "security",
            "priority": "P0",
            "reason": "Composant critique (auth/payment/data) impacté",
        })

    return recommendations


# ═══════════════════════════════════════════════════════════════════════════════
#  TOOL 4 — record_analysis
# ═══════════════════════════════════════════════════════════════════════════════

@mcp.tool()
def record_analysis(
    title: str,
    findings: list[dict],
    project: str = "external",
    trigger: str = "manual_request",
    scope_type: str = "file",
    scope_reference: str = "",
    verdict: str = "approved_with_caveats",
) -> str:
    """Enregistre une analyse en mémoire épisodique.

    Crée un fichier YAML structuré dans memory/episodic/analyses/ avec un
    identifiant unique et met à jour l'index.

    Args:
        title: Titre court de l'analyse (ex: "Audit sécurité module auth").
        findings: Liste de findings, chacun avec :
            - title (str): Titre du finding
            - risk_level (str): "critical", "high", "medium", "low"
            - category (str): "regression", "bug", "code_smell", "security", "performance", "test_gap"
            - description (str): Description détaillée
            - recommendation (str): Action recommandée
        project: Identifiant du projet analysé. Par défaut "external".
        trigger: Déclencheur de l'analyse ("manual_request", "PR_merge", "incident_response").
        scope_type: Type de scope ("file", "PR", "directory", "full_project").
        scope_reference: Référence du scope (chemin, numéro de PR, etc.).
        verdict: Verdict global ("approved", "approved_with_caveats", "rejected").

    Returns:
        Confirmation avec l'identifiant de l'analyse créée.
    """
    # Validation taille findings (SEC-004 — fichiers YAML géants)
    if len(findings) > MAX_FINDINGS_COUNT:
        return json.dumps({
            "error": f"Trop de findings ({len(findings)}). Maximum autorisé : {MAX_FINDINGS_COUNT}.",
        }, ensure_ascii=False, indent=2)

    # Rate limiting journalier (SEC-003 — écritures disque illimitées)
    now = datetime.now(timezone.utc)
    date_str = now.strftime("%Y-%m-%d")
    today_count = _count_today_analyses(date_str)
    if today_count >= MAX_DAILY_ANALYSES:
        return json.dumps({
            "error": f"Limite journalière atteinte ({MAX_DAILY_ANALYSES} analyses/jour).",
        }, ensure_ascii=False, indent=2)

    timestamp_str = now.isoformat()

    # Calculer le prochain ID
    index = mem.load_index()
    current_count = index.get("episodic", {}).get("analyses", {}).get("count", 0)
    seq = current_count + 1
    analysis_id = f"ANL-{now.strftime('%Y%m%d')}-{seq:03d}"

    # Construire les findings structurés
    structured_findings = []
    for i, f in enumerate(findings, 1):
        structured_findings.append({
            "id": f"F{i:03d}",
            "risk_level": f.get("risk_level", "medium"),
            "category": f.get("category", "finding"),
            "title": f.get("title", ""),
            "description": f.get("description", ""),
            "recommendation": f.get("recommendation", ""),
            "related_patterns": f.get("related_patterns", []),
            "related_heuristics": f.get("related_heuristics", []),
            "confidence": f.get("confidence", 0.7),
        })

    # Construire le document d'analyse
    analysis = {
        "quick_summary": {
            "one_liner": title,
            "key_numbers": [
                f"{len(structured_findings)} findings identifiés",
                f"Profil: {project}",
            ],
            "status": "✅ COMPLETED",
            "key_deliverables": [f"Analyse {analysis_id} enregistrée"],
            "next_action": "Revue des findings et actions correctives",
        },
        "id": analysis_id,
        "timestamp": timestamp_str,
        "duration_minutes": 0,
        "project": project,
        "trigger": trigger,
        "triggered_by": "mcp_server",
        "scope": {
            "type": scope_type,
            "reference": scope_reference,
            "files_analyzed": [],
            "components_affected": [],
        },
        "findings": structured_findings,
        "api_analysis": {"applicable": False},
        "recommendations": [
            {
                "priority": "P1",
                "action": f.get("recommendation", ""),
                "rationale": f.get("title", ""),
                "effort": "medium",
            }
            for f in structured_findings
            if f.get("recommendation")
        ],
        "metrics": {
            "coverage_before": None,
            "coverage_after": None,
            "complexity_delta": 0,
            "lines_changed": 0,
            "tests_affected": 0,
        },
        "testing": {
            "tests_executed": [],
            "tests_recommended": [],
        },
        "verdict": {
            "status": verdict,
            "summary": title,
            "conditions": [],
        },
        "outcome": {
            "recorded_at": None,
            "what_happened": None,
            "incidents_related": [],
            "feedback_received": None,
        },
        "consolidation": {
            "consolidated": False,
            "consolidated_at": None,
            "patterns_extracted": [],
            "reinforcements_generated": 0,
        },
        "tags": ["mcp-recorded", project],
    }

    # Écrire le fichier
    filename = f"{date_str}_analysis_{analysis_id}.yaml"
    output_path = mem.get_memory_root() / "episodic" / "analyses" / filename

    with open(output_path, "w", encoding="utf-8") as fp:
        yaml.dump(analysis, fp, allow_unicode=True, default_flow_style=False, sort_keys=False)

    # Mettre à jour l'index
    _update_index_after_analysis(analysis_id, seq)

    return json.dumps({
        "status": "success",
        "analysis_id": analysis_id,
        "file_path": str(output_path),
        "findings_count": len(structured_findings),
        "message": f"Analyse {analysis_id} enregistrée avec succès.",
    }, ensure_ascii=False, indent=2)


def _count_today_analyses(date_str: str) -> int:
    """Compte le nombre d'analyses enregistrées aujourd'hui."""
    analyses_dir = mem.get_memory_root() / "episodic" / "analyses"
    if not analyses_dir.exists():
        return 0
    return sum(
        1 for f in analyses_dir.glob(f"{date_str}_*.yaml")
        if not f.name.startswith("_template")
    )


def _update_index_after_analysis(analysis_id: str, new_count: int) -> None:
    """Met à jour _index.yaml après l'enregistrement d'une analyse."""
    index_path = mem.get_memory_root() / "_index.yaml"
    index = mem.load_index()

    # Mettre à jour les compteurs
    if "episodic" not in index:
        index["episodic"] = {}
    if "analyses" not in index["episodic"]:
        index["episodic"]["analyses"] = {}

    index["episodic"]["analyses"]["count"] = new_count
    index["episodic"]["analyses"]["latest"] = analysis_id
    index["last_updated"] = datetime.now(timezone.utc).strftime("%Y-%m-%d")

    # Mettre à jour les totaux et séquences
    if "statistics" in index and "totals" in index["statistics"]:
        index["statistics"]["totals"]["episodes"] = (
            index["statistics"]["totals"].get("episodes", 0) + 1
        )
    if "sequences" in index:
        index["sequences"]["analysis"] = new_count

    with open(index_path, "w", encoding="utf-8") as fp:
        yaml.dump(index, fp, allow_unicode=True, default_flow_style=False, sort_keys=False)


# ═══════════════════════════════════════════════════════════════════════════════
#  RESOURCES — Expose key files as MCP resources
# ═══════════════════════════════════════════════════════════════════════════════

@mcp.resource("argus://persona")
def get_persona() -> str:
    """La persona complète d'Argus (CRAFT/TOON framework)."""
    persona_path = mem.get_project_root() / "argus.persona.yaml"
    return persona_path.read_text(encoding="utf-8")


@mcp.resource("argus://memory/stats")
def get_stats_resource() -> str:
    """Statistiques globales de la mémoire."""
    return json.dumps(mem.get_memory_stats(), ensure_ascii=False, indent=2, default=str)


# ═══════════════════════════════════════════════════════════════════════════════
#  ENTRY POINT
# ═══════════════════════════════════════════════════════════════════════════════

def main():
    """Point d'entrée principal — lance le serveur MCP en mode stdio."""
    # Ensure data directory is initialized (first-run setup for pip users)
    resolved = data_dir.get_data_dir()
    print(f"Argus data: {resolved}", file=sys.stderr)
    mcp.run(transport="stdio")


if __name__ == "__main__":
    main()
