#!/usr/bin/python3
# -*- coding: utf-8 -*-

from __future__ import annotations

import argparse
from pathlib import Path
from typing import Callable

from sbo_create.__metadata__ import __version__
from sbo_create.cli import parse_args
from sbo_create.handlers.bump import BumpHandler
from sbo_create.handlers.download import DownloadHandler
from sbo_create.handlers.fallback import SboResolver
from sbo_create.handlers.fetch import FetchHandler
from sbo_create.handlers.files import FileHandler
from sbo_create.handlers.git import GitHandler
from sbo_create.handlers.misc import MiscHandler
from sbo_create.handlers.repology import RepologyHandler
from sbo_create.handlers.scan import ScanHandler
from sbo_create.handlers.validate import ValidateHandler
from sbo_create.models import PROG, AppContext, colorize, confirm, set_color


class SBoCreate:  # pylint: disable=too-few-public-methods,too-many-instance-attributes
    """SlackBuild Create Tool coordinator.

    Wires together all handlers, parses CLI arguments, and dispatches
    each subcommand to the appropriate handler method.
    """

    def __init__(self) -> None:
        self.ctx = AppContext()
        self.ctx.prg_name = self.ctx.current_folder.name

        self.files = FileHandler(self.ctx)
        self.resolver = SboResolver(self.ctx, self.files)
        self.validate = ValidateHandler(self.ctx, self.files, self.resolver)
        self.download = DownloadHandler(self.ctx, self.files)
        self.fetch = FetchHandler(self.ctx, self.files, self.resolver)
        self.git = GitHandler(self.ctx, self.files)
        self.misc = MiscHandler(self.ctx, self.files, self.validate, self.resolver)
        self.bump = BumpHandler(self.ctx, self.files, self.validate, self.download)
        self.repology = RepologyHandler(self.ctx)
        self.scan = ScanHandler(self.ctx)

        parsed = parse_args(__version__)

        if parsed.no_color:
            set_color(False)

        self.files.ensure_config_dir()
        self.files.init_info_text()
        self.files.read_maintainer_file()

        if parsed.command == 'create':
            self._init_templates()

        self.arguments(parsed)

    def _warn_if_sbo_exists(self) -> None:
        """Warn if the current package already exists in the SBo repository.

        Loads the SBo package list via the resolver (local repo or JSON cache)
        and checks whether the current package name is present. If found,
        prints a warning with the category and prompts the user to continue
        or abort.
        """
        self.resolver.load()
        if self.ctx.prg_name not in self.ctx.sbo_packages:
            return
        category: str = self.ctx.sbo_packages[self.ctx.prg_name].split()[0]
        print(f"{colorize('Warning', 'red')}: '{self.ctx.prg_name}' already exists in SBo ({category})")
        if not confirm("Continue anyway?", after_newline=False):
            raise SystemExit(0)

    def _init_templates(self) -> None:
        """Ensure the templates directory exists before creating files.

        Prints an error and exits with code 1 if the templates directory
        has not been populated yet (i.e. 'sbo-create update' has not been run).
        """
        if self.ctx.templates_path.exists():
            return
        message: str = "Templates not found. Run 'update' to download them."
        print(colorize(f"{PROG}: {message}", 'red'))
        raise SystemExit(1)

    def _cmd_create(self, parsed: argparse.Namespace) -> None:
        effective_name: str = parsed.prgnam or parsed.prgnam_pos or ''
        interactive: bool = not any([
            effective_name, parsed.prg_version, parsed.template,
            parsed.arm64, parsed.output, parsed.dry_run,
            parsed.strip_comments,
        ])
        if interactive:
            self._init_templates()
            self.files.create_interactive(warn_callback=self._warn_if_sbo_exists)
            return
        if parsed.output:
            output_dir = Path(parsed.output).expanduser().resolve()
            output_dir.mkdir(parents=True, exist_ok=True)
            self.ctx.current_folder = output_dir
        if effective_name:
            self.ctx.prg_name = effective_name
        if parsed.prg_version:
            self.ctx.info.version = parsed.prg_version
        if parsed.template:
            self.ctx.template = parsed.template
        if parsed.arm64:
            self.files.init_info_text_arm()
        if parsed.strip_comments:
            self.ctx.strip_comments = True
        self._warn_if_sbo_exists()
        self.files.create_files(dry_run=parsed.dry_run)

    def _cmd_validate(self, parsed: argparse.Namespace) -> None:
        if getattr(parsed, 'all', False):
            self.validate.validate_all(check_md5=getattr(parsed, 'md5', False),
                                       fix=getattr(parsed, 'fix', False),
                                       strict=getattr(parsed, 'strict', False),
                                       maintainer=getattr(parsed, 'maintainer', None),
                                       path=getattr(parsed, 'path', None))
        else:
            self.validate.validate_info(check_md5=getattr(parsed, 'md5', False),
                                        fix=getattr(parsed, 'fix', False),
                                        strict=getattr(parsed, 'strict', False),
                                        path=getattr(parsed, 'path', None))

    def _cmd_fetch(self, parsed: argparse.Namespace) -> None:
        if parsed.list:
            self.fetch.fetch_package(parsed.name[0], slack_ver=parsed.slackware_version,
                                     list_files=True)
        elif parsed.file:
            self.fetch.fetch_package(parsed.name[0], slack_ver=parsed.slackware_version,
                                     read_files=parsed.file)
        elif parsed.maintainer:
            self.fetch.list_maintainer_packages(parsed.name[0], wide=parsed.wide)
        else:
            for name in parsed.name:
                self.fetch.fetch_package(name, slack_ver=parsed.slackware_version)

    def _cmd_package(self, parsed: argparse.Namespace) -> None:
        if parsed.output:
            output_dir = Path(parsed.output).expanduser().resolve()
            output_dir.mkdir(parents=True, exist_ok=True)
            self.ctx.current_folder = output_dir
        self.misc.create_package()

    def _cmd_clean(self) -> None:
        self.misc.clean_sources()
        raise SystemExit(0)

    def _cmd_deps(self, parsed: argparse.Namespace) -> None:
        for name in parsed.names:
            self.misc.deps_tree(name, tree=parsed.tree)
        raise SystemExit(0)

    def _cmd_rdeps(self, parsed: argparse.Namespace) -> None:
        for name in parsed.names:
            self.misc.reverse_deps(name, tree=parsed.tree)
        raise SystemExit(0)

    def _cmd_repo(self, parsed: argparse.Namespace) -> None:
        repo_dispatch: dict[str, Callable[[], None]] = {
            'diff': lambda: self.misc.diff_package(parsed.names, context=parsed.unified),
            'sync': self.git.sync_repo,
            'submit': self.git.submit_package,
            'copy': self.misc.copy_package,
            'log': self.git.log_branch,
        }
        repo_dispatch[parsed.repo_command]()

    def arguments(self, parsed: argparse.Namespace) -> None:
        """Dispatch the parsed CLI subcommand to the appropriate handler.

        Args:
            parsed: Namespace returned by parse_args(), containing the
                selected subcommand and all associated flags.
        """
        dispatch: dict[str, Callable[[], None]] = {
            'create': lambda: self._cmd_create(parsed),
            'download': lambda: self.download.sources_download(check_urls=parsed.check_urls),
            'check': lambda: self.misc.is_the_sbo_exists(parsed.names),
            'search': lambda: self.misc.search_sbo_packages(parsed.names),
            'scan': lambda: self.scan.scan_dependencies(parsed.tarball, fmt=parsed.format),
            'clean': self._cmd_clean,
            'info': self.misc.show_info,
            'list': self.misc.list_templates,
            'validate': lambda: self._cmd_validate(parsed),
            'bump': lambda: self.bump.bump_version(parsed.version,
                                                   with_sources=parsed.with_sources),
            'package': lambda: self._cmd_package(parsed),
            'edit': lambda: self.misc.edit_files(editor=parsed.editor),
            'rename': lambda: self.misc.rename_package(parsed.name),
            'fetch': lambda: self._cmd_fetch(parsed),
            'deps': lambda: self._cmd_deps(parsed),
            'rdeps': lambda: self._cmd_rdeps(parsed),
            'repo': lambda: self._cmd_repo(parsed),
            'update': self.misc.update_templates,
            'maintainer': lambda: self.misc.configure_maintainer(show=parsed.show),
            'repology': lambda: self.repology.check_maintainer(
                email=parsed.maintainer,
                newest=parsed.newest,
                outdated=parsed.outdated,
                problematic=parsed.problematic,
            ),
        }
        dispatch[parsed.command]()


def main() -> None:
    """Entry point for the sbo-create CLI.

    Instantiates SBoCreate, which drives the full argument parsing and
    dispatch lifecycle. Catches KeyboardInterrupt and exits cleanly.
    """
    try:
        SBoCreate()
    except KeyboardInterrupt as err:
        raise SystemExit(err) from err
