"""
Advanced File Editor Skill - Edición avanzada de archivos con validación de race conditions y confirmación.

Esta skill está migrada desde `kogniterm/core/tools/advanced_file_editor_tool.py`.
Provee una función `advanced_file_editor` que implementa la lógica de edición y devuelve
un diccionario con el resultado o, en caso de requerir confirmación, la información del diff.
"""

import os
import difflib
import re
import logging
from typing import Optional, Dict, Any

from kogniterm.core.race_condition_guard import RaceConditionGuard, RaceConditionDetected

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _read_file_content(path: str) -> Dict[str, Any]:
    """Lee el contenido del archivo indicado.

    Devuelve un dict con `status` y `content` o `message` en caso de error.
    """
    try:
        if os.path.exists(path):
            with open(path, "r", encoding="utf-8") as f:
                return {"status": "success", "content": f.read()}
        else:
            return {"status": "error", "message": f"El archivo '{path}' no fue encontrado."}
    except Exception as e:
        return {"status": "error", "message": f"Error al leer el archivo '{path}': {e}"}


def _apply_advanced_update_with_validation(path: str, content: str) -> Dict[str, Any]:
    """Escribe el nuevo contenido en *path* validando race conditions.

    Utiliza el `RaceConditionGuard` del core para asegurarse de que el archivo no
    haya sido modificado entre la lectura y la escritura.
    """
    llm_service = globals().get("llm_service", None)
    agent_state = getattr(llm_service, "_current_agent_state", None) if llm_service else None

    if agent_state and os.path.exists(path):
        try:
            with open(path, "r", encoding="utf-8") as f:
                current_content = f.read()
            is_safe, message = RaceConditionGuard.validate_write(
                agent_state, path, current_content
            )
            if not is_safe:
                raise RaceConditionDetected(message)
        except Exception as e:
            logger.warning(f"Race condition validation skipped: {e}")

    try:
        with open(path, "w", encoding="utf-8") as f:
            f.write(content)
        if agent_state:
            RaceConditionGuard.register_write(agent_state, path, content)
        return {
            "status": "success",
            "path": path,
            "message": f"Archivo '{path}' actualizado exitosamente.",
        }
    except Exception as e:
        return {"status": "error", "path": path, "message": f"Error al aplicar la actualización: {e}"}


# ---------------------------------------------------------------------------
# Main skill function
# ---------------------------------------------------------------------------

def advanced_file_editor(
    path: str,
    action: str,
    content: Optional[str] = None,
    line_number: Optional[int] = None,
    regex_pattern: Optional[str] = None,
    replacement_content: Optional[str] = None,
    __internal_confirmed__: bool = False,
    **kwargs
) -> Dict[str, Any]:
    """Ejecuta la acción solicitada sobre *path*.
    
    El parámetro __internal_confirmed__ NO está disponible para el LLM y se usa 
    solo para llamadas de retorno tras la aprobación manual del usuario.
    """
    path = path.strip().replace('@', '')
    
    # Si viene con confirmación interna, aplicamos el contenido pre-calculado
    if __internal_confirmed__:
        new_content = kwargs.get("new_content")
        if new_content is not None:
            return _apply_advanced_update_with_validation(path, new_content)
        return {"error": "Falta 'new_content' para aplicar la edición confirmada."}
    logger.debug(
        f"AdvancedFileEditorSkill invoked: path={path}, action={action}, confirmed={__internal_confirmed__}"
    )

    # Lectura del archivo original
    read_result = _read_file_content(path)
    if read_result["status"] == "error":
        return {"error": read_result.get("message", "Error desconocido")}

    original_content = read_result["content"]
    original_lines = original_content.splitlines(keepends=True)
    modified_lines = list(original_lines)

    # ---------------------------------------------------------------------
    # Aplicar la transformación solicitada
    # ---------------------------------------------------------------------
    if action == "insert_line":
        # Aseguramos que line_number sea un entero, intentando la conversión si es necesario.
        try:
            if line_number is not None:
                line_number = int(line_number)
        except (ValueError, TypeError):
             return {"error": f"line_number debe ser un entero válido (o una cadena numérica), se recibió: {line_number} (tipo: {type(line_number).__name__})"}

        if line_number is None or line_number < 1:
            return {"error": "line_number debe ser un entero positivo (basado en 1) para 'insert_line'."}
            
        if content is None:
            return {"error": "El 'content' no puede ser None para 'insert_line'."}
        insert_idx = line_number - 1
        insert_content = content if content.endswith("\n") else content + "\n"
        if insert_idx > len(modified_lines):
            modified_lines.append(insert_content)
        else:
            modified_lines.insert(insert_idx, insert_content)

    elif action == "replace_regex":
        if not regex_pattern or replacement_content is None:
            return {"error": "Se requieren 'regex_pattern' y 'replacement_content' para 'replace_regex'."}
        try:
            # Validar el patrón compilándolo
            re.compile(regex_pattern)
            
            # Intentamos usar el replacement tal cual
            safe_replacement = replacement_content
            try:
                # Hacemos una prueba de sustitución mínima para validar escapes en el reemplazo
                re.sub(regex_pattern, replacement_content, "", count=1)
            except (re.error, Exception) as e:
                # Si falla por escapes inválidos (ej: \d, \w que no son válidos en replacement strings),
                # intentamos un escape automático de barras invertidas que NO parecen backreferences (\1)
                # ni escapes standard (\n, \t, etc) ni la letra 'g' para \g<name>.
                logger.warning(f"Contenido de reemplazo inválido en regex, intentando escape: {e}")
                # Escapar \ que no vayan seguidas de: dígitos, g, n, r, t, v, f, b o otra \
                safe_replacement = re.sub(r'\\(?![0-9gnrtvfb\\])', r'\\\\', replacement_content)
                
            modified_content_str = re.sub(regex_pattern, safe_replacement, original_content)
            modified_lines = modified_content_str.splitlines(keepends=True)
        except re.error as e:
            return {"error": f"Error de expresión regular inválida en el patrón: {e}"}
        except Exception as e:
            return {"error": f"Error al aplicar regex: {e}. Sugerencia: Para reemplazos literales usa la acción 'multi_replace' que es más segura."}

    elif action == "prepend_content":
        if content is None:
            return {"error": "El 'content' no puede ser None para 'prepend_content'."}
        prepend_content = content if content.endswith("\n") else content + "\n"
        modified_lines.insert(0, prepend_content)

    elif action == "append_content":
        if content is None:
            return {"error": "El 'content' no puede ser None para 'append_content'."}
        append_content = content if content.endswith("\n") else content + "\n"
        modified_lines.append(append_content)

    elif action == "multi_replace":
        replacement_chunks = kwargs.get("replacement_chunks")
        if not replacement_chunks:
            return {"error": "Se requiere 'replacement_chunks' para la acción 'multi_replace'."}
            
        # Convertir a lista de líneas para trabajar más cómodamente con rangos de líneas
        current_lines = original_content.splitlines(keepends=True)
        
        # Para evitar problemas con índices que cambian al reemplazar, 
        # primero validamos todos los chunks y luego aplicamos de abajo hacia arriba 
        # o usamos un enfoque de reconstrucción.
        
        # Ordenar chunks por StartLine descendente para que los cambios al final no afecten los índices de arriba
        try:
            sorted_chunks = sorted(replacement_chunks, key=lambda x: x.get('start_line', 0), reverse=True)
        except Exception as e:
            return {"error": f"Error al procesar chunks: {e}"}

        new_lines = list(current_lines)
        
        for chunk in sorted_chunks:
            target = chunk.get("target_content")
            replacement = chunk.get("replacement_content")
            start = chunk.get("start_line")
            end = chunk.get("end_line")
            
            if target is None or replacement is None or start is None or end is None:
                return {"error": "Cada chunk en 'replacement_chunks' debe tener 'target_content', 'replacement_content', 'start_line' y 'end_line'."}
            
            # Ajustar a índices de Python (0-based)
            s_idx = start - 1
            e_idx = end # end es inclusivo en lineas, pero en slice [s:e] e no se incluye, así que es perfecto si end es la última línea
            
            # Extraer el bloque de líneas del archivo original
            if s_idx < 0 or e_idx > len(current_lines):
                return {"error": f"Rango de líneas [{start}, {end}] fuera de límites para el archivo (total líneas: {len(current_lines)})."}
                
            actual_block = "".join(current_lines[s_idx:e_idx])
            
            if target != actual_block:
                # Intento de ser flexible con espacios en blanco finales si no coincide exactamente
                if target.strip() == actual_block.strip():
                    logger.warning(f"Aviso: El target_content coincide con el bloque tras strip(), aplicando de todos modos.")
                else:
                    return {
                        "error": f"El contenido objetivo (target_content) no coincide con las líneas {start}-{end} en el archivo.",
                        "expected": target,
                        "found": actual_block,
                        "start_line": start,
                        "end_line": end
                    }
            
            # Realizar el reemplazo en el buffer temporal
            # Reemplazamos el rango [s_idx:e_idx] con las líneas del replacement
            replacement_lines = replacement.splitlines(keepends=True)
            new_lines[s_idx:e_idx] = replacement_lines

        modified_lines = new_lines

    else:
        return {"error": f"Acción '{action}' no soportada. Las acciones válidas son 'insert_line', 'replace_regex', 'prepend_content', 'append_content', 'multi_replace'."}

    new_content = "".join(modified_lines)

    # Con el flujo de seguridad, ya no permitimos escribir directamente aquí basándonos
    # en un parámetro que el LLM podría manipular. La lógica de aplicación real está
    # al principio de la función, activada por __internal_confirmed__ (que NO está en el schema).

    # Generar diff para que el LLM lo presente al usuario.
    diff = "".join(
        difflib.unified_diff(
            original_content.splitlines(keepends=True),
            new_content.splitlines(keepends=True),
            fromfile=f"a/{path}",
            tofile=f"b/{path}",
        )
    )

    if not diff:
        return {"status": "success", "message": f"El archivo '{path}' no requirió cambios para la acción '{action}'."}

    # Devolver la información necesaria para que la capa superior solicite confirmación.
    return {
        "status": "requires_confirmation",
        "action_description": f"aplicar edición avanzada en el archivo '{path}'",
        "operation": "advanced_file_editor",
        "args": {
            "path": path,
            "action": action,
            "content": content,
            "line_number": line_number,
            "regex_pattern": regex_pattern,
            "replacement_content": replacement_content,
            "replacement_chunks": kwargs.get("replacement_chunks"),
            "__internal_confirmed__": True,
            "new_content": new_content,
        },
        "diff": diff,
    }


def get_action_description(path: str, action: str, **kwargs) -> str:
    """Devuelve una descripción legible de la acción que realiza la herramienta."""
    path = path.strip().replace('@', '')
    if action == "insert_line":
        return f"Insertando línea en {path}..."
    elif action == "replace_regex":
        return f"Reemplazando contenido con regex en {path}..."
    elif action == "prepend_content":
        return f"Añadiendo contenido al inicio de {path}..."
    elif action == "append_content":
        return f"Añadiendo contenido al final de {path}..."
    elif action == "multi_replace":
        return f"Aplicando múltiples ediciones en {path}..."
    return f"Editando archivo {path}..."


# ---------------------------------------------------------------------------
# Parámetros del LLM (schema JSON)
# ---------------------------------------------------------------------------

parameters_schema = {
    "type": "object",
    "properties": {
        "path": {"type": "string", "description": "Ruta del archivo a editar"},
        "action": {
            "type": "string",
            "description": "Acción a realizar",
            "enum": ["insert_line", "replace_regex", "prepend_content", "append_content", "multi_replace"],
        },
        "content": {"type": "string", "description": "Contenido a insertar o añadir"},
        "line_number": {"type": "integer", "description": "Número de línea (1‑based) para insert_line"},
        "regex_pattern": {"type": "string", "description": "Patrón regex para replace_regex"},
        "replacement_content": {"type": "string", "description": "Contenido de reemplazo para replace_regex"},
        "replacement_chunks": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "target_content": {"type": "string", "description": "Contenido exacto a reemplazar"},
                    "replacement_content": {"type": "string", "description": "Nuevo contenido"},
                    "start_line": {"type": "integer", "description": "Línea inicial (1-based)"},
                    "end_line": {"type": "integer", "description": "Línea final (1-based, inclusiva)"}
                },
                "required": ["target_content", "replacement_content", "start_line", "end_line"]
            },
            "description": "Lista de chunks para la acción 'multi_replace'"
        }
    },
    "required": ["path", "action"],
}

