import importlib.util
import os
import re
import sys
from typing import Any

from . import _click
from ._click.shell_completion import CompletionItem, ShellComplete, add_completion_class
from ._click.shell_completion import split_arg_string as click_split_arg_string
from ._completion_shared import (
    COMPLETION_SCRIPT_BASH,
    COMPLETION_SCRIPT_FISH,
    COMPLETION_SCRIPT_POWER_SHELL,
    COMPLETION_SCRIPT_ZSH,
    Shells,
)


def _sanitize_help_text(text: str) -> str:
    """Sanitizes the help text by removing rich tags"""
    if not importlib.util.find_spec("rich"):
        return text
    from . import rich_utils

    return rich_utils.rich_render_text(text)


class BashComplete(ShellComplete):
    name = Shells.bash.value
    source_template = COMPLETION_SCRIPT_BASH

    def source_vars(self) -> dict[str, Any]:
        return {
            "complete_func": self.func_name,
            "autocomplete_var": self.complete_var,
            "prog_name": self.prog_name,
        }

    def get_completion_args(self) -> tuple[list[str], str]:
        cwords = click_split_arg_string(os.environ["COMP_WORDS"])
        cword = int(os.environ["COMP_CWORD"])
        args = cwords[1:cword]

        try:
            incomplete = cwords[cword]
        except IndexError:
            incomplete = ""

        return args, incomplete

    def format_completion(self, item: CompletionItem) -> str:
        # TODO: Explore replicating the new behavior from Click, with item types and
        # triggering completion for files and directories
        # return f"{item.type},{item.value}"
        return f"{item.value}"

    def complete(self) -> str:
        args, incomplete = self.get_completion_args()
        completions = self.get_completions(args, incomplete)
        out = [self.format_completion(item) for item in completions]
        return "\n".join(out)

    @staticmethod
    def _check_version() -> None:
        import shutil
        import subprocess

        bash_exe = shutil.which("bash")

        if bash_exe is None:
            match = None  # pragma: no cover
        else:
            output = subprocess.run(
                [bash_exe, "--norc", "-c", 'echo "${BASH_VERSION}"'],
                stdout=subprocess.PIPE,
            )
            match = re.search(r"^(\d+)\.(\d+)\.\d+", output.stdout.decode())

        if match is not None:
            major, minor = match.groups()

            if major < "4" or major == "4" and minor < "4":
                _click.utils.echo(
                    "Shell completion is not supported for Bash versions older than 4.4.",
                    err=True,
                )
        else:
            _click.utils.echo(
                "Couldn't detect Bash version, shell completion is not supported.",
                err=True,
            )  # pragma: no cover

    def source(self) -> str:
        self._check_version()
        return super().source()


class ZshComplete(ShellComplete):
    name = Shells.zsh.value
    source_template = COMPLETION_SCRIPT_ZSH

    def source_vars(self) -> dict[str, Any]:
        return {
            "complete_func": self.func_name,
            "autocomplete_var": self.complete_var,
            "prog_name": self.prog_name,
        }

    def get_completion_args(self) -> tuple[list[str], str]:
        completion_args = os.getenv("_TYPER_COMPLETE_ARGS", "")
        cwords = click_split_arg_string(completion_args)
        args = cwords[1:]
        if args and not completion_args.endswith(" "):
            incomplete = args[-1]
            args = args[:-1]
        else:
            incomplete = ""
        return args, incomplete

    def format_completion(self, item: CompletionItem) -> str:
        def escape(s: str) -> str:
            return (
                s.replace('"', '""')
                .replace("'", "''")
                .replace("$", "\\$")
                .replace("`", "\\`")
                .replace(":", r"\\:")
            )

        # TODO: Explore replicating the new behavior from Click, pay attention to
        # the difference with and without escape
        # return f"{item.type}\n{item.value}\n{item.help if item.help else '_'}"
        if item.help:
            return f'"{escape(item.value)}":"{_sanitize_help_text(escape(item.help))}"'
        else:
            return f'"{escape(item.value)}"'

    def complete(self) -> str:
        args, incomplete = self.get_completion_args()
        completions = self.get_completions(args, incomplete)
        res = [self.format_completion(item) for item in completions]
        if res:
            args_str = "\n".join(res)
            return f"_arguments '*: :(({args_str}))'"
        else:
            return "_files"


class FishComplete(ShellComplete):
    name = Shells.fish.value
    source_template = COMPLETION_SCRIPT_FISH

    def source_vars(self) -> dict[str, Any]:
        return {
            "complete_func": self.func_name,
            "autocomplete_var": self.complete_var,
            "prog_name": self.prog_name,
        }

    def get_completion_args(self) -> tuple[list[str], str]:
        completion_args = os.getenv("_TYPER_COMPLETE_ARGS", "")
        cwords = click_split_arg_string(completion_args)
        args = cwords[1:]
        if args and not completion_args.endswith(" "):
            incomplete = args[-1]
            args = args[:-1]
        else:
            incomplete = ""
        return args, incomplete

    def format_completion(self, item: CompletionItem) -> str:
        # TODO: Explore replicating the new behavior from Click, pay attention to
        # the difference with and without formatted help
        # if item.help:
        #     return f"{item.type},{item.value}\t{item.help}"

        # return f"{item.type},{item.value}
        if item.help:
            formatted_help = re.sub(r"\s", " ", item.help)
            return f"{item.value}\t{_sanitize_help_text(formatted_help)}"
        else:
            return f"{item.value}"

    def complete(self) -> str:
        complete_action = os.getenv("_TYPER_COMPLETE_FISH_ACTION", "")
        args, incomplete = self.get_completion_args()
        completions = self.get_completions(args, incomplete)
        show_args = [self.format_completion(item) for item in completions]
        if complete_action == "get-args":
            if show_args:
                return "\n".join(show_args)
        elif complete_action == "is-args":
            if show_args:
                # Activate complete args (no files)
                sys.exit(0)
            else:
                # Deactivate complete args (allow files)
                sys.exit(1)
        return ""  # pragma: no cover


class PowerShellComplete(ShellComplete):
    name = Shells.powershell.value
    source_template = COMPLETION_SCRIPT_POWER_SHELL

    def source_vars(self) -> dict[str, Any]:
        return {
            "complete_func": self.func_name,
            "autocomplete_var": self.complete_var,
            "prog_name": self.prog_name,
        }

    def get_completion_args(self) -> tuple[list[str], str]:
        completion_args = os.getenv("_TYPER_COMPLETE_ARGS", "")
        incomplete = os.getenv("_TYPER_COMPLETE_WORD_TO_COMPLETE", "")
        cwords = click_split_arg_string(completion_args)
        args = cwords[1:-1] if incomplete else cwords[1:]
        return args, incomplete

    def format_completion(self, item: CompletionItem) -> str:
        return f"{item.value}:::{_sanitize_help_text(item.help) if item.help else ' '}"


def completion_init() -> None:
    add_completion_class(BashComplete, Shells.bash.value)
    add_completion_class(ZshComplete, Shells.zsh.value)
    add_completion_class(FishComplete, Shells.fish.value)
    add_completion_class(PowerShellComplete, Shells.powershell.value)
    add_completion_class(PowerShellComplete, Shells.pwsh.value)
