"""Utilities for debugging"""

from __future__ import annotations

import logging
import sys
import time
from datetime import datetime
from typing import Dict, List

import wrapt
from rich import print

__all__ = [
    "debug_breakpoint",
    "debug_watch",
    "get_debug_flags",
    "get_debug_options",
    "is_debug",
    "relocate_debug_options",
    "set_debug",
    "wrap_function",
]


# global variable to control debug output
# set via --debug
__osxphotos_debug = False


def set_debug(debug: bool):
    """set debug flag"""
    global __osxphotos_debug
    __osxphotos_debug = debug
    logger = logging.getLogger("osxphotos")
    if debug:
        logger.setLevel(logging.DEBUG)
    else:
        logger.setLevel(logging.WARNING)


def is_debug():
    """return debug flag"""
    global __osxphotos_debug
    return __osxphotos_debug


def debug_watch(wrapped, instance, args, kwargs):
    """For use with wrapt.wrap_function_wrapper to watch calls to a function"""
    caller = sys._getframe().f_back.f_code.co_name
    name = wrapped.__name__
    timestamp = datetime.now().isoformat()
    print(
        f"{timestamp} {name} called from {caller} with args: {args} and kwargs: {kwargs}"
    )
    start_t = time.perf_counter()
    rv = wrapped(*args, **kwargs)
    stop_t = time.perf_counter()
    print(f"{timestamp} {name} returned: {rv}, elapsed time: {stop_t - start_t} sec")
    return rv


def debug_breakpoint(wrapped, instance, args, kwargs):
    """For use with wrapt.wrap_function_wrapper to set breakpoint on a function"""
    breakpoint()
    return wrapped(*args, **kwargs)


def wrap_function(function_path, wrapper):
    """Wrap a function with wrapper function"""
    module, name = function_path.split("::", 1)
    try:
        return wrapt.wrap_function_wrapper(module, name, wrapper)
    except AttributeError as e:
        raise AttributeError(f"{module}.{name} does not exist") from e


def get_debug_options(arg_names: List, argv: List) -> Dict:
    """Get the options for the debug options;
    Some of the debug options like --watch and --breakpoint need to be processed before any other packages are loaded
    so they can't be handled in the normal click argument processing, thus this function is called
    from osxphotos/cli/__init__.py

    Assumes multi-valued options are OK and that all options take form of --option VALUE or --option=VALUE
    """
    # argv[0] is the program name
    # argv[1] is the command
    # argv[2:] are the arguments
    args = {}
    for arg_name in arg_names:
        for idx, arg in enumerate(argv[1:]):
            if arg.startswith(f"{arg_name}="):
                arg_value = arg.split("=")[1]
                try:
                    args[arg].append(arg_value)
                except KeyError:
                    args[arg] = [arg_value]
            elif arg == arg_name:
                try:
                    args[arg].append(argv[idx + 2])
                except KeyError:
                    try:
                        args[arg] = [argv[idx + 2]]
                    except IndexError as e:
                        raise ValueError(f"Missing value for {arg}") from e
                except IndexError as e:
                    raise ValueError(f"Missing value for {arg}") from e
    return args


def get_debug_flags(arg_names: List, argv: List) -> Dict:
    """Get the flags for the debug options;
    Processes flags like --debug that resolve to True or False
    """
    # argv[0] is the program name
    # argv[1] is the command
    # argv[2:] are the arguments
    args = {arg_name: False for arg_name in arg_names}
    for arg_name in arg_names:
        if arg_name in argv[1:]:
            args[arg_name] = True
    return args


def relocate_debug_options(
    argv: List,
    flags: List[str] | None = None,
    options: List[str] | None = None,
) -> List:
    """Relocate debug options from anywhere in argv to after the program name.

    This allows debug options to be specified anywhere in the command line
    (e.g., 'osxphotos export --profile' instead of 'osxphotos --profile export')
    while still being processed correctly by Click.

    Args:
        argv: The command line arguments (typically sys.argv)
        flags: List of flag option names (e.g., ['--debug', '--profile'])
        options: List of option names that take values (e.g., ['--watch', '--profile-sort'])

    Returns:
        Modified argv with debug options relocated after the program name
    """
    flags = flags or []
    options = options or []

    if len(argv) <= 1:
        return argv

    # Work with a copy to avoid modifying the original
    result = list(argv)
    extracted = []
    i = 1  # Start after program name

    while i < len(result):
        arg = result[i]

        # Check for flags
        if arg in flags:
            extracted.append(result.pop(i))
            continue

        # Check for options with values
        matched_option = False
        for opt in options:
            if arg == opt:
                # --option value format
                if i + 1 < len(result):
                    extracted.append(result.pop(i))
                    extracted.append(result.pop(i))
                    matched_option = True
                    break
            elif arg.startswith(f"{opt}="):
                # --option=value format
                extracted.append(result.pop(i))
                matched_option = True
                break

        if matched_option:
            continue

        i += 1

    # Re-insert extracted options after program name (position 1)
    for j, item in enumerate(extracted):
        result.insert(1 + j, item)

    return result
