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

import logging
import os
import signal
import subprocess
import threading
import time
from pathlib import Path

from sun.configs import DAEMON_PID_FILE, configs_load
from sun.fetcher import Fetcher
from sun.pending import Pending
from sun.utils import Utilities

logger = logging.getLogger(__name__)


class Manager:

    """Manages daemon lifecycle and package update checks.

    Attributes:
        configs (Configs): Application configuration and system information.
        fetch (Fetcher): Fetcher instance for retrieving package updates.
        utils (Utilities): Utilities instance for process and file operations.
    """

    def __init__(self) -> None:
        self.configs = configs_load
        self.utils = Utilities()
        self.fetch = Fetcher()
        self.pending = Pending()

    def check_updates(self) -> tuple[str, list[str]]:
        """Checks for available software updates.

        Returns:
            tuple[str, list[str]]: A status message and the list of updated package names.
        """
        packages: list[str] = list(self.fetch.updates())
        count_packages: int = len(packages)

        if self.fetch.last_fetch_error:
            message = 'SUN: Error: Failed to fetch remote ChangeLog.'
        elif count_packages > 0:
            message = f'{count_packages} Software Updates Available\n'
        else:
            message = 'No news is good news!'

        return message, packages

    def check_pending(self) -> tuple[str, list[str]]:
        """Checks for installed packages that have a newer version available in the repo.

        Returns:
            tuple[str, list[str]]: A status message and the list of packages pending upgrade.
        """
        packages: list[str] = self.pending.upgradable()
        count: int = len(packages)
        if count > 0:
            message = f'{count} Package(s) Pending Upgrade\n'
        else:
            message = 'System is up to date.'
        return message, packages

    def daemon_status(self) -> bool:
        """Checks whether the sun-daemon process is currently running.

        Returns:
            bool: True if the daemon is running, False otherwise.
        """
        pid = self.utils.get_daemon_pid()
        if not pid:
            # Fallback: PID file missing but daemon is running (e.g. crash without
            # cleanup or manual delete). Scan /proc and recreate the PID file.
            pids = self.utils.process_pids('sun-daemon')
            if pids:
                DAEMON_PID_FILE.parent.mkdir(exist_ok=True)
                DAEMON_PID_FILE.write_text(str(pids[0]))
                return True
            return False
        try:
            os.kill(int(pid), 0)
            # os.kill(pid, 0) succeeds for zombie processes too; check /proc to exclude them.
            status_file = Path(f'/proc/{pid}/status')
            if not status_file.is_file():
                DAEMON_PID_FILE.unlink(missing_ok=True)
                return False
            for line in status_file.read_text(encoding='utf-8').splitlines():
                if line.startswith('State:') and 'Z' in line:
                    logger.warning('sun-daemon (PID %s) is a zombie process', pid)
                    DAEMON_PID_FILE.unlink(missing_ok=True)
                    return False
            return True
        except (ProcessLookupError, ValueError):
            DAEMON_PID_FILE.unlink(missing_ok=True)
            return False

    def _daemon_start(self) -> None:
        """Start sun-daemon in background without leaving zombie processes.

        Uses subprocess.Popen with start_new_session=True to detach sun-daemon
        from the calling terminal/session. A daemon thread calls proc.wait()
        asynchronously so the OS entry is reaped as soon as sun-daemon exits,
        preventing [sun-daemon] <defunct> zombie entries.

        This approach is preferred over os.fork() because Python's subprocess
        module handles fork safely in multithreaded processes (e.g. PyQt5 tray),
        whereas a raw os.fork() in a multithreaded context can deadlock due to
        inherited Qt locks in the child.
        """
        proc = subprocess.Popen(  # pylint: disable=consider-using-with
            ['sun-daemon'],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
            start_new_session=True
        )
        threading.Thread(target=proc.wait, daemon=True).start()

    def _daemon_stop(self) -> None:
        """Stop sun-daemon via SIGTERM and remove PID file."""
        pid = self.utils.get_daemon_pid()
        if not pid:
            raise OSError('sun-daemon PID not found')
        os.kill(int(pid), signal.SIGTERM)
        DAEMON_PID_FILE.unlink(missing_ok=True)

    def daemon_process(self, arg: str, message: str) -> str:
        """Executes a daemon command and returns a result message.

        Args:
            arg (str): Daemon command: 'start', 'stop', or 'restart'.
            message (str): Success message to return if the command succeeds.

        Returns:
            str: Result message to display to the user.
        """
        running: bool = self.daemon_status()

        if running and arg == 'start':
            return 'SUN is already running'
        if not running and arg in ('stop', 'restart'):
            return 'SUN is not running'

        try:
            if arg == 'start':
                self._daemon_start()
            elif arg == 'stop':
                self._daemon_stop()
            elif arg == 'restart':
                self._daemon_stop()
                time.sleep(1)
                self._daemon_start()
        except OSError as e:
            return f'FAILED: {message} ({e})'

        return message
