import json
import os
import shutil
from pathlib import Path
from typing import List, Dict, Optional
import re

import typer
from rich.console import Console
from rich.prompt import Prompt, Confirm

from project_generator.domain.models.project_models import ProjectRequest
from project_generator.domain.usecases.generation_use_case import GenerateProjectUseCase
from project_generator.domain.usecases.update_use_case import UpdateProjectUseCase
from project_generator.infrastructure.entry_points.cli.parsers import (
    ToolDefinitionParser,
    PromptDefinitionParser,
    ResourceDefinitionParser
)
from project_generator.domain.usecases.restore_use_case import RestoreBackupUseCase
from project_generator.domain.models.project_models import ProjectAnalyzerGateway

class CLIHandlers:
    """Handles CLI operations for project generation."""
    
    def __init__(
        self,
        console: Console,
        generation_use_case: GenerateProjectUseCase = None,
        update_use_case: UpdateProjectUseCase = None,
        restore_use_case: RestoreBackupUseCase = None,
        analyzer: ProjectAnalyzerGateway = None,
        tool_parser: ToolDefinitionParser = None,
        prompt_parser: PromptDefinitionParser = None,
        resource_parser: ResourceDefinitionParser = None
    ):
        self._generation_use_case = generation_use_case
        self._update_use_case = update_use_case
        self._restore_use_case = restore_use_case
        self._analyzer = analyzer
        self._tool_parser = tool_parser
        self._prompt_parser = prompt_parser
        self._resource_parser = resource_parser
        self._console = console
    
    def handle_add(
                self,
                tool_definitions: List[str],
                prompt_definitions: List[str],
                resource_definitions: List[str]
            ) -> None:
                try:
                    self._console.print("Analizando proyecto MCP existente...")

                    tools_to_add = [self._tool_parser.parse(d) for d in tool_definitions]
                    prompts_to_add = [self._prompt_parser.parse(d) for d in prompt_definitions]
                    resources_to_add = [self._resource_parser.parse(d) for d in resource_definitions]

                    if not any([tools_to_add, prompts_to_add, resources_to_add]):
                        self._console.print("Advertencia: No se especificaron componentes para añadir. Usa --tool, --prompt, o --resource.")
                        raise typer.Exit()


                    inventory = self._analyzer.analyze(".")
                    if not inventory.is_valid:
                        self._console.print("Error: No se encontró un proyecto MCP válido en el directorio actual.")
                        raise typer.Exit(code=1)
                    
                    count, warnings, backup_message = self._update_use_case.execute(
                        tools_to_add, prompts_to_add, resources_to_add
                    )

                    if warnings:
                        self._console.print("\nAdvertencias durante la validación:")
                        for warning in warnings:
                            self._console.print(f"- {warning}")

                    self._console.print(f"\n¡Éxito! Se añadieron {count} componente(s) al proyecto.")
                    self._console.print(f"Info Backup: {backup_message}")


                except typer.Exit as e:
                    raise e
                except (ValueError, FileNotFoundError) as e:
                    self._console.print(f"Error: {e}")
                    raise typer.Exit(code=1)
                except RuntimeError as e:
                    self._console.print(f"Error Crítico durante la inyección: {e}")
                    raise typer.Exit(code=1)
                except Exception as e:
                    self._console.print(f"Ocurrió un error inesperado:\n{e}")
                    raise typer.Exit(code=1)
    
    def handle_from_file(self, config_file: Path, output_dir: Path) -> None:
        """Handle project generation from JSON file."""
        self._console.print(f"Reading configuration from: {config_file}")
        
        try:
            with config_file.open("r") as f:
                data = json.load(f)
            
            project_request = ProjectRequest.model_validate(data)
            self._execute_generation(project_request, output_dir, no_zip=False)
            
        except Exception as e:
            self._console.print(f"An unexpected error occurred:\n{e}")
            raise typer.Exit(code=1)
    
    def handle_create(
        self,
        project_name: str,
        tool_definitions: List[str],
        prompt_definitions: List[str],
        resource_definitions: List[str],
        output_dir: Path,
        no_zip: bool
    ) -> None:
        """Handle direct project creation with definitions."""
        try:

            project_name_pattern = r"^[a-z]+(_[a-z]+)*_smcp$"
            if not project_name.endswith("_smcp"):
                self._console.print(f"Error: El nombre del proyecto '{project_name}' debe terminar con el sufijo '_smcp'.")
                raise typer.Exit(code=1)
            if not re.match(project_name_pattern, project_name):
                self._console.print(f"Error: El nombre del proyecto '{project_name}' debe estar en snake_case (minúsculas y guiones bajos) y terminar con '_smcp'.")
                raise typer.Exit(code=1)
            self._console.print(f"Nombre de proyecto '{project_name}' validado.")

            dynamic_tools = [self._tool_parser.parse(d) for d in tool_definitions]
            dynamic_prompts = [self._prompt_parser.parse(d) for d in prompt_definitions]
            dynamic_resources = [self._resource_parser.parse(d) for d in resource_definitions]
           
            self._console.print(f"Creating project '{project_name}' with:")
            self._console.print(f"  - {len(dynamic_tools)} tools")
            self._console.print(f"  - {len(dynamic_prompts)} prompts")
            self._console.print(f"  - {len(dynamic_resources)} resources")

            project_request = ProjectRequest(
                project_name=project_name,
                dynamic_tools=dynamic_tools,
                dynamic_prompts=dynamic_prompts,
                dynamic_resources=dynamic_resources
            )

            self._execute_generation(project_request, output_dir, no_zip)
             
        except ValueError as e:
            self._console.print(f"Error parsing definition: {e}")
            raise typer.Exit(code=1)
        except Exception as e:
            self._console.print(f"An unexpected error occurred:\n{e}")
            raise typer.Exit(code=1)
    
    def handle_interactive(self, output_dir: Path) -> None:
        """Handle interactive project creation."""
        try:
            self._console.print("MCP Project Generator - Interactive Mode\n")
            
            project_name = Prompt.ask("Project name")
            dynamic_tools = self._collect_items_interactively("tool", self._create_tool_interactively)
            dynamic_prompts = self._collect_items_interactively("prompt", self._create_prompt_interactively)
            dynamic_resources = self._collect_items_interactively("resource", self._create_resource_interactively)
            
            self._show_project_summary(project_name, dynamic_tools, dynamic_prompts, dynamic_resources)
            
            if Confirm.ask("\nGenerate project?"):
                project_request = ProjectRequest(
                    project_name=project_name,
                    dynamic_tools=dynamic_tools,
                    dynamic_prompts=dynamic_prompts,
                    dynamic_resources=dynamic_resources
                )
                self._execute_generation(project_request, output_dir, no_zip=False)
            else:
                self._console.print("Project generation cancelled.")
        
        except Exception as e:
            self._console.print(f"An unexpected error occurred:\n{e}")
            raise typer.Exit(code=1)
    
    def _collect_items_interactively(self, item_name: str, creation_func) -> List[dict]:
        """Generic function to collect items (tools, prompts, etc.) interactively."""
        items = []
        self._console.print(f"\n--- {item_name.capitalize()} Definitions ---")
        while True:
            add_item = Confirm.ask(f"Add a {item_name}?")
            if add_item:
                item = creation_func()
                items.append(item)
            else:
                break
        return items

    def _create_tool_interactively(self) -> dict:
        self._console.print("\nCreating a new tool:")
        name = Prompt.ask("Tool name")
        description = Prompt.ask("Tool description")
        params = Prompt.ask("Parameters (e.g., 'location: str')", default="")
        return_type = Prompt.ask("Return type", default="Dict[str, Any]")
        return {"name": name, "description": description, "params": params, "return_type": return_type}

    def _create_prompt_interactively(self) -> dict:
        self._console.print("\nCreating a new prompt:")
        name = Prompt.ask("Prompt name")
        description = Prompt.ask("Prompt description")
        return {"name": name, "description": description}

    def _create_resource_interactively(self) -> dict:
        self._console.print("\nCreating a new resource:")
        name = Prompt.ask("Resource name")
        uri = Prompt.ask("Resource URI (e.g., 'policy://{id}')")
        description = Prompt.ask("Resource description")
        params = Prompt.ask("Parameters (e.g., 'id: str')", default="")
        return_type = Prompt.ask("Return type", default="Dict[str, Any]")
        return {"name": name, "uri": uri, "description": description, "params": params, "return_type": return_type}

    def _show_project_summary(self, project_name: str, tools: List[Dict], prompts: List[Dict], resources: List[Dict]) -> None:
        self._console.print(f"\n Project Summary ")
        self._console.print(f"Project: {project_name}")
        
        self._console.print(f"\n Tools ({len(tools)}):")
        if not tools: self._console.print("   None")
        for i, tool in enumerate(tools, 1): self._console.print(f"  {i}. {tool['name']}")

        self._console.print(f"\n Prompts ({len(prompts)}):")
        if not prompts: self._console.print("   None")
        for i, prompt in enumerate(prompts, 1): self._console.print(f"  {i}. {prompt['name']}")

        self._console.print(f"\n Resources ({len(resources)}):")
        if not resources: self._console.print("   None")
        for i, resource in enumerate(resources, 1): self._console.print(f"  {i}. {resource['name']} ({resource['uri']})")
    
    def _execute_generation(self, project_request: ProjectRequest, output_dir: Path, no_zip: bool) -> None:
            result = None
            temp_dir_to_clean = None
            try:
                self._console.print(f"\nStarting project generation for: '{project_request.project_name}'")

                result = self._generation_use_case.execute(project_request, no_zip)
                temp_dir_to_clean = result.temp_dir

                if result.zip_path:
                    final_zip_path = output_dir / result.zip_filename
                    if final_zip_path.exists():
                        self._console.print(f"Error: File '{final_zip_path}' already exists.")
                        raise typer.Exit(code=1)
                    shutil.move(result.zip_path, final_zip_path)
                    self._console.print(f"\n Success! Project ZIP created at: {final_zip_path}")
                elif result.output_path:
                    source_folder_path = Path(result.output_path)
                    generated_folder_name = source_folder_path.name
                    final_folder_path = output_dir / generated_folder_name

                    if final_folder_path.exists():
                        self._console.print(f"Error: Directory '{final_folder_path}' already exists.")
                        if temp_dir_to_clean and os.path.exists(temp_dir_to_clean):
                            shutil.rmtree(temp_dir_to_clean)
                        raise typer.Exit(code=1)

                    shutil.move(str(source_folder_path), str(final_folder_path))
                    self._console.print(f"\n Success! Project files generated at: {final_folder_path}")
                else:
                    raise RuntimeError("Generation finished but no output path (zip or folder) was provided.")

            except Exception as e:
                self._console.print(f"An unexpected error occurred:\n{e}")
                if temp_dir_to_clean and os.path.exists(temp_dir_to_clean):
                    try:
                        shutil.rmtree(temp_dir_to_clean)
                    except Exception as cleanup_error:
                        self._console.print(f"Error cleaning up temp directory {temp_dir_to_clean}: {cleanup_error}")
                raise typer.Exit(code=1)
            finally:
                if temp_dir_to_clean and os.path.exists(temp_dir_to_clean):
                    try:
                        shutil.rmtree(temp_dir_to_clean)
                    except Exception as cleanup_error:
                        self._console.print(f"Warning: Failed to clean up temp directory {temp_dir_to_clean}: {cleanup_error}")
    
    def handle_restore_backup(self, backup_name: Optional[str], force: bool) -> None:
        try:
            inventory = self._analyzer.analyze(".")
            if not inventory.is_valid:
                self._console.print("Error: No se encontró un proyecto MCP válido en el directorio actual.")
                raise typer.Exit(code=1)

            project_path = inventory.project_path
            available_backups = self._restore_use_case.list_backups(project_path)

            if not available_backups:
                self._console.print("No se encontraron backups disponibles.")
                raise typer.Exit()

            if backup_name is None:
                self._console.print("Backups disponibles (más recientes primero):")
                for i, bk_name in enumerate(available_backups):
                    self._console.print(f"  {i + 1}. {bk_name}")
                self._console.print("\nUsa 'mcp-generator restore-backup <nombre_backup>' para restaurar.")
                return
            else:
                if backup_name not in available_backups:
                    self._console.print(f"Error: El backup '{backup_name}' no existe.")
                    self._console.print("Backups disponibles:")
                    for bk_name in available_backups:
                        self._console.print(f"  - {bk_name}")
                    raise typer.Exit(code=1)

                if not force:
                    confirmed = Confirm.ask(
                        f"¿Estás seguro de que quieres restaurar desde '{backup_name}'?\n"
                        f"Esto reemplazará el contenido actual de la carpeta 'src'.",
                        default=False
                    )
                    if not confirmed:
                        self._console.print("Restauración cancelada.")
                        raise typer.Exit()

                self._console.print(f"Restaurando desde '{backup_name}'...")
                result_message = self._restore_use_case.execute(project_path, backup_name)
                self._console.print(f"¡Éxito! {result_message}")


        except typer.Exit as e:
            raise e
        except (FileNotFoundError, ValueError) as e:
            self._console.print(f"Error: {e}")
            raise typer.Exit(code=1)
        except RuntimeError as e:
            self._console.print(f"Error Crítico durante la restauración: {e}")
            raise typer.Exit(code=1)
        except Exception as e:
            self._console.print(f"Ocurrió un error inesperado:\n{e}")
            raise typer.Exit(code=1)
