"""CLI autocomplete support for Vidhi configurations.

This module provides shell completion scripts and runtime completion
for bash, zsh, and fish shells.

Usage:
    # Generate completion script for your shell
    eval "$(python -m vidhi.cli_completion bash)"  # For bash
    eval "$(python -m vidhi.cli_completion zsh)"   # For zsh
    python -m vidhi.cli_completion fish > ~/.config/fish/completions/myapp.fish

    # Or use in your application
    from vidhi.cli_completion import generate_completion_script
    script = generate_completion_script(FlatConfig, "myapp", "bash")
"""

from __future__ import annotations

import os
import shlex
import sys
from typing import Any, Dict, List, Optional, Type


def _sanitize_shell_identifier(s: str) -> str:
    """Restrict a string to safe shell identifier characters [a-zA-Z0-9_-]."""
    return "".join(c if c.isalnum() or c in "_-" else "_" for c in s)


def get_cli_arguments(
    config_class_or_type: Type, *, walker=None
) -> List[Dict[str, Any]]:
    """Extract CLI argument information from a config class.

    Args:
        config_class_or_type: The config class to extract arguments from
        walker: Optional ConfigWalker instance. If not provided, one will be created.

    Returns:
        List of argument dictionaries with name, type, help, choices, etc.
    """
    if walker is None:
        from vidhi.nested_config import ConfigWalker

        walker = ConfigWalker(config_class_or_type)

    parser = walker.build_parser()
    arguments = []

    for arg_name, arg in parser.arguments.items():
        arguments.append(
            {
                "name": arg.cli_name,
                "internal_name": arg.name,
                "help": arg.help_text or "",
                "is_bool": arg.is_boolean,
                "is_list": arg.is_list,
                "choices": arg.choices or [],
                "default": arg.default,
            }
        )

    return arguments


def generate_bash_completion(
    config_class: Type,
    command_name: str,
    *,
    walker=None,
) -> str:
    """Generate bash completion script.

    Args:
        config_class: The config class
        command_name: Name of the CLI command
        walker: Optional ConfigWalker instance

    Returns:
        Bash completion script as a string
    """
    command_name = _sanitize_shell_identifier(command_name)
    args = get_cli_arguments(config_class, walker=walker)

    # Build argument list
    all_args = []
    choices_map = {}

    for arg in args:
        cli_name = f"--{arg['name']}"
        all_args.append(cli_name)
        if arg["choices"]:
            choices_map[cli_name] = arg["choices"]
        elif arg["is_bool"]:
            # Boolean arguments take explicit true/false values
            choices_map[cli_name] = ["true", "false"]

    # Generate completion function
    opts_quoted = shlex.quote(" ".join(all_args))
    script = f"""# Bash completion for {command_name}
# Generated by Vidhi CLI completion

_{command_name}_completions() {{
    local cur prev opts
    COMPREPLY=()
    cur="${{COMP_WORDS[COMP_CWORD]}}"
    prev="${{COMP_WORDS[COMP_CWORD-1]}}"

    # All available options
    opts={opts_quoted}

    # Handle argument values with choices
    case "${{prev}}" in
"""

    for arg_name, choices in choices_map.items():
        choices_quoted = shlex.quote(" ".join(str(c) for c in choices))
        script += f"""        {arg_name})
            COMPREPLY=( $(compgen -W {choices_quoted} -- "${{cur}}") )
            return 0
            ;;
"""

    script += f"""        *)
            ;;
    esac

    # Complete option names
    if [[ "${{cur}}" == -* ]]; then
        COMPREPLY=( $(compgen -W "${{opts}}" -- "${{cur}}") )
        return 0
    fi

    # Default to file completion
    COMPREPLY=( $(compgen -f -- "${{cur}}") )
}}

complete -F _{command_name}_completions {command_name}
"""

    return script


def generate_zsh_completion(
    config_class: Type,
    command_name: str,
    *,
    walker=None,
) -> str:
    """Generate zsh completion script.

    Args:
        config_class: The config class
        command_name: Name of the CLI command
        walker: Optional ConfigWalker instance

    Returns:
        Zsh completion script as a string
    """
    command_name = _sanitize_shell_identifier(command_name)
    args = get_cli_arguments(config_class, walker=walker)

    script = f"""#compdef {command_name}
# Zsh completion for {command_name}
# Generated by Vidhi CLI completion

_{command_name}() {{
    local -a opts
    opts=(
"""

    for arg in args:
        cli_name = arg["name"]
        help_text = arg["help"] if arg["help"] else "No description"

        if arg["is_bool"]:
            spec = f"--{cli_name}=[{help_text}]:value:(true false)"
        elif arg["choices"]:
            choices_str = " ".join(str(c) for c in arg["choices"])
            spec = f"--{cli_name}=[{help_text}]:value:({choices_str})"
        else:
            spec = f"--{cli_name}=[{help_text}]:value:_files"

        script += f"        {shlex.quote(spec)}\n"

    script += f"""    )

    _arguments -s $opts
}}

_{command_name} "$@"
"""

    return script


def generate_fish_completion(
    config_class: Type,
    command_name: str,
    *,
    walker=None,
) -> str:
    """Generate fish completion script.

    Args:
        config_class: The config class
        command_name: Name of the CLI command
        walker: Optional ConfigWalker instance

    Returns:
        Fish completion script as a string
    """
    command_name = _sanitize_shell_identifier(command_name)
    args = get_cli_arguments(config_class, walker=walker)

    script = f"""# Fish completion for {command_name}
# Generated by Vidhi CLI completion

# Disable file completion by default
complete -c {command_name} -f

"""

    for arg in args:
        cli_name = arg["name"]
        help_quoted = shlex.quote(arg["help"] if arg["help"] else "No description")

        if arg["is_bool"]:
            script += f"complete -c {command_name} -l {cli_name} -d {help_quoted} -xa 'true false'\n"
        elif arg["choices"]:
            choices_quoted = shlex.quote(" ".join(str(c) for c in arg["choices"]))
            script += f"complete -c {command_name} -l {cli_name} -d {help_quoted} -xa {choices_quoted}\n"
        else:
            # Allow file completion for this argument
            script += f"complete -c {command_name} -l {cli_name} -d {help_quoted} -r\n"

    return script


def generate_completion_script(
    config_class: Type,
    command_name: str,
    shell: str,
    *,
    walker=None,
) -> str:
    """Generate a shell completion script.

    Args:
        config_class: The config class to generate completions for
        command_name: Name of the CLI command to complete
        shell: Shell type - "bash", "zsh", or "fish"
        walker: Optional ConfigWalker instance

    Returns:
        Shell completion script as a string

    Raises:
        ValueError: If shell is not supported
    """
    generators = {
        "bash": generate_bash_completion,
        "zsh": generate_zsh_completion,
        "fish": generate_fish_completion,
    }

    if shell not in generators:
        raise ValueError(
            f"Unsupported shell: {shell}. Supported: {list(generators.keys())}"
        )

    return generators[shell](config_class, command_name, walker=walker)


def detect_shell() -> Optional[str]:
    """Detect the current user's shell.

    Returns:
        Shell name (bash, zsh, fish) or None if unknown
    """
    shell_path = os.environ.get("SHELL", "")
    shell_name = os.path.basename(shell_path)

    if "bash" in shell_name:
        return "bash"
    elif "zsh" in shell_name:
        return "zsh"
    elif "fish" in shell_name:
        return "fish"
    return None


def get_completion_install_path(command_name: str, shell: str) -> str:
    """Get the path where completion script should be installed.

    Args:
        command_name: Name of the CLI command
        shell: Shell type (bash, zsh, fish)

    Returns:
        Path to install the completion script
    """
    command_name = _sanitize_shell_identifier(command_name)
    home = os.path.expanduser("~")

    if shell == "bash":
        # Prefer user-local directory
        local_dir = os.path.join(
            home, ".local", "share", "bash-completion", "completions"
        )
        if os.path.exists(os.path.dirname(local_dir)):
            return os.path.join(local_dir, command_name)
        # Fallback to bashrc sourcing
        return os.path.join(home, ".bash_completion.d", command_name)

    elif shell == "zsh":
        # Check for oh-my-zsh
        omz_dir = os.path.join(home, ".oh-my-zsh", "completions")
        if os.path.exists(os.path.dirname(omz_dir)):
            return os.path.join(omz_dir, f"_{command_name}")
        # Standard zsh completions
        return os.path.join(home, ".zsh", "completions", f"_{command_name}")

    elif shell == "fish":
        return os.path.join(
            home, ".config", "fish", "completions", f"{command_name}.fish"
        )

    raise ValueError(f"Unknown shell: {shell}")


def install_completion(
    config_class: Type,
    command_name: str,
    shell: Optional[str] = None,
    *,
    walker=None,
) -> str:
    """Install shell completion for a CLI command.

    This writes the completion script to the appropriate location for the shell
    and optionally updates shell config files.

    Args:
        config_class: The config class
        command_name: Name of the CLI command
        shell: Shell type (bash, zsh, fish). Auto-detected if None.
        walker: Optional ConfigWalker instance

    Returns:
        Message describing what was installed

    Raises:
        ValueError: If shell cannot be detected or is unsupported
    """
    command_name = _sanitize_shell_identifier(command_name)

    if shell is None:
        shell = detect_shell()
        if shell is None:
            raise ValueError(
                "Could not detect shell. Please specify shell explicitly: "
                "install_completion(command_name, shell='bash')"
            )

    # Generate the completion script
    script = generate_completion_script(
        config_class, command_name, shell, walker=walker
    )

    # Get install path
    install_path = get_completion_install_path(command_name, shell)

    # Create directory if needed
    install_dir = os.path.dirname(install_path)
    os.makedirs(install_dir, exist_ok=True)

    # Delete old file if it exists (ensures clean overwrite)
    if os.path.exists(install_path):
        os.remove(install_path)

    # Write the script
    with open(install_path, "w") as f:
        f.write(script)

    # For bash, we may need to update .bashrc
    result_msg = f"Completion installed to {install_path}"

    if shell == "bash":
        bashrc = os.path.join(os.path.expanduser("~"), ".bashrc")
        source_line = f'[ -f "{install_path}" ] && source "{install_path}"'

        # Check if already sourced
        if os.path.exists(bashrc):
            with open(bashrc, "r") as f:
                if install_path not in f.read():
                    with open(bashrc, "a") as f:
                        f.write(f"\n# {command_name} completion\n{source_line}\n")
                    result_msg += f"\nAdded source line to {bashrc}"
        else:
            result_msg += f"\nAdd this to your .bashrc: {source_line}"
        result_msg += "\n\nTo activate, restart your shell or run: source ~/.bashrc"

    elif shell == "zsh":
        zshrc = os.path.join(os.path.expanduser("~"), ".zshrc")
        fpath_line = f'fpath=("{os.path.dirname(install_path)}" $fpath)'

        if os.path.exists(zshrc):
            with open(zshrc, "r") as f:
                content = f.read()
                if os.path.dirname(install_path) not in content:
                    # Add fpath before compinit
                    result_msg += f"\nAdd to .zshrc (before compinit): {fpath_line}"
        result_msg += "\n\nTo activate, restart your shell or run: source ~/.zshrc"

    elif shell == "fish":
        result_msg += (
            "\n\nFish will automatically load the completion on next shell start."
        )

    return result_msg


def is_completion_installed(command_name: str, shell: Optional[str] = None) -> bool:
    """Check if completion is already installed for a command.

    Args:
        command_name: Name of the CLI command
        shell: Shell to check. Auto-detected if None.

    Returns:
        True if completion script exists at the expected location
    """
    if shell is None:
        shell = detect_shell()
        if shell is None:
            return False

    try:
        install_path = get_completion_install_path(command_name, shell)
        return os.path.exists(install_path)
    except ValueError:
        return False


def print_completion_instructions(command_name: str) -> None:
    """Print instructions for enabling completions."""
    print(f"""
# CLI Completion Setup for {command_name}

## Automatic Installation (Recommended)
    {command_name} --install-completion

## Manual Setup

### Bash
Add to ~/.bashrc:
    eval "$({command_name} --completion bash)"

Or save to a file:
    {command_name} --completion bash > ~/.local/share/bash-completion/completions/{command_name}

### Zsh
Add to ~/.zshrc:
    eval "$({command_name} --completion zsh)"

Or save to completions directory:
    {command_name} --completion zsh > ~/.zsh/completions/_{command_name}

### Fish
Save to completions directory:
    {command_name} --completion fish > ~/.config/fish/completions/{command_name}.fish

After setup, restart your shell or source your config file.
""")


class CompletionMixin:
    """Mixin class to add completion support to config classes.

    Usage:
        script = generate_completion_script(MyConfig, "myapp", "bash")
    """

    @classmethod
    def get_completion_script(cls, command_name: str, shell: str) -> str:
        """Generate completion script for the given shell."""
        return generate_completion_script(cls, command_name, shell)

    @classmethod
    def print_completion(cls, command_name: str, shell: str) -> None:
        """Print completion script to stdout."""
        print(generate_completion_script(cls, command_name, shell))


# =============================================================================
# CLI Entry Point
# =============================================================================

if __name__ == "__main__":
    # Simple CLI for generating completion scripts
    if len(sys.argv) < 2:
        print("Usage: python -m vidhi.cli_completion <shell>")
        print("Shells: bash, zsh, fish")
        print("\nThis generates a generic completion template.")
        print("For application-specific completions, use the API directly.")
        sys.exit(1)

    shell = sys.argv[1].lower()
    if shell not in ("bash", "zsh", "fish"):
        print(f"Unknown shell: {shell}")
        print("Supported: bash, zsh, fish")
        sys.exit(1)

    print_completion_instructions("your-app")
