"""
@RAG_CONTEXT
{
  "archivo": "src/rag_indexer/main.py",
  "lineas": 228,
  "tokens_estimados": 2600,
  "hash_contenido": "3ee259dad8427397e356e7492a929e0a7738ae255568197a04314baebe847572",
  "creacion": "2026-03-04",
  "modificacion": "2026-03-05",
  "tags": [
    "install_git_hook",
    "argparse",
    "datetime",
    ".hook_manager",
    "COMMENT_SYNTAX",
    "pathspec",
    "os",
    "get_llm_descriptions",
    ".llm_client",
    ".file_parser",
    "stat",
    ".config",
    "json"
  ],
  "descripcion_corta": "",
  "descripcion_larga": ""
}
"""
import os
import stat
import json
import time
import platform
import subprocess
import datetime
import argparse
import pathspec

from .config import COMMENT_SYNTAX, DEFAULT_EXCLUDES, DATA_ONLY_EXTENSIONS, API_KEYS, ACTIVE_LLM
from .file_parser import (
    split_metadata_and_code,
    extract_dependencies,
    calculate_hash,
    inject_metadata,
)
from .llm_client import get_llm_descriptions
from .hook_manager import install_git_hook


def load_gitignore(root_dir):
    patterns = [
        'node_modules/', '.git/', 'venv/', '.env',
        '__pycache__/', '*.min.js', '*.min.css',
    ]
    gitignore_path = os.path.join(root_dir, '.gitignore')
    if os.path.exists(gitignore_path):
        with open(gitignore_path, 'r', encoding='utf-8') as f:
            patterns.extend(f.read().splitlines())
    return pathspec.PathSpec.from_lines(pathspec.patterns.GitWildMatchPattern, patterns)


def is_valid_file(filepath, spec, root_dir):
    rel_path = os.path.relpath(filepath, root_dir)
    if spec.match_file(rel_path):
        return False
    ext = os.path.splitext(filepath)[1].lower()
    if ext in DEFAULT_EXCLUDES or ext in DATA_ONLY_EXTENSIONS or ext not in COMMENT_SYNTAX:
        return False
    return True


def process_single_file(filepath, root_dir, is_scaffold=False):
    if not is_scaffold and not API_KEYS.get(ACTIVE_LLM, ""):
        print(f"⚠️ No se detectó API Key para {ACTIVE_LLM.upper()}. Entrando en modo andamiaje automático...")
        is_scaffold = True

    ext = os.path.splitext(filepath)[1].lower()
    rel_path = os.path.relpath(filepath, root_dir).replace('\\', '/')

    with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
        original_content = f.read()

    old_metadata, clean_code = split_metadata_and_code(original_content, ext)
    code_hash = calculate_hash(clean_code)
    today = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%d")

    metadata = {
        "archivo": rel_path,
        "lineas": len(clean_code.splitlines()),
        "tokens_estimados": len(clean_code) // 4,
        "hash_contenido": code_hash,
        "creacion": old_metadata.get("creacion", today) if old_metadata else today,
        "modificacion": today,
        "tags": extract_dependencies(clean_code, ext),
        "descripcion_corta": old_metadata.get("descripcion_corta", "") if old_metadata else "",
        "descripcion_larga": old_metadata.get("descripcion_larga", "") if old_metadata else "",
    }

    needs_update = not old_metadata or old_metadata.get("hash_contenido") != code_hash or not metadata["descripcion_corta"]

    if needs_update:
        if is_scaffold:
            print(f"  Preparando andamiaje para: {rel_path}")
            metadata["descripcion_corta"] = ""
            metadata["descripcion_larga"] = ""
        else:
            print(f"  Llamando a LLM para contexto de: {rel_path}")
            corta, larga = get_llm_descriptions(clean_code, rel_path)
            metadata["descripcion_corta"], metadata["descripcion_larga"] = corta, larga
    else:
        print(f"  Sin cambios (hash idéntico): {rel_path}")

    inject_metadata(filepath, original_content, clean_code, metadata, ext)
    return metadata


BUENAS_PRACTICAS_CONTENT = """\
# Constitución y Buenas Prácticas de Ingeniería

## 1. CONTROL DE VERSIONES (ESTRICTO)
- NUNCA termines una tarea sin hacer: `git add .` y `git commit -m "tipo(alcance): descripción"`.
- Usa Conventional Commits (`feat:`, `fix:`, `refactor:`, `chore:`).
- Haz commits atómicos y frecuentes. No acumules 50 archivos en un solo commit.

## 2. ARQUITECTURA Y CÓDIGO LIMPIO (CLEAN CODE)
- **Nombres descriptivos:** Las variables y funciones deben explicar qué hacen sin necesidad de comentarios. Evita abreviaturas oscuras (ej. usa `calcular_total_impuestos` en lugar de `calc_tot_imp`).
- **DRY (Don't Repeat Yourself):** Si copias y pegas código más de dos veces, extráelo a una función o componente reutilizable.
- **Funciones Atómicas:** Una función debe hacer UNA sola cosa y hacerla bien.
- **Tipado Estricto:** Usa Type Hints en Python (`def funcion(param: str) -> bool:`) o TypeScript en el frontend siempre que sea posible.

## 3. SEGURIDAD Y MANEJO DE ERRORES
- **Cero Secretos:** NUNCA escribas contraseñas, API Keys o tokens directamente en el código. Usa siempre variables de entorno (`os.getenv()` o `.env`).
- **Manejo de Excepciones:** No uses bloques `try/except` vacíos (silenciar errores). Captura excepciones específicas y registra el error adecuadamente.
- **Cero basura:** Elimina todos los `print()` o `console.log()` de depuración antes de dar la tarea por terminada.

## 4. PLANIFICACIÓN DE CAMBIOS GRANDES (OBLIGATORIO)
- Todo cambio complejo DEBE documentarse antes de programar en: `.ai/planes/{nombre_del_cambio}.json`.
- El JSON DEBE seguir esta estructura exacta:
  {
    "nombre_tarea": "Breve descripción",
    "estado_general": "pendiente | en_progreso | completado",
    "pasos": [
      { "id": 1, "descripcion": "Hacer X", "estado": "pendiente | completado" }
    ],
    "archivos_a_tocar": ["ruta/archivo1.py"],
    "archivos_no_contemplados": [
      { "archivo": "ruta/nuevo.py", "accion": "crear | editar | eliminar", "motivo": "Explicación lógica" }
    ]
  }

## 5. FLUJO DE TRABAJO Y ESTADO ACTIVO
- Por cada sub-cambio (paso) que completes, DEBES abrir el `.json`, cambiar su estado a "completado" y volver a leer todo el plan de acción para no perder el contexto.
- Si necesitas modificar o crear un archivo que no estaba en `archivos_a_tocar`, ESTÁ PROHIBIDO tocarlo sin antes agregarlo al arreglo `archivos_no_contemplados`.
"""


def setup_ai_prompts_folder(project_root):
    prompts_dir = os.path.join(project_root, '.ai', 'prompts')
    planes_dir = os.path.join(project_root, '.ai', 'planes')
    os.makedirs(prompts_dir, exist_ok=True)
    os.makedirs(planes_dir, exist_ok=True)
    rules_file = os.path.join(prompts_dir, '01_buenas_practicas.md')
    if not os.path.exists(rules_file):
        with open(rules_file, 'w', encoding='utf-8') as f:
            f.write(BUENAS_PRACTICAS_CONTENT)
        print(f"✅ Archivo de buenas prácticas creado en: {rules_file}")
    else:
        with open(rules_file, 'w', encoding='utf-8') as f:
            f.write(BUENAS_PRACTICAS_CONTENT)
        print(f"✅ Carpetas .ai/prompts/ y .ai/planes/ listas. Reglas actualizadas.")


def run_indexer(is_scaffold=False):
    project_root = os.getcwd()
    modo = "Andamiaje (Scaffold)" if is_scaffold else "Completo (API LLM)"
    print(f"Iniciando motor de indexación RAG en: {project_root} | Modo: {modo}")

    setup_ai_prompts_folder(project_root)

    spec = load_gitignore(project_root)
    master_tree = []
    total_tokens = 0
    total_files = 0

    for subdir, dirs, files in os.walk(project_root):
        dirs[:] = [
            d for d in dirs
            if not spec.match_file(
                os.path.relpath(os.path.join(subdir, d), project_root) + '/'
            )
        ]
        for file in files:
            filepath = os.path.join(subdir, file)
            if is_valid_file(filepath, spec, project_root):
                try:
                    meta = process_single_file(filepath, project_root, is_scaffold=is_scaffold)
                    master_tree.append(meta)
                    total_tokens += meta['tokens_estimados']
                    total_files += 1
                except Exception as e:
                    print(f"Error en {filepath}: {e}")

    instrucciones_agente = (
        "PROTOCOLO DE INDEXACIÓN AUTÓNOMA (PARA LA IA):\n"
        "Este archivo está bloqueado a nivel de sistema (Read-Only). Tienes prohibido intentar editarlo directamente.\n"
        "Cuando el usuario te pida leer este archivo o actualizar el contexto, DEBES seguir este flujo exacto y de forma autónoma:\n\n"
        "PASO 1: Ejecuta en tu terminal el comando `rag-indexer prepare` y espera a que termine de procesar.\n"
        "PASO 2: Vuelve a leer este archivo JSON (que ahora estará actualizado) y busca los archivos que tengan las descripciones vacías.\n"
        "PASO 3: Analiza esos archivos y escribe las descripciones técnicas DIRECTAMENTE en los bloques @RAG_CONTEXT dentro de los archivos fuente (.py, .js, .html, etc.).\n"
        "PASO 4: Al finalizar de editar todo el código fuente, ejecuta obligatoriamente en tu terminal el comando `rag-indexer run` para sellar el índice.\n"
        "PASO 5: Entrégale al usuario un reporte detallado indicando cuántos archivos analizaste, qué descripciones agregaste y el resumen de salida del indexador.\n\n"
        "IMPORTANTE: Lee obligatoriamente `.ai/prompts/01_buenas_practicas.md`. Si vas a programar código nuevo, crea el plan en `.ai/planes/`, actualiza el estado paso a paso y registra archivos no contemplados."
    )

    master_index = {
        "_INSTRUCCIONES_PARA_EL_AGENTE": instrucciones_agente,
        "proyecto": os.path.basename(project_root) or "Proyecto_RAG",
        "ultima_indexacion": datetime.datetime.now(datetime.timezone.utc).isoformat() + "Z",
        "estadisticas": {
            "total_archivos": total_files,
            "total_tokens_estimados": total_tokens
        },
        "arbol": master_tree
    }

    output_path = os.path.join(project_root, 'rag-indexer.json')
    if os.path.exists(output_path):
        os.chmod(output_path, stat.S_IWRITE | stat.S_IREAD)
    with open(output_path, 'w', encoding='utf-8') as f:
        json.dump(master_index, f, indent=2, ensure_ascii=False)
    os.chmod(output_path, stat.S_IREAD)
    print(f"🔒 Archivo {output_path} bloqueado con éxito (Read-Only).")

    print(f"\nOperación completada. Archivos: {total_files} | Tokens: ~{total_tokens}")


def run_sync():
    """
    Comando interno: escaneo automático con candado de API Key.
    Flujo: verificar clave → detectar cambios → llamar LLM → sellar índice.
    """
    # PASO 1 — Candado de seguridad: verificar API Key en entorno o .env
    from dotenv import load_dotenv
    load_dotenv(os.path.join(os.getcwd(), '.env'))

    api_keys_encontradas = [
        os.environ.get("OPENAI_API_KEY", ""),
        os.environ.get("ANTHROPIC_API_KEY", ""),
        os.environ.get("GEMINI_API_KEY", ""),
        os.environ.get("DEEPSEEK_API_KEY", ""),
    ]
    if not any(api_keys_encontradas):
        print("Auto-escaneo omitido: No se detectó API Key")
        return

    # PASO 2 — Detectar archivos modificados o con descripciones vacías (fase prepare)
    project_root = os.getcwd()
    spec = load_gitignore(project_root)
    archivos_con_cambios = []

    for subdir, dirs, files in os.walk(project_root):
        dirs[:] = [
            d for d in dirs
            if not spec.match_file(
                os.path.relpath(os.path.join(subdir, d), project_root) + '/'
            )
        ]
        for file in files:
            filepath = os.path.join(subdir, file)
            if not is_valid_file(filepath, spec, project_root):
                continue
            try:
                with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
                    content = f.read()
                ext = os.path.splitext(filepath)[1].lower()
                old_metadata, clean_code = split_metadata_and_code(content, ext)
                code_hash = calculate_hash(clean_code)
                needs_update = (
                    not old_metadata
                    or old_metadata.get("hash_contenido") != code_hash
                    or not old_metadata.get("descripcion_corta")
                )
                if needs_update:
                    archivos_con_cambios.append(filepath)
            except Exception:
                pass

    # PASO 3 — Solo si hay archivos con cambios o descripciones vacías, invocar IA
    if not archivos_con_cambios:
        print("Auto-Sync: Sin cambios detectados. Ciclo completado sin trabajo.")
        return

    print(f"Auto-Sync: {len(archivos_con_cambios)} archivo(s) con cambios o descripciones vacías. Invocando IA...")

    # PASO 4 — Sellar el índice (equivalente a `rag-indexer run`)
    run_indexer(is_scaffold=False)


def autosync():
    """
    Activa el Agente de Sincronización Continua con detección híbrida de OS.
    - Linux: inyecta regla en crontab (0 * * * *)
    - Otros (Windows/Mac): inicia daemon Python con bucle infinito de 1 hora
    """
    sistema = platform.system()

    if sistema == 'Linux':
        # Modo Cron: registrar `rag-indexer run-sync` en el crontab del usuario
        cron_job = "0 * * * * rag-indexer run-sync >> /tmp/rag-autosync.log 2>&1\n"

        result = subprocess.run(
            ['crontab', '-l'],
            capture_output=True,
            text=True
        )
        crontab_actual = result.stdout if result.returncode == 0 else ""

        if cron_job.strip() in crontab_actual:
            print("✅ Auto-Sync (Linux Cron): La regla ya estaba registrada. Sin cambios.")
            return

        nuevo_crontab = crontab_actual + cron_job
        subprocess.run(
            ['crontab', '-'],
            input=nuevo_crontab,
            text=True,
            check=True
        )
        print("✅ Auto-Sync activado (Linux Cron): rag-indexer run-sync se ejecutará silenciosamente cada hora.")
        print("   Log disponible en: /tmp/rag-autosync.log")
    else:
        # Modo Daemon: bucle Python bloqueante (requiere terminal abierta)
        print(f"⚠️  Auto-Sync iniciado en modo Daemon ({sistema}).")
        print("   IMPORTANTE: Mantén esta terminal abierta o ejecútala en segundo plano (nohup, screen, etc.).")
        print("   El escaneo automático se realizará cada 1 hora.")
        while True:
            run_sync()
            time.sleep(3600)


def main():
    parser = argparse.ArgumentParser(
        description="RAG Context Indexer - DevTool para optimización de contexto en LLMs"
    )
    subparsers = parser.add_subparsers(dest="command", help="Comandos disponibles")
    subparsers.add_parser("init", help="Instala el Git Pre-commit Hook")
    subparsers.add_parser("prepare", help="Fase 1: Inyecta plantillas vacías (Scaffold) para agentes locales")
    subparsers.add_parser("run", help="Fase 2: Ejecuta escaneo y llama a la API (Flujo normal/Sellado)")
    subparsers.add_parser(
        "autosync",
        help="Activa sincronización continua: Cron en Linux, Daemon en otros OS"
    )
    subparsers.add_parser(
        "run-sync",
        help="[Interno] Ciclo de auto-escaneo con candado de API Key (usado por Cron/Daemon)"
    )

    args = parser.parse_args()

    if args.command == "init":
        install_git_hook()
    elif args.command == "prepare":
        run_indexer(is_scaffold=True)
    elif args.command == "run":
        run_indexer(is_scaffold=False)
    elif args.command == "autosync":
        autosync()
    elif args.command == "run-sync":
        run_sync()
    else:
        parser.print_help()


if __name__ == "__main__":
    main()
