"""Base OS module for minimal Debian installation.

This module provides the core functionality for creating a minimal Debian
chroot environment using debootstrap. It handles:
- Base system installation via debootstrap
- Package repository configuration
- Essential package installation (kernel, live-boot, etc.)
- System configuration (hostname, network, etc.)
- Debconf preseeding to avoid interactive prompts

The base module is always registered and executed first in the build process.
Other modules can extend this functionality by registering additional modules.

Example:
    The base module is automatically registered by OSBuilder:
        builder = OSBuilder()
        # BaseModule is automatically added to modules list
"""

from pathlib import Path
from typing import Optional
from pymergetic.os.builder.config import Config
from pymergetic.os.builder.utils import run_command, mount_chroot_filesystems, unmount_chroot_filesystems
from pymergetic.os.builder.console import print_step, print_info, print_success, print_warning


class BaseModule:
    """Base OS module for Debian installation and configuration.
    
    This module handles the core Debian installation using debootstrap and
    sets up the essential system components required for a bootable live ISO.
    It is always the first module executed and provides the foundation for
    all other modules.
    
    The module performs the following operations:
    1. Creates Debian chroot using debootstrap
    2. Configures APT package repositories
    3. Installs essential packages (kernel, live-boot, etc.)
    4. Configures system settings (hostname, network)
    5. Regenerates initrd for live boot support
    
    Attributes:
        name: Module identifier ("base").
        description: Human-readable description of the module.
        config: Configuration object containing OS and build settings.
    
    Example:
        The module is instantiated automatically by OSBuilder:
            module = BaseModule(config)
            module.install(chroot_dir)
    """
    
    name = "base"
    description = "Base Debian OS installation"
    
    def __init__(self, config: Config) -> None:
        """Initialize the base module with configuration.
        
        Args:
            config: Configuration object containing OS settings (release,
                   hostname, architecture, packages) and build settings.
        """
        self.config = config
    
    def install(self, chroot_dir: Path) -> None:
        """Install the base Debian system using debootstrap.
        
        Creates a minimal Debian chroot environment in the specified directory.
        Uses debootstrap with the 'minbase' variant for a minimal installation,
        then configures the system and installs essential packages.
        
        The installation process:
        1. Runs debootstrap to create base system
        2. Sets up APT sources for package installation
        3. Installs essential packages (kernel, live-boot, etc.)
        4. Configures system settings (hostname, network)
        
        Args:
            chroot_dir: Path where the chroot environment will be created.
                      This directory will contain the complete Debian root
                      filesystem.
        
        Raises:
            RuntimeError: If debootstrap fails, if package installation
                         fails, or if system configuration fails.
            FileNotFoundError: If debootstrap command is not found.
        
        Note:
            - Requires sudo privileges
            - The chroot directory must not exist or will be overwritten
            - Requires internet access for downloading packages
            - Uses debian-archive-keyring if available for signature verification
        """
        print_step("BASE", f"Installing base OS ({self.config.release})...")
        
        # Check for keyring
        keyring_path = self._get_keyring_path()
        debootstrap_cmd = [
            'debootstrap',
            '--arch', self.config.architecture,
            '--variant=minbase',
        ]
        
        # Add keyring if available
        if keyring_path and keyring_path.exists():
            debootstrap_cmd.extend(['--keyring', str(keyring_path)])
            print_info(f"Using keyring: [bold]{keyring_path}[/bold]")
        else:
            print_warning("No keyring found, signature verification disabled")
        
        debootstrap_cmd.extend([
            self.config.release,
            str(chroot_dir),
            self._get_mirror(),
        ])
        
        # Run debootstrap
        run_command(debootstrap_cmd, sudo=True)
        
        # Setup basic system
        self._setup_system(chroot_dir)
    
    def _get_mirror(self) -> str:
        """Get the Debian mirror URL for the configured release.
        
        Returns the appropriate Debian mirror URL based on the release
        configuration. Currently uses the official Debian mirror for all
        releases.
        
        Returns:
            str: Debian mirror URL (e.g., "http://deb.debian.org/debian").
        
        Note:
            Future enhancements could support custom mirrors or mirror
            selection based on geographic location.
        """
        release = self.config.release
        if release in ['stable', 'testing', 'unstable', 'sid']:
            return f'http://deb.debian.org/debian'
        return f'http://deb.debian.org/debian'
    
    def _get_keyring_path(self) -> Optional[Path]:
        """Locate the Debian archive keyring for package verification.
        
        Searches for the debian-archive-keyring package's GPG keyring file
        in common system locations. The keyring is used by debootstrap to
        verify package signatures for security.
        
        Search locations (in order):
        1. /usr/share/keyrings/debian-archive-keyring.gpg
        2. /usr/share/debootstrap/keyrings/debian-archive-keyring.gpg
        3. Via dpkg -L debian-archive-keyring (if package is installed)
        
        Returns:
            Optional[Path]: Path to the keyring file if found, None otherwise.
        
        Note:
            If no keyring is found, debootstrap will proceed without signature
            verification, which is less secure but still functional. It's
            recommended to install debian-archive-keyring on the host system.
        """
        # Common keyring locations
        keyring_paths = [
            Path('/usr/share/keyrings/debian-archive-keyring.gpg'),
            Path('/usr/share/keyrings/debian-archive-keyring.gpg'),
            Path('/usr/share/debootstrap/keyrings/debian-archive-keyring.gpg'),
        ]
        
        for path in keyring_paths:
            if path.exists():
                return path
        
        # Try to find it via dpkg
        try:
            result = run_command(
                ['dpkg', '-L', 'debian-archive-keyring'],
                capture_output=True,
                check=False,
            )
            if result.returncode == 0:
                for line in result.stdout.decode().split('\n'):
                    if line.endswith('.gpg'):
                        return Path(line)
        except:
            pass
        
        return None
    
    def _setup_system(self, chroot_dir: Path) -> None:
        """Configure the base system after debootstrap.
        
        Performs essential system configuration tasks:
        1. Creates mount point directories (dev, proc, sys, tmp, run)
        2. Configures APT package repositories
        3. Installs essential packages
        4. Configures system settings (hostname, network, live-boot)
        
        Args:
            chroot_dir: Path to the chroot directory to configure.
        
        Raises:
            RuntimeError: If any configuration step fails.
        
        Note:
            This method orchestrates the complete system setup after the
            initial debootstrap installation. It's called automatically
            by install() after debootstrap completes.
        """
        print_step("BASE", "Configuring base system...")
        
        # Create mount point directories (need sudo for chroot)
        for dir_name in ['dev', 'proc', 'sys', 'tmp', 'run']:
            dir_path = chroot_dir / dir_name
            run_command(['mkdir', '-p', str(dir_path)], sudo=True)
        
        # Setup apt sources
        self._setup_apt_sources(chroot_dir)
        
        # Install essential packages
        self._install_essential_packages(chroot_dir)
        
        # Configure system
        self._configure_system(chroot_dir)
    
    def _setup_apt_sources(self, chroot_dir: Path) -> None:
        """Configure APT package repositories in the chroot.
        
        Creates /etc/apt/sources.list with appropriate repositories based on
        the Debian release. For stable releases (bookworm, bullseye), includes
        main, updates, and security repositories. For testing/unstable releases,
        only includes the main repository.
        
        Args:
            chroot_dir: Path to the chroot directory where sources.list will
                      be created.
        
        Raises:
            RuntimeError: If the sources.list file cannot be created.
        
        Note:
            - Stable releases get: main, updates, security
            - Testing/unstable releases get: main only
            - The security repository is not available for testing/unstable
        """
        release = self.config.release
        sources_file = chroot_dir / 'etc' / 'apt' / 'sources.list'
        
        # Determine repositories
        if release in ['stable', 'bookworm', 'bullseye']:
            repos = [
                f'deb http://deb.debian.org/debian {release} main',
                f'deb http://deb.debian.org/debian {release}-updates main',
                f'deb http://security.debian.org/debian-security {release}-security main',
            ]
        else:
            # Testing/unstable only have main repo
            repos = [
                f'deb http://deb.debian.org/debian {release} main',
            ]
        
        # Create directory with sudo
        run_command(['mkdir', '-p', str(sources_file.parent)], sudo=True)
        repos_content = '\n'.join(repos) + '\n'
        
        # Write using temp file and sudo cp
        import tempfile
        
        with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.list') as tmp:
            tmp.write(repos_content)
            tmp_path = tmp.name
        
        try:
            run_command(['cp', tmp_path, str(sources_file)], sudo=True)
        finally:
            import os
            os.unlink(tmp_path)
    
    def _preseed_debconf(self, chroot_dir: Path) -> None:
        """Preseed debconf values to automate package configuration.
        
        Sets debconf values for keyboard-configuration and console-setup
        packages to avoid interactive prompts during package installation.
        The preseeding uses default values suitable for most systems:
        - Keyboard: US English layout (pc105)
        - Console: UTF-8, Latin1/Latin5 codeset, 8x16 font
        
        Args:
            chroot_dir: Path to the chroot directory where debconf values
                      will be set.
        
        Raises:
            RuntimeError: If debconf-set-selections fails.
        
        Note:
            This method is called automatically before package installation
            to ensure a fully non-interactive build process. The preseed file
            is created temporarily and cleaned up after use.
        """
        print_info("Preseeding debconf values...")
        
        # Preseed values for keyboard-configuration and console-setup
        # Format: package question type value
        preseed_values = [
            # Keyboard configuration - US English layout (option 1 from prompt)
            'keyboard-configuration keyboard-configuration/layoutcode string us',
            'keyboard-configuration keyboard-configuration/variantcode string',
            'keyboard-configuration keyboard-configuration/modelcode string pc105',
            # Console setup - Latin1 and Latin5 (option 14 from prompt)
            'console-setup console-setup/charmap47 select UTF-8',
            'console-setup console-setup/codeset47 string Lat15',
            'console-setup console-setup/fontsize47 string 8x16',
            'console-setup console-setup/fontface47 string Fixed',
        ]
        
        # Write preseed values to temp file
        import tempfile
        import os
        
        with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.preseed') as tmp:
            tmp.write('\n'.join(preseed_values) + '\n')
            tmp_path = tmp.name
        
        try:
            # Copy to chroot
            chroot_preseed = chroot_dir / 'tmp' / 'debconf.preseed'
            run_command(['cp', tmp_path, str(chroot_preseed)], sudo=True)
            
            # Apply preseed values
            run_command([
                'chroot', str(chroot_dir),
                'debconf-set-selections', '/tmp/debconf.preseed',
            ], sudo=True)
            
            # Clean up
            run_command(['rm', '-f', str(chroot_preseed)], sudo=True)
        finally:
            os.unlink(tmp_path)
    
    def _install_essential_packages(self, chroot_dir: Path) -> None:
        """Install essential packages required for a bootable live system.
        
        Installs the following essential packages:
        - linux-image-{architecture}: Kernel for the target architecture
        - live-boot: Live system boot infrastructure
        - live-boot-initramfs-tools: Initrd hooks for live-boot
        - systemd-sysv: Systemd as init system
        - initramfs-tools: Tools for creating initrd
        - locales: Locale data
        - keyboard-configuration: Keyboard layout configuration
        - console-setup: Console font and keymap setup
        - User-specified packages from configuration
        - Optional desktop environment (if configured)
        
        The installation process:
        1. Preseeds debconf values to avoid prompts
        2. Updates package lists (apt-get update)
        3. Installs all packages with DEBIAN_FRONTEND=noninteractive
        4. Cleans up package caches and temporary files
        
        Args:
            chroot_dir: Path to the chroot directory where packages will
                      be installed.
        
        Raises:
            RuntimeError: If package installation fails or if required
                         packages cannot be found.
        
        Note:
            - Requires /proc, /sys, /dev to be mounted (handled automatically)
            - Uses --no-install-recommends to keep installation minimal
            - All operations use DEBIAN_FRONTEND=noninteractive
            - Package caches are cleaned after installation to reduce size
        """
        print_step("BASE", "Installing essential packages...")
        
        # Mount filesystems needed for chroot operations
        mount_chroot_filesystems(chroot_dir)
        
        try:
            # Preseed debconf values before installing packages
            self._preseed_debconf(chroot_dir)
            
            packages = [
                f'linux-image-{self.config.architecture}',
                'live-boot',
                'live-boot-initramfs-tools',
                'systemd-sysv',
                'initramfs-tools',
                'locales',
                'keyboard-configuration',
                'console-setup',
            ]
            
            # Add user packages
            packages.extend(self.config.packages)
            
            # Add desktop if specified
            if self.config.desktop:
                packages.append(self.config.desktop)
            
            # Install packages with DEBIAN_FRONTEND=noninteractive
            run_command([
                'chroot', str(chroot_dir),
                'env', 'DEBIAN_FRONTEND=noninteractive',
                'apt-get', 'update',
            ], sudo=True)
            
            run_command([
                'chroot', str(chroot_dir),
                'env', 'DEBIAN_FRONTEND=noninteractive',
                'apt-get', 'install', '-y', '--no-install-recommends',
            ] + packages, sudo=True)
            
            # Cleanup
            run_command([
                'chroot', str(chroot_dir),
                'env', 'DEBIAN_FRONTEND=noninteractive',
                'apt-get', 'clean',
            ], sudo=True)
            
            run_command([
                'chroot', str(chroot_dir),
                'rm', '-rf', '/var/lib/apt/lists/*', '/tmp/*', '/var/tmp/*',
            ], sudo=True)
        finally:
            # Always unmount, even if there's an error
            unmount_chroot_filesystems(chroot_dir)
    
    def _configure_system(self, chroot_dir: Path) -> None:
        """Configure system-level settings in the chroot.
        
        Performs final system configuration:
        1. Sets system hostname from configuration
        2. Configures network interfaces (/etc/network/interfaces)
        3. Creates live-boot directory structure
        4. Regenerates initrd with live-boot hooks
        
        Args:
            chroot_dir: Path to the chroot directory to configure.
        
        Raises:
            RuntimeError: If configuration files cannot be created or if
                         initrd regeneration fails.
        
        Note:
            - Network is configured for DHCP on eth0 interface
            - Initrd regeneration may fail silently (check=False) as it's
              not critical for all use cases
            - Requires /proc, /sys, /dev to be mounted for initrd regeneration
        """
        print_step("BASE", "Configuring system...")
        
        import tempfile
        import os
        
        # Set hostname
        hostname_file = chroot_dir / 'etc' / 'hostname'
        with tempfile.NamedTemporaryFile(mode='w', delete=False) as tmp:
            tmp.write(f'{self.config.hostname}\n')
            tmp_path = tmp.name
        try:
            run_command(['cp', tmp_path, str(hostname_file)], sudo=True)
        finally:
            os.unlink(tmp_path)
        
        # Setup network
        network_dir = chroot_dir / 'etc' / 'network'
        run_command(['mkdir', '-p', str(network_dir)], sudo=True)
        interfaces_file = network_dir / 'interfaces'
        interfaces_content = '''auto lo
iface lo inet loopback

auto eth0
iface eth0 inet dhcp
'''
        with tempfile.NamedTemporaryFile(mode='w', delete=False) as tmp:
            tmp.write(interfaces_content)
            tmp_path = tmp.name
        try:
            run_command(['cp', tmp_path, str(interfaces_file)], sudo=True)
        finally:
            os.unlink(tmp_path)
        
        # Setup live-boot
        live_dir = chroot_dir / 'lib' / 'live' / 'mount' / 'medium'
        run_command(['mkdir', '-p', str(live_dir)], sudo=True)
        
        etc_live_dir = chroot_dir / 'etc' / 'live'
        run_command(['mkdir', '-p', str(etc_live_dir)], sudo=True)
        
        # Create debug/rescue mode handler script
        self._create_debug_rescue_handler(chroot_dir)
        
        # Regenerate initrd (need filesystems mounted)
        print_info("Regenerating initrd...")
        mount_chroot_filesystems(chroot_dir)
        try:
            run_command([
                'chroot', str(chroot_dir),
                'env', 'DEBIAN_FRONTEND=noninteractive',
                'update-initramfs', '-u',
            ], sudo=True, check=False)  # May fail, that's ok
        finally:
            unmount_chroot_filesystems(chroot_dir)
    
    def _create_debug_rescue_handler(self, chroot_dir: Path) -> None:
        """Create debug/rescue mode handler script.
        
        Creates a systemd service that checks for debug/rescue kernel parameters
        and drops to shell if enabled. This runs early in the boot process.
        
        Args:
            chroot_dir: Path to the chroot directory.
        """
        import tempfile
        import os
        
        # Create debug/rescue handler script
        script_dir = chroot_dir / 'usr' / 'local' / 'bin'
        run_command(['mkdir', '-p', str(script_dir)], sudo=True)
        
        script_content = r'''#!/bin/bash
# PymergeticOS Debug/Rescue Mode Handler
# Checks kernel parameters and drops to shell if debug/rescue mode is enabled

# Check kernel command line for debug or rescue parameters
if grep -q "pymergetic.debug=1" /proc/cmdline 2>/dev/null; then
    echo ""
    echo "=========================================="
    echo "PymergeticOS Debug Mode"
    echo "=========================================="
    echo "Booting directly to shell for debugging..."
    echo ""
    
    # Mount essential filesystems if not already mounted
    [ -d /proc ] || mount -t proc proc /proc
    [ -d /sys ] || mount -t sysfs sysfs /sys
    [ -d /dev ] || mount -t devtmpfs devtmpfs /dev || mount -t tmpfs tmpfs /dev
    
    # Drop to shell
    exec /bin/bash
fi

if grep -q "pymergetic.rescue=1" /proc/cmdline 2>/dev/null; then
    echo ""
    echo "=========================================="
    echo "PymergeticOS Rescue Mode"
    echo "=========================================="
    echo "Entering rescue mode..."
    echo ""
    echo "TODO: Rescue mode implementation"
    echo "For now, dropping to debug shell..."
    echo ""
    
    # Mount essential filesystems if not already mounted
    [ -d /proc ] || mount -t proc proc /proc
    [ -d /sys ] || mount -t sysfs sysfs /sys
    [ -d /dev ] || mount -t devtmpfs devtmpfs /dev || mount -t tmpfs tmpfs /dev
    
    # Drop to shell (rescue mode will be extended later)
    exec /bin/bash
fi
'''
        
        script_path = script_dir / 'pymergetic-debug-rescue'
        
        with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.sh') as tmp:
            tmp.write(script_content)
            tmp_path = tmp.name
        
        try:
            run_command(['cp', tmp_path, str(script_path)], sudo=True)
            run_command(['chmod', '+x', str(script_path)], sudo=True)
        finally:
            os.unlink(tmp_path)
        
        # Create systemd service to run early in boot
        service_dir = chroot_dir / 'etc' / 'systemd' / 'system'
        run_command(['mkdir', '-p', str(service_dir)], sudo=True)
        
        service_content = '''[Unit]
Description=PymergeticOS Debug/Rescue Mode Handler
DefaultDependencies=no
Before=sysinit.target
ConditionPathExists=/proc/cmdline

[Service]
Type=oneshot
ExecStart=/usr/local/bin/pymergetic-debug-rescue
StandardOutput=journal+console
StandardError=journal+console
RemainAfterExit=yes

[Install]
WantedBy=sysinit.target
'''
        
        service_path = service_dir / 'pymergetic-debug-rescue.service'
        
        with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.service') as tmp:
            tmp.write(service_content)
            tmp_path = tmp.name
        
        try:
            run_command(['cp', tmp_path, str(service_path)], sudo=True)
            
            # Enable the service in chroot
            mount_chroot_filesystems(chroot_dir)
            try:
                run_command([
                    'chroot', str(chroot_dir),
                    'systemctl', 'enable', 'pymergetic-debug-rescue.service'
                ], sudo=True, check=False)
            finally:
                unmount_chroot_filesystems(chroot_dir)
        finally:
            os.unlink(tmp_path)
    
    def get_assets(self) -> Optional[Path]:
        """Get the path to module assets directory.
        
        Returns None as the base module does not provide any additional
        assets beyond the standard system installation. Other modules may
        override this method to return a path to their asset files.
        
        Returns:
            Optional[Path]: Always returns None for the base module.
        
        Note:
            This method is part of the module interface and may be used
            by the builder to copy additional files into the chroot. Future
            modules (e.g., app module) may return asset paths here.
        """
        return None

