#!/usr/bin/env python3
"""
Validate Argus YAML files against their JSON Schemas.
Usage:
  python tests/validate_schemas.py [--verbose]     # Validate all YAML files
  python tests/validate_schemas.py --staged        # Validate only staged YAML files (for pre-commit)
  python tests/validate_schemas.py --quiet         # Silent mode (exit code only)
"""
import json
import sys
import os
import subprocess
from pathlib import Path

try:
    import yaml
    import jsonschema
except ImportError:
    print("Missing dependencies. Install with: pip install pyyaml jsonschema")
    sys.exit(1)

# ─── Schema mapping: YAML file path pattern → schema file ───────────────────# Deux niveaux de mapping :
#   1. SCHEMA_MAP : correspondance exacte (fichiers uniques + templates)
#   2. DIR_SCHEMA_MAP : correspondance par répertoire (enregistrements épisodiques)
# Un fichier sans mapping est simplement ignoré (pas d'erreur)
ROOT = Path(__file__).resolve().parent.parent
SCHEMAS_DIR = ROOT / "schemas"

SCHEMA_MAP = {
    # Exact files
    "argus.persona.yaml":                    "persona.schema.json",
    "memory/_index.yaml":                    "memory_index.schema.json",
    "memory/episodic/evolution_log.yaml":    "evolution_log.schema.json",
    "memory/relational/associations.yaml":   "associations.schema.json",
    "memory/semantic/defect_patterns.yaml":  "defect_patterns.schema.json",
    "memory/semantic/testing_heuristics.yaml": "testing_heuristics.schema.json",
    "memory/semantic/technology_knowledge.yaml": "technology_knowledge.schema.json",
    "memory/semantic/istqb_knowledge.yaml":  "istqb_knowledge.schema.json",
    "memory/semantic/context_profiles.yaml":   "context_profiles.schema.json",
    
    # Templates
    "memory/episodic/analyses/_template_analysis.yaml": "analysis.schema.json",
    "memory/episodic/decisions/_template_decision.yaml": "decision.schema.json",
    "memory/episodic/executions/_template_execution.yaml": "execution.schema.json",
    "memory/episodic/incidents/_template_incident.yaml": "incident.schema.json",
    "memory/projects/_template_project.yaml": "project.schema.json",
    
    # Deliverable templates
    "memory/deliverables/_template_test_plan.yaml": "test_plan.schema.json",
    "memory/deliverables/_template_traceability_matrix.yaml": "traceability_matrix.schema.json",
    "memory/deliverables/_template_exploratory_charter.yaml": "exploratory_charter.schema.json",
}

# Mapping par répertoire pour les enregistrements réels (pas les templates)
# Les fichiers dans ces dossiers héritent du schema correspondant au type d'épisode
DIR_SCHEMA_MAP = {
    "memory/episodic/analyses/":   "analysis.schema.json",
    "memory/episodic/decisions/":  "decision.schema.json",
    "memory/episodic/executions/": "execution.schema.json",
    "memory/episodic/incidents/":  "incident.schema.json",
    "memory/projects/":            "project.schema.json",
    "memory/deliverables/":         None,  # Each deliverable has its own schema via SCHEMA_MAP
}


def get_schema_for_file(rel_path: str) -> str | None:
    """Determine which schema applies to a given YAML file."""
    rel_path = rel_path.replace("\\", "/")
    
    # Exact match
    if rel_path in SCHEMA_MAP:
        return SCHEMA_MAP[rel_path]
    
    # Directory-based match (for actual records, not templates)
    for dir_prefix, schema in DIR_SCHEMA_MAP.items():
        if rel_path.startswith(dir_prefix) and rel_path.endswith(".yaml"):
            filename = os.path.basename(rel_path)
            if not filename.startswith("_"):  # Skip templates (already mapped)
                return schema
    
    return None


def load_schema(schema_name: str) -> dict:
    """Load a JSON schema file."""
    schema_path = SCHEMAS_DIR / schema_name
    with open(schema_path, "r", encoding="utf-8") as f:
        return json.load(f)


def validate_file(yaml_path: Path, schema_name: str, verbose: bool = False) -> list[str]:
    """Validate a YAML file against its schema. Returns list of errors."""
    errors = []
    
    try:
        with open(yaml_path, "r", encoding="utf-8") as f:
            data = yaml.safe_load(f)
    except yaml.YAMLError as e:
        return [f"YAML parse error: {e}"]
    
    if data is None:
        return [f"Empty YAML file"]
    
    try:
        schema = load_schema(schema_name)
    except (FileNotFoundError, json.JSONDecodeError) as e:
        return [f"Schema error ({schema_name}): {e}"]
    
    # Draft-07 est le format de nos schemas JSON
    validator = jsonschema.Draft7Validator(schema)
    
    # Trier par chemin absolu pour un affichage déterministe des erreurs
    for error in sorted(validator.iter_errors(data), key=lambda e: list(e.absolute_path)):
        path = " → ".join(str(p) for p in error.absolute_path)
        if path:
            errors.append(f"  [{path}] {error.message}")
        else:
            errors.append(f"  {error.message}")
    
    return errors


def find_all_yaml_files() -> list[tuple[Path, str, str]]:
    """Find all YAML files and their corresponding schemas."""
    results = []
    
    for root, dirs, files in os.walk(ROOT):
        # Exclure les répertoires générés ou internes qui ne contiennent pas de données Argus
        dirs[:] = [d for d in dirs if not d.startswith(".") and d != "__pycache__" and d != "schemas"]
        
        for f in files:
            if f.endswith(".yaml") and not f.startswith("."):
                full_path = Path(root) / f
                rel_path = str(full_path.relative_to(ROOT)).replace("\\", "/")
                schema = get_schema_for_file(rel_path)
                if schema:
                    results.append((full_path, rel_path, schema))
    
    return results


def find_staged_yaml_files() -> list[tuple[Path, str, str]]:
    """Find only Git-staged YAML files and their corresponding schemas."""
    results = []
    
    try:
        output = subprocess.check_output(
            ["git", "diff", "--cached", "--name-only", "--diff-filter=ACM"],
            cwd=str(ROOT),
            text=True,
            stderr=subprocess.DEVNULL
        )
    except (subprocess.CalledProcessError, FileNotFoundError):
        return results
    
    for line in output.strip().splitlines():
        line = line.strip()
        if not line.endswith(".yaml"):
            continue
        
        rel_path = line.replace("\\", "/")
        full_path = ROOT / rel_path
        
        if not full_path.exists():
            continue
        
        schema = get_schema_for_file(rel_path)
        if schema:
            results.append((full_path, rel_path, schema))
    
    return results


def main():
    verbose = "--verbose" in sys.argv or "-v" in sys.argv
    staged = "--staged" in sys.argv
    quiet = "--quiet" in sys.argv or "-q" in sys.argv
    
    if quiet:
        verbose = False
    
    if staged:
        files = find_staged_yaml_files()
        if not files:
            if not quiet:
                print("No staged YAML files to validate against schemas.")
            return 0
    else:
        files = find_all_yaml_files()
    
    if not files:
        if not quiet:
            print("No YAML files found to validate.")
        return 0
    
    total_errors = 0
    validated = 0
    
    if not quiet:
        mode = "Staged Files" if staged else "All Files"
        print(f"{'='*70}")
        print(f"  ARGUS — JSON Schema Validation ({mode})")
        print(f"  Schemas: {SCHEMAS_DIR}")
        print(f"{'='*70}\n")
    
    for full_path, rel_path, schema_name in sorted(files):
        errors = validate_file(full_path, schema_name, verbose)
        validated += 1
        
        if errors:
            total_errors += len(errors)
            if not quiet:
                print(f"❌ {rel_path} (schema: {schema_name})")
                for err in errors[:10]:
                    print(f"   {err}")
                if len(errors) > 10:
                    print(f"   ... and {len(errors) - 10} more errors")
                print()
        elif verbose:
            print(f"✅ {rel_path} (schema: {schema_name})")
    
    if not quiet:
        print(f"\n{'='*70}")
        print(f"  Results: {validated} files validated, {total_errors} errors found")
        print(f"{'='*70}")
    
    return 1 if total_errors > 0 else 0


if __name__ == "__main__":
    sys.exit(main())
