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

import argparse
import getpass
import pydoc
import re
import subprocess
import sys
import time

from sun.__metadata__ import __version__
from sun.daemon import LOG_FILE
from sun.manager import Manager


class SunArgumentParser(argparse.ArgumentParser):
    """ArgumentParser subclass with a cleaner error message for unknown commands."""

    def error(self, message: str) -> None:  # type: ignore[override]
        cmd = next((a for a in sys.argv[1:] if not a.startswith('-')), '')
        if cmd:
            print(f"sun: Unknown command '{cmd}'.\nUse 'sun -h' for available commands.")
        else:
            print(f'sun: {message}')
        raise SystemExit(2)


class Cli(Manager):
    """Command line interface for controlling the SUN daemon and viewing system info."""

    bold: str = '\033[1m'
    green: str = '\x1b[32m'
    yellow: str = '\x1b[33m'
    red: str = '\x1b[31m'
    bgreen: str = f'{bold}{green}'
    cyan: str = '\x1b[36m'
    endc: str = '\x1b[0m'

    ACTION_COLORS: dict[str, str] = {
        'Upgraded': '\x1b[33m',
        'Rebuilt': '\x1b[36m',
        'Added': '\x1b[32m',
        'Removed': '\x1b[31m',
    }

    @staticmethod
    def su() -> None:
        """Root privileges not required.

        Raises:
            SystemExit: Exit.
        """
        if getpass.getuser() == 'root':
            raise SystemExit('sun: Error: It should not be run as root')

    def menu(self) -> None:
        """Parses CLI arguments and dispatches to the appropriate view method."""
        self.su()

        parser = SunArgumentParser(
            prog='sun',
            usage='sun [-h] [-v] COMMAND ...',
            description='SUN (Slackware Update Notifier)',
            epilog='Start tray app from the terminal: sun start --tray\nFor command details: sun <command> -h\nManage daemon autostart (requires root): sun-autostart enable|disable|status',
            formatter_class=argparse.RawDescriptionHelpFormatter
        )
        parser.add_argument('-v', '--version', action='version', version=f'sun {__version__}')

        subparsers = parser.add_subparsers(dest='command')
        start_p = subparsers.add_parser('start', help='Start sun daemon.')
        start_p.add_argument('--tray', action='store_true', help='Start tray app.')
        subparsers.add_parser('stop', help='Stop sun daemon.')
        subparsers.add_parser('restart', help='Restart sun daemon.')
        subparsers.add_parser('status', help='Sun daemon status.')
        check_p = subparsers.add_parser('check', help='Check for software updates.')
        check_p.add_argument('pattern', nargs='?', default='', help='Filter packages by name (e.g. sun check kernel).')
        check_p.add_argument('-n', '--no-color', action='store_true', help='Disable colored output.')
        pending_p = subparsers.add_parser('pending', help='Show packages pending upgrade.')
        pending_p.add_argument('-n', '--no-color', action='store_true', help='Disable colored output.')
        info_p = subparsers.add_parser('info', help='OS and machine information.')
        info_p.add_argument('-n', '--no-color', action='store_true', help='Disable colored output.')
        subparsers.add_parser('changelog', help='View the remote Slackware ChangeLog.')
        subparsers.add_parser('log', help='View the sun-daemon log file.')
        subparsers.add_parser('config', help='View the current sun configuration.')

        args = parser.parse_args()

        if args.command is None:
            parser.print_help()
            raise SystemExit(1)

        if args.command == 'start' and args.tray:
            subprocess.Popen(  # pylint: disable=consider-using-with
                ['sun-tray'],
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
                start_new_session=True
            )
            return

        dispatch = {
            'start': self.view_start,
            'stop': self.view_stop,
            'restart': self.view_restart,
            'status': self.view_status,
            'check': lambda: self.view_updates(args.pattern, getattr(args, 'no_color', False)),
            'info': lambda: self.view_os_info(getattr(args, 'no_color', False)),
            'changelog': self.view_changelog,
            'log': self.view_log,
            'config': self.view_config,
            'pending': lambda: self.view_pending(getattr(args, 'no_color', False)),
        }
        dispatch[args.command]()  # type: ignore[no-untyped-call]

    def _colorize_package(self, package: str, max_len: int = 0) -> str:
        """Colorize and align a package line by action type using ANSI escape codes.

        Splits the line at the double-space separator, pads the package name to
        max_len for column alignment, then applies the action color.

        Args:
            package (str): Package line from ChangeLog (e.g. 'foo-1.0.txz:  Upgraded.').
            max_len (int): Column width to pad the package name to.

        Returns:
            str: Aligned and colorized package line.
        """
        parts = package.split('  ', 1)
        if len(parts) != 2:
            return package
        name, action = parts
        padded = name.ljust(max_len)
        for act, color in self.ACTION_COLORS.items():
            if action.startswith(act):
                return f'{padded}  {color}{action}{self.endc}'
        return f'{padded}  {action}'

    def view_updates(self, pattern: str = '', no_color: bool = False) -> None:
        """Prints the available software updates to the terminal.

        Args:
            pattern (str): Optional filter string to match against package names.
            no_color (bool): If True, disable colored and aligned output.
        """
        if not self.configs.ignore_mirror_warning:
            warning = self.utils.mirror_version_warning()
            if warning:
                print(f'{self.yellow}{warning}{self.endc}' if not no_color else warning)
        message, packages = self.check_updates()
        if pattern:
            packages = [p for p in packages if pattern.lower() in p.lower()]
            message = f'{len(packages)} software updates are available\n' if packages else 'No news is good news!'
        print(f'{self.bold}{message}{self.endc}' if not no_color else message)
        if len(packages) > 0:
            max_len = max(len(p.split('  ', 1)[0]) for p in packages)
            for i, package in enumerate(packages, 1):
                if no_color:
                    parts = package.split('  ', 1)
                    line = f'{parts[0].ljust(max_len)}  {parts[1]}' if len(parts) == 2 else package
                else:
                    line = self._colorize_package(package, max_len)
                print(f'{i:2}. {line}')

    def view_pending(self, no_color: bool = False) -> None:
        """Prints packages that have a newer version available but are not yet installed.

        Args:
            no_color (bool): If True, disable colored output.
        """
        message, packages = self.check_pending()
        print(f'{self.bold}{message}{self.endc}' if not no_color else message)
        for i, pkg in enumerate(packages, 1):
            line = pkg.replace('.txz', '')
            if not no_color:
                parts = line.split(' (installed: ', 1)
                package = parts[0]
                inst = f'(installed: {parts[1]}' if len(parts) == 2 else ''
                line = f'{self.yellow}{package}{self.endc} {inst}'
            print(f'{i:2}. {line}')

    def view_start(self) -> None:
        """View starting message.
        """
        message = 'Starting SUN daemon:  /usr/bin/sun-daemon'
        result = self.daemon_process('start', message)
        if result == 'SUN is already running':
            pid = self.utils.get_daemon_pid()
            result = f'SUN is already running (PID {self.yellow}{pid}{self.endc})' if pid else 'SUN is already running'
        elif result == message:
            for _ in range(10):
                pid = self.utils.get_daemon_pid()
                if pid:
                    break
                time.sleep(0.1)
            result = f'{message} (PID {self.yellow}{pid}{self.endc})...' if pid else f'{message}...'
        self.print_result(result)

    def view_stop(self) -> None:
        """View stopping message.
        """
        pid = self.utils.get_daemon_pid()
        if pid:
            pid = f' (PID {self.yellow}{pid}{self.endc})'
        result = self.daemon_process('stop', f'Stopping SUN daemon:  /usr/bin/sun-daemon{pid}...')
        self.print_result(result)

    def view_restart(self) -> None:
        """View restarting message.
        """
        message = 'Restarting SUN daemon:  /usr/bin/sun-daemon'
        result = self.daemon_process('restart', message)
        if result == message:
            for _ in range(10):
                pid = self.utils.get_daemon_pid()
                if pid:
                    break
                time.sleep(0.1)
            result = f'{message} (PID {self.yellow}{pid}{self.endc})...' if pid else f'{message}...'
        self.print_result(result)

    def view_status(self) -> None:
        """View status message.
        """
        pid = self.utils.get_daemon_pid()
        print(f'SUN is running as PID {self.yellow}{pid}{self.endc}' if self.daemon_status() else 'SUN is not running')

    def view_os_info(self, no_color: bool = False) -> None:
        """Prints OS and system information to the terminal.

        Args:
            no_color (bool): If True, disable cyan section header colors.
        """
        html_text: str = self.utils.os_info_html()
        text = re.sub(r'<[^>]+>', '', html_text.replace('<br><br>', '\n').replace('<br>', '')).strip()
        if not no_color:
            text = re.sub(r'(\[[^\]]+\])', f'{self.cyan}\\1{self.endc}', text)
        print(text)

    def view_changelog(self) -> None:
        """Fetches and displays the remote Slackware ChangeLog via pager.
        """
        changelog = self.utils.read_repo_text_file()
        if changelog:
            pydoc.pager(changelog)
        else:
            print('SUN: Error: Failed to fetch ChangeLog.')

    def view_log(self) -> None:
        """Displays the sun-daemon log file via pager.
        """
        if LOG_FILE.is_file():
            pydoc.pager(LOG_FILE.read_text(encoding='utf-8'))
        else:
            print(f'SUN: Log file not found: {LOG_FILE}')

    def view_config(self) -> None:
        """Prints the sun.conf configuration file contents.
        """
        html = self.utils.config_html()
        text = re.sub(r'<[^>]+>', '', html).strip()
        text = re.sub(r'(\[[^\]]+\])', f'{self.cyan}\\1{self.endc}', text)
        print(text)

    def print_result(self, result: str) -> None:
        """Print result with delay 1 second.

        Args:
            result (str): Message for print.
        """
        if result.startswith('FAILED') or result.startswith('SUN is'):
            print(result)
        else:
            print(result, end=' ', flush=True)
            time.sleep(1)
            print(f'{self.bgreen}Done{self.endc}')


def main() -> None:
    """Entry point for the sun CLI command.

    Raises:
        SystemExit: Exit on keyboard interrupt.
    """
    try:
        cli = Cli()
        cli.menu()
    except KeyboardInterrupt as e:
        raise SystemExit(1) from e
