"""
Interactive CLI module for cl-preset package.

Provides beautiful interactive prompts for strategy selection and installation.
"""

from pathlib import Path
from typing import TYPE_CHECKING

import questionary
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from rich.text import Text

from cl_preset.git_strategy import GitStrategyManager
from cl_preset.manager import PresetManager
from cl_preset.models import PresetMetadata, PresetScope, PresetType

if TYPE_CHECKING:
    pass


class TUIError(Exception):
    """Exception raised when TUI fails to initialize or run."""

    pass


class InteractiveCLI:
    """Interactive CLI for strategy selection and installation."""

    # Scope mapping
    SCOPE_MAP = {
        "u": PresetScope.USER,
        "p": PresetScope.PROJECT,
        "user": PresetScope.USER,
        "project": PresetScope.PROJECT,
    }

    SCOPE_LABELS = {
        PresetScope.USER: "user",
        PresetScope.PROJECT: "project",
    }

    def __init__(self) -> None:
        """Initialize the interactive CLI."""
        self.console = Console()
        self.preset_manager = PresetManager()
        self.git_manager = GitStrategyManager()

    def show_welcome(self) -> None:
        """Show welcome message with styled panel."""
        welcome_text = Text()
        welcome_text.append("Welcome to ", style="white")
        welcome_text.append("cl-preset", style="bold cyan")
        welcome_text.append("! ", style="white")
        welcome_text.append("Interactive Strategy Management", style="dim")

        panel = Panel(
            welcome_text,
            border_style="cyan",
            padding=(1, 2),
        )
        self.console.print(panel)
        self.console.print()

    def get_available_strategies(self) -> list[dict]:
        """
        Get all available strategies (presets, git strategies, agents, skills, rules).

        Returns:
            List of strategy dictionaries with name, type, and description
        """
        strategies = []

        # Add built-in presets from examples directory
        examples_path = Path(__file__).parent.parent.parent / "examples" / "presets"
        if examples_path.exists():
            for preset_dir in examples_path.iterdir():
                if preset_dir.is_dir():
                    readme_path = preset_dir / "README.md"
                    description = ""
                    if readme_path.exists():
                        description = readme_path.read_text()
                        # Extract first line after title
                        for line in description.split("\n")[1:10]:
                            line = line.strip()
                            if line and not line.startswith("#"):
                                description = line
                                break

                    strategies.append(
                        {
                            "name": preset_dir.name,
                            "type": "preset",
                            "description": description or f"Preset from {preset_dir.name}",
                            "path": preset_dir,
                        }
                    )

        # Add git strategies
        git_strategies = self.git_manager.list_strategies(include_builtin=True)
        for strategy in git_strategies:
            strategies.append(
                {
                    "name": strategy["name"],
                    "type": "git-strategy",
                    "description": strategy["description"],
                    "source": strategy.get("source", "builtin"),
                }
            )

        # Discover agents from ~/.claude/agents/
        agents_path = Path.home() / ".claude" / "agents"
        if agents_path.exists():
            for agent_file in agents_path.glob("*.md"):
                name = agent_file.stem
                # Extract description from frontmatter
                description = self._extract_frontmatter_description(agent_file)
                strategies.append(
                    {
                        "name": name,
                        "type": "agent",
                        "description": description or f"Agent: {name}",
                        "path": agent_file,
                        "source": "user",
                    }
                )

        # Discover agents from project .claude/agents/
        project_agents_path = Path.cwd() / ".claude" / "agents"
        if project_agents_path.exists():
            for agent_file in project_agents_path.glob("*.md"):
                name = agent_file.stem
                # Skip if already added from user scope
                if any(s["name"] == name and s["type"] == "agent" for s in strategies):
                    continue
                description = self._extract_frontmatter_description(agent_file)
                strategies.append(
                    {
                        "name": name,
                        "type": "agent",
                        "description": description or f"Agent: {name}",
                        "path": agent_file,
                        "source": "project",
                    }
                )

        # Discover skills from ~/.claude/skills/
        skills_path = Path.home() / ".claude" / "skills"
        if skills_path.exists():
            for skill_file in skills_path.glob("*.md"):
                name = skill_file.stem
                description = self._extract_frontmatter_description(skill_file)
                strategies.append(
                    {
                        "name": name,
                        "type": "skill",
                        "description": description or f"Skill: {name}",
                        "path": skill_file,
                        "source": "user",
                    }
                )

        # Discover skills from project .claude/skills/
        project_skills_path = Path.cwd() / ".claude" / "skills"
        if project_skills_path.exists():
            for skill_file in project_skills_path.glob("*.md"):
                name = skill_file.stem
                if any(s["name"] == name and s["type"] == "skill" for s in strategies):
                    continue
                description = self._extract_frontmatter_description(skill_file)
                strategies.append(
                    {
                        "name": name,
                        "type": "skill",
                        "description": description or f"Skill: {name}",
                        "path": skill_file,
                        "source": "project",
                    }
                )

        # Discover rules from ~/.claude/rules/
        rules_path = Path.home() / ".claude" / "rules"
        if rules_path.exists():
            for category_dir in rules_path.iterdir():
                if category_dir.is_dir():
                    category = category_dir.name
                    for rule_file in category_dir.glob("*.md"):
                        name = rule_file.stem
                        description = self._extract_frontmatter_description(rule_file)
                        strategies.append(
                            {
                                "name": f"{category}/{name}",
                                "type": "rule",
                                "description": description or f"Rule: {category}/{name}",
                                "path": rule_file,
                                "source": "user",
                            }
                        )

        # Discover rules from project .claude/rules/
        project_rules_path = Path.cwd() / ".claude" / "rules"
        if project_rules_path.exists():
            for category_dir in project_rules_path.iterdir():
                if category_dir.is_dir():
                    category = category_dir.name
                    for rule_file in category_dir.glob("*.md"):
                        name = rule_file.stem
                        full_name = f"{category}/{name}"
                        if any(s["name"] == full_name and s["type"] == "rule" for s in strategies):
                            continue
                        description = self._extract_frontmatter_description(rule_file)
                        strategies.append(
                            {
                                "name": full_name,
                                "type": "rule",
                                "description": description or f"Rule: {category}/{name}",
                                "path": rule_file,
                                "source": "project",
                            }
                        )

        return strategies

    def _extract_frontmatter_description(self, file_path: Path) -> str:
        """Extract description from YAML frontmatter."""
        try:
            content = file_path.read_text()
            if content.startswith("---"):
                parts = content.split("---", 2)
                if len(parts) >= 2:
                    frontmatter = parts[1]
                    # Look for description field
                    for line in frontmatter.split("\n"):
                        if line.startswith("description:"):
                            # Extract description value
                            desc = line.split(":", 1)[1].strip()
                            # Remove quotes if present
                            desc = desc.strip('"').strip("'")
                            return desc
        except Exception:
            pass
        return ""

    def show_strategy_table(self, strategies: list[dict]) -> None:
        """
        Display strategies in a table format with scope selection guide.

        Args:
            strategies: List of strategy dictionaries
        """
        table = Table(title="Available Strategies")
        table.add_column("#", style="bold cyan", width=4)
        table.add_column("Type", style="yellow", width=10)
        table.add_column("Name", style="green")
        table.add_column("Description", style="dim", width=50)

        for idx, strategy in enumerate(strategies, 1):
            type_label = self._get_type_label(strategy["type"])
            desc = strategy["description"]
            if len(desc) > 47:
                desc = desc[:47] + "..."
            table.add_row(str(idx), type_label, strategy["name"], desc)

        self.console.print(table)

        # Show scope selection guide
        guide_panel = Panel(
            "[bold]Selection Format:[/bold]\n"
            "  [cyan]<number><scope>[/cyan] - e.g., [yellow]1u[/yellow], [yellow]2p[/yellow]\n"
            "  [cyan]Multiple selections:[/cyan] - e.g., [yellow]1u 2p 3u[/yellow]\n\n"
            "[bold]Scope Codes:[/bold]\n"
            "  [cyan]u[/cyan] = user    (User-level, recommended)\n"
            "  [cyan]p[/cyan] = project (Project-specific)\n\n"
            "[dim]Example: [yellow]1u 2p[/yellow] installs 1 to user, 2 to project[/dim]",
            title="Selection Guide",
            border_style="cyan",
        )
        self.console.print(guide_panel)

    def _get_type_label(self, strategy_type: str) -> str:
        """Get formatted label for strategy type."""
        labels = {
            "preset": "[Preset]",
            "git-strategy": "[Git]",
            "agent": "[Agent]",
            "skill": "[Skill]",
            "rule": "[Rule]",
        }
        return labels.get(strategy_type, f"[{strategy_type}]")

    def parse_selections(
        self, input_str: str, strategies: list[dict]
    ) -> list[tuple[dict, PresetScope]] | None:
        """
        Parse user selection input.

        Args:
            input_str: User input string (e.g., "1u 2p")
            strategies: List of available strategies

        Returns:
            List of (strategy, scope) tuples or None if invalid
        """
        if not input_str.strip():
            return None

        selections = []
        parts = input_str.strip().split()

        for part in parts:
            if len(part) < 2:
                self.console.print(f"[red]Invalid selection: {part}[/red]")
                return None

            # Parse number and scope
            num_part = part.rstrip("up")
            scope_part = part[-1].lower()

            try:
                idx = int(num_part) - 1
                if idx < 0 or idx >= len(strategies):
                    self.console.print(f"[red]Invalid strategy number: {num_part}[/red]")
                    return None

                scope = self.SCOPE_MAP.get(scope_part)
                if scope is None:
                    self.console.print(f"[red]Invalid scope: {scope_part}[/red]")
                    return None

                selections.append((strategies[idx], scope))

            except ValueError:
                self.console.print(f"[red]Invalid selection format: {part}[/red]")
                return None

        return selections

    def select_strategies_with_scope(self) -> list[tuple[dict, PresetScope]] | None:
        """
        Show interactive table-based strategy and scope selection.

        Returns:
            List of (strategy, scope) tuples or None if cancelled
        """
        strategies = self.get_available_strategies()

        if not strategies:
            self.console.print("[yellow]No strategies available.[/yellow]")
            return None

        # Show table
        self.show_strategy_table(strategies)

        # Get user input
        self.console.print()
        user_input = questionary.text(
            "Enter your selections (or press Enter to cancel):",
            qmark=">",
        ).ask()

        if user_input is None or user_input.strip() == "":
            return None

        # Parse selections
        selections = self.parse_selections(user_input, strategies)
        return selections

    def select_scope(self) -> PresetScope | None:
        """
        Show interactive scope selection prompt.

        Returns:
            Selected PresetScope or None if cancelled
        """
        scope_choices = [
            questionary.Choice(
                title="user     (User-level installation)",
                value="user",
                description="Install for current user only (recommended)",
            ),
            questionary.Choice(
                title="project  (Project-specific installation)",
                value="project",
                description="Install for current project only",
            ),
            questionary.Choice(title="Cancel", value="cancel"),
        ]

        selected_scope = questionary.select(
            "Select installation scope:",
            choices=scope_choices,
            qmark=">",
            pointer=">",
            default="user",
        ).ask()

        if selected_scope == "cancel" or selected_scope is None:
            return None

        return PresetScope(selected_scope)

    def install_preset(self, strategy: dict, scope: PresetScope) -> bool:
        """
        Install a preset strategy.

        Args:
            strategy: Strategy dictionary
            scope: Installation scope

        Returns:
            True if successful, False otherwise
        """
        from cl_preset.manager import Preset

        if "path" not in strategy:
            self.console.print("[red]Strategy path not found.[/red]")
            return False

        source_path = strategy["path"]

        # Try to read preset metadata
        metadata_path = source_path / "preset.json"
        if metadata_path.exists():
            import json

            metadata_data = json.loads(metadata_path.read_text())
            metadata = PresetMetadata(**metadata_data)
        else:
            # Create metadata from directory name
            metadata = PresetMetadata(
                name=strategy["name"],
                version="1.0.0",
                description=strategy["description"],
                preset_type=PresetType.STRATEGY,
            )

        preset = Preset(
            config={
                "metadata": metadata,
                "source_path": source_path,
                "scope": scope,
            }
        )

        return self.preset_manager.install(preset)

    def apply_git_strategy(self, strategy: dict, scope: PresetScope) -> bool:
        """
        Apply a git strategy.

        Args:
            strategy: Strategy dictionary
            scope: Installation scope (for git strategies, affects where config is written)

        Returns:
            True if successful, False otherwise
        """
        import subprocess

        name = strategy["name"]
        git_strategy = self.git_manager.get_strategy(name)

        if git_strategy is None:
            self.console.print(f"[red]Strategy '{name}' not found.[/red]")
            return False

        self.console.print(f"[cyan]Applying git strategy: {git_strategy.name}[/cyan]")
        self.console.print(f"[dim]{git_strategy.description}[/dim]")
        self.console.print()

        # Check if we're in a git repository
        try:
            result = subprocess.run(
                ["git", "rev-parse", "--git-dir"],
                capture_output=True,
                text=True,
                cwd=Path.cwd(),
            )
            if result.returncode != 0:
                self.console.print(
                    "[red]Not in a git repository. Initialize one first with 'git init'[/red]"
                )
                return False

        except Exception as e:
            self.console.print(f"[red]Error checking git repository: {e}[/red]")
            return False

        # Create .moai/config/sections directory
        config_dir = Path.cwd() / ".moai" / "config" / "sections"
        config_dir.mkdir(parents=True, exist_ok=True)

        # Export strategy config
        strategy_file = config_dir / "git-strategy.yaml"
        success = self.git_manager.export_strategy_yaml(name, strategy_file)

        if success:
            self.console.print()
            self.console.print(
                Panel(
                    f"[green]Strategy configuration written to {strategy_file}[/green]\n\n"
                    f"Main branch: [cyan]{git_strategy.main_branch}[/cyan]\n"
                    f"Feature pattern: [cyan]{git_strategy.branch_patterns.feature}[/cyan]",
                    title="Git Strategy Applied",
                    border_style="green",
                )
            )
            return True

        return False

    def run(self) -> None:
        """Run the interactive CLI flow."""
        self.show_welcome()

        # Step 1: Select strategies with scopes (table-based)
        selections = self.select_strategies_with_scope()
        if selections is None or len(selections) == 0:
            self.console.print("[dim]Cancelled.[/dim]")
            return

        self.console.print()

        # Step 2: Confirm and install
        self.console.print("[cyan]Selected installations:[/cyan]")
        for strategy, scope in selections:
            scope_label = self.SCOPE_LABELS[scope]
            self.console.print(
                f"  [cyan]{strategy['name']}[/cyan] -> [yellow]{scope_label}[/yellow]"
            )
        self.console.print()

        confirm = questionary.confirm(
            "Proceed with installation?",
            default=True,
            qmark="?",
        ).ask()

        if confirm is None or not confirm:
            self.console.print("[dim]Cancelled.[/dim]")
            return

        self.console.print()

        # Step 3: Install/Apply all selected strategies
        results = []
        for strategy, scope in selections:
            strategy_type = strategy.get("type", "")
            strategy_name = strategy["name"]
            scope_label = self.SCOPE_LABELS[scope]

            if strategy_type == "preset":
                self.console.print(
                    f"[cyan]Installing {strategy_name} to {scope_label} scope...[/cyan]"
                )
                success = self.install_preset(strategy, scope)
            else:  # git-strategy
                self.console.print(
                    f"[cyan]Applying git strategy {strategy_name} to {scope_label} scope...[/cyan]"
                )
                success = self.apply_git_strategy(strategy, scope)

            results.append(
                {
                    "name": strategy_name,
                    "type": strategy_type,
                    "scope": scope_label,
                    "success": success,
                }
            )
            self.console.print()

        # Step 4: Show summary
        successful = [r for r in results if r["success"]]
        failed = [r for r in results if not r["success"]]

        if len(successful) == len(results):
            # All successful
            success_text = "[bold green]All strategies installed successfully![/bold green]\n\n"
            for r in results:
                success_text += f"  [cyan]{r['name']}[/cyan] -> [yellow]{r['scope']}[/yellow] ([dim]{r['type']}[/dim])\n"

            self.console.print(
                Panel(
                    success_text,
                    title=f"Success ({len(successful)}/{len(results)})",
                    border_style="green",
                )
            )
        elif len(successful) > 0:
            # Partial success
            summary_text = f"[bold yellow]Partial success: {len(successful)}/{len(results)} installed[/bold yellow]\n\n"

            summary_text += "[green]Successfully installed:[/green]\n"
            for r in successful:
                summary_text += f"  [cyan]{r['name']}[/cyan] -> [yellow]{r['scope']}[/yellow]\n"

            summary_text += "\n[red]Failed to install:[/red]\n"
            for r in failed:
                summary_text += f"  [cyan]{r['name']}[/cyan]\n"

            self.console.print(
                Panel(
                    summary_text,
                    title="Partial Success",
                    border_style="yellow",
                )
            )
        else:
            # All failed
            self.console.print(
                Panel(
                    "[bold red]All installations failed.[/bold red]\n\n"
                    "Please check the error messages above and try again.",
                    title="Error",
                    border_style="red",
                )
            )

    def run_tui(self) -> None:
        """Run the Textual TUI for strategy selection.

        This method launches the Textual TUI app for visual strategy selection.
        Falls back to text-based input if TUI fails.

        Raises:
            TUIError: If TUI fails to initialize or run
        """
        try:
            from cl_preset.tui import StrategyTUI

            # Create and run the TUI app
            app = StrategyTUI()
            app.run()

        except ImportError as e:
            # Textual not installed or import error
            self.console.print(
                f"[yellow]TUI not available: {e}[/yellow]\n"
                "[dim]Falling back to text-based interface...[/dim]"
            )
            self.run()
        except Exception as e:
            # Other TUI errors
            self.console.print(
                f"[yellow]TUI error: {e}[/yellow]\n"
                "[dim]Falling back to text-based interface...[/dim]"
            )
            self.run()

    def run_with_fallback(self, use_tui: bool = True) -> None:
        """Run the interactive CLI with TUI as default, text as fallback.

        Args:
            use_tui: Whether to try TUI first (default: True)
        """
        if use_tui:
            self.run_tui()
        else:
            self.run()
