#!/usr/bin/python3

from __future__ import annotations

import argparse
import logging
import logging.handlers
import sys
from signal import SIG_DFL, SIGPIPE, signal
from typing import Any, Callable

from slpkg.cli_parser import (_COMMAND_CATEGORIES, _print_category_help,
                              build_parser)
from slpkg.config import config_load
from slpkg.errors import Errors
from slpkg.repositories import Repositories
from slpkg.runner import Run

# Ignore broken pipe errors when piping output to other commands (e.g., `slpkg list | head`).
signal(SIGPIPE, SIG_DFL)


# This option allows users to directly set the logging level to DEBUG from the command line,
# overriding any logging_level configuration specified in slpkg.toml.
if "--debug" in sys.argv[1:] or "-d" in sys.argv[1:]:
    logging_level = logging.DEBUG
else:
    # --- Global Logging Configuration for the entire application ---
    # Determine LOGGING_LEVEL from config_load (which reads from slpkg.toml).
    # This needs to happen before config_load.update_from_args is called,
    # as update_from_args might also log.
    logging_level = getattr(logging, config_load.logging_level, logging.INFO)

# Validate the retrieved logging level. If invalid, default to INFO.
if not isinstance(logging_level, int):
    print(f"Warning: Invalid log level '{config_load.logging_level}' in config. Using INFO.", file=sys.stderr)
    logging_level = logging.INFO

# Configure the root logger. This setup will apply to all loggers created with getLogger(__name__).
# The log file will be overwritten each time the script starts (filemode='w').
logging.basicConfig(
    filename=config_load.slpkg_log_file,
    level=logging_level,
    format="%(levelname)s: %(asctime)s - %(name)s - %(funcName)s - %(message)s",
    filemode="w",
)

# Initialize the logger for the main module.
logger = logging.getLogger(__name__)
logger.info(
    "slpkg application started. Logging initialized to level: %s (file: %s)",
    logging.getLevelName(logging_level),
    config_load.slpkg_log_file,
)
# --- End of Global Logging Configuration ---


def check_for_repositories(
    repository: str, args: argparse.Namespace, parser: argparse.ArgumentParser, option_args: dict[str, Any]
) -> None:
    """Manages repository rules and validation."""
    logger.debug(
        "Checking repository rules for repository: '%s', command: '%s', options: %s",
        repository,
        args.command,
        option_args,
    )
    repos = Repositories()
    repo_config = repos.repositories.get(repository)

    if repository != "*" and repository is not None and repository != "":
        if repo_config is None:
            logger.error("Repository '%s' does not exist.", repository)
            parser.error(f"Repository '{repository}' does not exist.")
        elif not repo_config.get("enable"):
            logger.error("Repository '%s' is not enabled.", repository)
            parser.error(f"Repository '{repository}' is not enabled.")

    # Special rules for '*' repository
    if repository == "*" and not (
        args.command in ("search", "ser", "s")
        or args.command in ("upgrade", "U", "upg")
        or args.command in ("install", "i", "inst")
    ):
        logger.error("Repository '*' is not allowed with command '%s'.", args.command)
        parser.error(f"Repository '{repository}' is not allowed with this command.")

    # Special rules for 'build' command
    if args.command == "build" and repository and repository not in {repos.sbo_repo_name, repos.ponce_repo_name}:
        logger.error("Repository '%s' is not allowed with 'build' command.", repository)
        parser.error(f"Repository '{repository}' is not allowed with this command.")
    logger.debug("Repository rules check completed successfully.")


def main() -> None:  # pylint: disable=[R0915]
    """Main control function for argparse arguments."""
    logger.info("Main function started.")
    error = Errors()

    parser, subparsers_map = build_parser()

    # --- Handle initial argument parsing for missing command or help ---
    # This logic is moved here before the main parse_args to handle cases where
    # a command is missing or only -h/--help is provided without a command.
    if len(sys.argv) == 1:
        logger.error("No command provided. Exiting with error and showing help message.")
        parser.error('Missing command. Use "help" for more information.')

    # Check for -h or --help without a specific command.
    # This block ensures that -h or --help do not show the full help,
    # but instead direct the user to the 'help' subcommand.
    if len(sys.argv) == 2 and sys.argv[1] in ("-h", "--help"):
        logger.info("'-h' or '--help' provided without a command. Directing user to 'help' subcommand.")
        parser.error('Missing command. Use "help" for more information.')

    # --- Parse arguments ---
    args: argparse.Namespace = parser.parse_args()
    logger.info("Arguments parsed: %s", args)

    # --- Update configs from argparse args. ---
    # Call the update_from_args method from config_load to apply CLI arguments to global config.
    config_load.update_from_args(args)
    logger.debug("Configuration updated from argparse arguments using config_load.update_from_args.")

    # Retrieve repository, directory, and query.
    # Normalize "all" → "*" so users can write -o all instead of -o "*".
    # Precedence: per-subcommand -o > global -o > default.
    _sub_repo = args.option_repository if hasattr(
        args, "option_repository") and args.option_repository is not None else None
    _global_repo = args.option_repository_global if args.option_repository_global is not None else None
    repository = _sub_repo or _global_repo or ""
    if repository.lower() == "all":
        repository = "*"
    directory = (
        args.option_directory
        if hasattr(args, "option_directory") and args.option_directory is not None
        else str(config_load.tmp_slpkg)
    )
    query = args.option_query if hasattr(args, "option_query") and args.option_query is not None else ""
    logger.debug("Extracted repository: '%s', directory: '%s', query: '%s'", repository, directory, query)

    option_args: dict[str, Any] = {
        "option_yes": getattr(args, "option_yes", False),
        "option_check": getattr(args, "option_check", False),
        "option_resolve_off": getattr(args, "option_resolve_off", False),
        "option_reinstall": getattr(args, "option_reinstall", False),
        "option_skip_installed": getattr(args, "option_skip_installed", False),
        "option_fetch": getattr(args, "option_fetch", False),
        "option_full_reverse": getattr(args, "option_full_reverse", False),
        "option_select": getattr(args, "option_select", False),
        "option_pkg_version": getattr(args, "option_pkg_version", False),
        "option_parallel": getattr(args, "option_parallel", False),
        "option_no_case": getattr(args, "option_no_case", False),
        "option_color": getattr(args, "option_color", None),
        "option_dialog": getattr(args, "option_dialog", False),
        "option_pkg_description": getattr(args, "option_pkg_description", False),
        "option_edit": getattr(args, "option_edit", False),
        "option_sbo_edit": getattr(args, "option_sbo_edit", False),
        "option_repo_tag": getattr(args, "option_repo_tag", None),
        "option_pager": getattr(args, "option_pager", False),
        "option_quiet": getattr(args, "option_quiet", False),
        "option_local": getattr(args, "option_local", False),
        "option_no_lock": getattr(args, "option_no_lock", False),
        "option_repository": repository,  # Store the actual repository string.
        "option_directory": directory,  # Store the actual directory string.
        "option_query": query,  # Store the actual query string.
        "option_lines": getattr(args, "option_lines", 0),
        "option_clear_history": getattr(args, "option_clear_history", False),
        "option_history_package": getattr(args, "option_history_package", ""),
        "option_transitive": getattr(args, "option_transitive", False),
        "option_unmark": getattr(args, "option_unmark", False),
        "option_unpin": getattr(args, "option_unpin", False),
        "option_blacklist_remove": getattr(args, "option_blacklist_remove", False),
        "option_autoremove": getattr(args, "option_autoremove", False),
        "option_extra_args": getattr(args, "option_extra_args", None),
        "option_json": getattr(args, "option_json", False),
        "option_top": getattr(args, "option_top", None),
        "option_no_deps": getattr(args, "option_no_deps", False),
        "option_repo_enable": getattr(args, "option_repo_enable", None),
        "option_repo_disable": getattr(args, "option_repo_disable", None),
        "option_repo_mirror": getattr(args, "option_repo_mirror", None),
        "option_repo_default": getattr(args, "option_repo_default", None),
        "option_repo_priority": getattr(args, "option_repo_priority", None),
        "option_repo_clear_priority": getattr(args, "option_repo_clear_priority", False),
        "option_repos_priority": getattr(args, "option_repos_priority", None),
        "option_install_new": getattr(args, "option_install_new", False),
        "option_ignore_tag": getattr(args, "option_ignore_tag", False),
        "option_fix": getattr(args, "option_fix", False),
        "option_short": getattr(args, "option_short", False),
        "option_date": getattr(args, "option_date", ""),
        "option_validate": getattr(args, "option_validate", False),
    }
    logger.debug("Extracted options dictionary: %s", option_args)

    # --- Repository validation ---
    check_for_repositories(repository, args, parser, option_args)
    logger.debug("Repository validation completed.")

    # --- Initialize Run class ---
    # Pass the full option_args dictionary to Run
    runner = Run(option_args, repository)
    logger.debug("Run class instance created.")

    # --- Command Dispatch ---
    runner.options.update(vars(args))

    def _handle_help() -> None:
        logger.info("Handling 'help' command for subcommand: %s", args.command_for_help)
        if args.command_for_help:
            target = args.command_for_help
            cat_names = {cat.lower() for cat, _ in _COMMAND_CATEGORIES}
            if target.lower() in cat_names:
                _print_category_help(target.lower(), parser)
                logger.info("Displayed category help for '%s'.", target)
            elif target in subparsers_map:
                subparsers_map[target].print_help()
                logger.info("Displayed help for subcommand: '%s'.", target)
            else:
                parser.error(f"Unknown command or category '{target}' for help.")
        else:
            parser.print_help()
            logger.info("Displayed general help for 'slpkg help'.")

    dispatch: dict[str, Callable[[], Any]] = {
        **dict.fromkeys(["update", "u", "upd"], runner.update),
        **dict.fromkeys(["upgrade", "U", "upg"], runner.upgrade),
        **dict.fromkeys(["config", "C", "conf"], runner.edit_configs),
        **dict.fromkeys(["repo-info", "ri", "rinf"], runner.repo_info),
        **dict.fromkeys(["repo", "rp", "rep"], runner.repo),
        **dict.fromkeys(["stats", "st", "stt"], runner.stats),
        **dict.fromkeys(["rollback", "rb", "und"], runner.rollback),
        **dict.fromkeys(["clean", "c", "cln"], runner.clean_tmp),
        **dict.fromkeys(["self-check", "sc", "schk"], runner.self_check),
        **dict.fromkeys(["check-broken", "cb", "cbr"], runner.check_broken),
        **dict.fromkeys(["clean-system", "cs", "csy"], runner.clean_system),
        **dict.fromkeys(["health-check", "hc", "hchk"], lambda: runner.health_check(args.packages)),
        **dict.fromkeys(["version", "v", "ver"], runner.version),
        **dict.fromkeys(["history", "hi", "hst"], runner.show_history),
        **dict.fromkeys(["orphans", "or", "orp"], runner.find_orphans),
        **dict.fromkeys(["rebuild", "rbd", "rbld"], runner.rebuild),
        **dict.fromkeys(["export", "ex", "exp"], lambda: runner.export(args.filename)),
        **dict.fromkeys(["import", "im", "imp"], lambda: runner.import_packages(args.filename)),
        **dict.fromkeys(["changelog", "n", "chg"], lambda: runner.changelog_print(query)),
        **dict.fromkeys(["download", "dl", "dwn"], lambda: runner.download(args.packages, directory)),
        **dict.fromkeys(["build", "b", "bld"], lambda: runner.build(args.packages)),
        **dict.fromkeys(["reinstall", "re", "rein"], lambda: runner.reinstall(args.packages)),
        **dict.fromkeys(["install", "i", "inst"], lambda: runner.install(args.packages)),
        **dict.fromkeys(["install-pkg", "ip", "ipkg"], lambda: runner.install_pkg(args.packages)),
        **dict.fromkeys(["remove", "R", "rmv"], lambda: runner.remove(args.packages)),
        **dict.fromkeys(["verify", "V", "vfy"], lambda: runner.verify(args.packages)),
        **dict.fromkeys(["audit", "ad", "adt"], lambda: runner.audit(args.packages)),
        **dict.fromkeys(["mark", "mk", "mrk"], lambda: runner.mark(args.packages)),
        **dict.fromkeys(["blacklist", "bl", "blk"], lambda: runner.blacklist(args.packages)),
        **dict.fromkeys(["pin", "pn", "pnh"], lambda: runner.pin(args.packages)),
        **dict.fromkeys(["list", "l", "lst"], lambda: runner.list_installed(args.packages)),
        **dict.fromkeys(["info", "inf", "show"], lambda: runner.info_package(args.packages)),
        **dict.fromkeys(["search", "s", "ser"], lambda: runner.search(args.packages)),
        **dict.fromkeys(["file-search", "f", "fs"], lambda: runner.file_search(args.packages)),
        **dict.fromkeys(["list-files", "lf", "lfl"], lambda: runner.list_files(args.packages)),
        **dict.fromkeys(["reverse", "r", "rdeps"], lambda: runner.reverse(args.packages)),
        **dict.fromkeys(["depends", "d", "dep"], lambda: runner.depends(args.packages)),
        **dict.fromkeys(["why", "w", "wy"], lambda: runner.why(args.packages)),
        **dict.fromkeys(["help", "h", "hlp"], _handle_help),
    }

    try:
        logger.info("Dispatching command: '%s'", args.command)
        dispatch[args.command]()
    except (KeyboardInterrupt, EOFError):
        logger.info("Operation cancelled by user (KeyboardInterrupt/EOFError). Exiting with status 1.")
        print("\nOperation canceled by the user.")
        sys.exit(1)
    except KeyError as e:
        logger.error("KeyError occurred during command dispatch: %s", e, exc_info=True)
        message: str = f"An internal error occurred: {e}. Check the log {config_load.slpkg_log_file} file."
        error.message(message=message, exit_status=1)
    logger.info("Main function finished.")


if __name__ == "__main__":
    try:
        main()
    except (KeyboardInterrupt, EOFError) as err:
        logger.info("Application terminated by user (KeyboardInterrupt/EOFError) at top level. Exiting with status 1.")
        print("\nOperation canceled by the user.")
        raise SystemExit(1) from err
    except Exception as e:  # pylint: disable=[W0718]
        logger.critical("An unexpected critical error occurred at top level: %s", e, exc_info=True)
        MSG: str = f"A critical error occurred: {e}. Check the log {config_load.slpkg_log_file} file."
        print(MSG, file=sys.stderr)
