import argparse

import shtab

from ._common import process_epilog
from ..constants import *  # NOQA
from ..helpers import archivename_validator  # used to detect ARCHIVE args for dynamic completion

# Dynamic completion for archive IDs (aid:...)
#
# This integrates with shtab by:
# - tagging argparse actions that accept an ARCHIVE (identified by type == archivename_validator)
#   with a .complete mapping pointing to our helper function.
# - using shtab.complete's 'preamble' parameter to inject the helper into the
#   generated completion script for supported shells.
#
# Notes / constraints (per plan):
# - Calls `borg repo-list --format ...` and filters results by the typed aid: hex prefix.
# - Non-interactive only. We rely on Borg to fail fast without prompting in non-interactive contexts.
#   If it cannot, we simply return no suggestions.

# Name of the helper function inserted into the generated completion script(s)
AID_BASH_FN_NAME = "_borg_complete_aid"
AID_ZSH_FN_NAME = "_borg_complete_aid"

# Global bash preamble that is prepended to the generated completion script.
# It aggregates only what we need:
# - wordbreak fixes for ':' and '=' so tokens like 'aid:' and '--repo=/path' stay intact
# - a minimal dynamic completion helper for aid: archive IDs
BASH_PREAMBLE = r"""
# keep ':' and '=' intact so tokens like 'aid:' and '--repo=/path' stay whole
if [[ ${COMP_WORDBREAKS-} == *:* ]]; then COMP_WORDBREAKS=${COMP_WORDBREAKS//:}; fi
if [[ ${COMP_WORDBREAKS-} == *=* ]]; then COMP_WORDBREAKS=${COMP_WORDBREAKS//=}; fi

# Complete aid:<hex-prefix> archive IDs by querying "borg repo-list --short"
# Note: we only suggest the first 8 hex digits (short ID) for completion.
_borg_complete_aid() {
  local cur="${COMP_WORDS[COMP_CWORD]}"
  [[ "$cur" == aid:* ]] || return 0

  local prefix="${cur#aid:}"
  [[ -n "$prefix" && ! "$prefix" =~ ^[0-9a-fA-F]*$ ]] && return 0

  # derive repo context from words: --repo=V, --repo V, -r=V, -rV, or -r V
  local repo_arg=()
  local i w
  for (( i=0; i<${#COMP_WORDS[@]}; i++ )); do
    w="${COMP_WORDS[i]}"
    if [[ "$w" == --repo=* ]]; then repo_arg=( --repo "${w#--repo=}" ); break
    elif [[ "$w" == -r=* ]]; then repo_arg=( -r "${w#-r=}" ); break
    elif [[ "$w" == -r* && "$w" != "-r" ]]; then repo_arg=( -r "${w#-r}" ); break
    elif [[ "$w" == "--repo" || "$w" == "-r" ]]; then
      if (( i+1 < ${#COMP_WORDS[@]} )); then repo_arg=( "$w" "${COMP_WORDS[i+1]}" ); fi
      break
    fi
  done

  # ask borg for raw IDs; avoid prompts and suppress stderr
  local out
  if [[ -n "${repo_arg[*]}" ]]; then
    out=$( borg repo-list "${repo_arg[@]}" --short 2>/dev/null </dev/null )
  else
    out=$( borg repo-list --short 2>/dev/null </dev/null )
  fi
  [[ -z "$out" ]] && return 0

  # filter by (case-insensitive) hex prefix and emit candidates
  local IFS=$'\n' id prelower idlower
  prelower="$(printf '%s' "$prefix" | tr '[:upper:]' '[:lower:]')"
  while IFS= read -r id; do
    [[ -z "$id" ]] && continue
    idlower="$(printf '%s' "$id" | tr '[:upper:]' '[:lower:]')"
    # Print only the first 8 hex digits of the ID for completion suggestions.
    [[ "$idlower" == "$prelower"* ]] && printf 'aid:%s\n' "${id:0:8}"
  done <<< "$out"
  return 0
}
"""


# Global zsh preamble providing dynamic completion for aid:<hex> archive IDs.
#
# Notes:
# - We use zsh's $words/$CURRENT arrays to inspect the command line.
# - Candidates are returned via `compadd`.
# - We try to detect repo context from --repo=V, --repo V, -r=V, -rV, -r V.
ZSH_PREAMBLE = r"""
# Complete aid:<hex-prefix> archive IDs by querying "borg repo-list --short"
# Note: we only suggest the first 8 hex digits (short ID) for completion.
_borg_complete_aid() {
  local cur
  cur="${words[$CURRENT]}"
  [[ "$cur" == aid:* ]] || return 0

  local prefix="${cur#aid:}"
  # allow only hex digits as prefix; empty prefix also allowed (list all)
  [[ -n "$prefix" && ! "$prefix" == [0-9a-fA-F]# ]] && return 0

  # derive repo context from words: --repo=V, --repo V, -r=V, -rV, or -r V
  local -a repo_arg=()
  local i w
  for i in {1..$#words}; do
    w="$words[$i]"
    if [[ "$w" == --repo=* ]]; then repo_arg=( --repo "${w#--repo=}" ); break
    elif [[ "$w" == -r=* ]]; then repo_arg=( -r "${w#-r=}" ); break
    elif [[ "$w" == -r* && "$w" != "-r" ]]; then repo_arg=( -r "${w#-r}" ); break
    elif [[ "$w" == "--repo" || "$w" == "-r" ]]; then
      if (( i+1 <= $#words )); then repo_arg=( "$w" "${words[$((i+1))]}" ); fi
      break
    fi
  done

  # ask borg for raw IDs; avoid prompts and suppress stderr
  local out
  if (( ${#repo_arg[@]} > 0 )); then
    out=$( borg repo-list "${repo_arg[@]}" --short 2>/dev/null </dev/null )
  else
    out=$( borg repo-list --short 2>/dev/null </dev/null )
  fi
  [[ -z "$out" ]] && return 0

  # filter by (case-insensitive) hex prefix and emit candidates
  local prelower id idlower
  prelower="${prefix:l}"
  local -a candidates=()
  for id in ${(f)out}; do
    [[ -z "$id" ]] && continue
    idlower="${id:l}"
    if [[ "$idlower" == "$prelower"* ]]; then
      candidates+=( "aid:${id[1,8]}" )
    fi
  done
  # -Q: do not escape special chars, so ':' remains as-is
  compadd -Q -- $candidates
  return 0
}
"""


def _attach_aid_completion(parser: argparse.ArgumentParser):
    """Tag all arguments that accept an ARCHIVE with aid:-completion.

    We detect ARCHIVE arguments by their type being archivename_validator.
    This function mutates the parser actions to add a .complete mapping used by shtab.
    """

    for action in parser._actions:
        # Recurse into subparsers
        if isinstance(action, argparse._SubParsersAction):
            for sub in action.choices.values():
                _attach_aid_completion(sub)
            continue

        # Assign dynamic completion only for arguments that take an archive name.
        if action.type is archivename_validator:
            action.complete = {"bash": AID_BASH_FN_NAME, "zsh": AID_ZSH_FN_NAME}  # type: ignore[attr-defined]


class CompletionMixIn:
    def do_completion(self, args):
        """Output shell completion script for the given shell."""
        # Automagically generates completions for subcommands and options. Also
        # adds dynamic completion for archive IDs with the aid: prefix for all ARCHIVE
        # arguments (identified by archivename_validator). It reuses `borg repo-list`
        # to enumerate archives and does not introduce any new commands or caching.
        parser = self.build_parser()
        _attach_aid_completion(parser)
        preamble = {"bash": BASH_PREAMBLE, "zsh": ZSH_PREAMBLE}
        script = shtab.complete(parser, shell=args.shell, preamble=preamble)  # nosec B604
        print(script)

    def build_parser_completion(self, subparsers, common_parser, mid_common_parser):
        shells = tuple(shtab.SUPPORTED_SHELLS)

        completion_epilog = process_epilog(
            """
        This command prints a shell completion script for the given shell.

        Please note that for some dynamic completions (like archive IDs), the shell
        completion script will call borg to query the repository. This will work best
        if that call can be made without prompting for user input, so you may want to
        set BORG_REPO and BORG_PASSPHRASE environment variables.
        """
        )

        subparser = subparsers.add_parser(
            "completion",
            parents=[common_parser],
            add_help=False,
            description=self.do_completion.__doc__,
            epilog=completion_epilog,
            formatter_class=argparse.RawDescriptionHelpFormatter,
            help="output shell completion script",
        )
        subparser.set_defaults(func=self.do_completion)
        subparser.add_argument(
            "shell", metavar="SHELL", choices=shells, help="shell to generate completion for (one of: %(choices)s)"
        )
