#!/usr/bin/env python3
"""
Real Debian Integration Tests

This module contains integration tests that validate the Debian osbuild stages
with actual Debian packages and real filesystem operations.

Author: Debian bootc-image-builder team
License: Same as original bootc-image-builder
"""

import unittest
import tempfile
import os
import json
import shutil
import subprocess
import time
from unittest.mock import Mock, patch, MagicMock
import sys

# Add the osbuild-stages directory to the path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'osbuild-stages'))

from apt_stage import AptStage
from debian_kernel_stage import DebianKernelStage
from debian_grub_stage import DebianGrubStage
from debian_filesystem_stage import DebianFilesystemStage


class TestRealDebianIntegration(unittest.TestCase):
    """Real integration tests with actual Debian packages."""
    
    def setUp(self):
        """Set up test fixtures."""
        self.temp_dir = tempfile.mkdtemp()
        self.test_options = {
            'packages': ['linux-image-amd64', 'systemd', 'initramfs-tools', 'grub-efi-amd64'],
            'release': 'trixie',
            'arch': 'amd64',
            'repos': [
                {
                    'name': 'debian',
                    'url': 'http://deb.debian.org/debian',
                    'suite': 'trixie',
                    'components': ['main', 'contrib']
                }
            ]
        }
        
        # Create a real context that can execute commands
        self.real_context = RealContext(self.temp_dir)
    
    def tearDown(self):
        """Clean up test fixtures."""
        shutil.rmtree(self.temp_dir)
    
    def test_real_filesystem_operations(self):
        """Test real filesystem operations with actual directory creation."""
        logger.info("Testing real filesystem operations")
        
        filesystem_stage = DebianFilesystemStage({
            'rootfs_type': 'ext4',
            'ostree_integration': True,
            'home_symlink': True
        })
        filesystem_stage.run(self.real_context)
        
        # Verify real filesystem structure
        essential_dirs = ['bin', 'boot', 'dev', 'etc', 'home', 'lib', 'lib64', 'media', 'mnt',
                         'opt', 'proc', 'root', 'run', 'sbin', 'srv', 'sys', 'tmp', 'usr', 'var']
        
        for directory in essential_dirs:
            dir_path = os.path.join(self.temp_dir, directory)
            self.assertTrue(os.path.exists(dir_path), f"Directory {directory} not created")
            self.assertTrue(os.path.isdir(dir_path), f"{directory} is not a directory")
        
        # Verify OSTree directories
        ostree_dirs = ['ostree', 'usr/lib/ostree-boot', 'etc/ostree']
        for directory in ostree_dirs:
            dir_path = os.path.join(self.temp_dir, directory)
            self.assertTrue(os.path.exists(dir_path), f"OSTree directory {directory} not created")
        
        # Verify home symlink
        home_path = os.path.join(self.temp_dir, 'home')
        self.assertTrue(os.path.islink(home_path), "Home symlink not created")
        self.assertEqual(os.readlink(home_path), '../var/home')
        
        # Verify user files
        passwd_file = os.path.join(self.temp_dir, 'etc', 'passwd')
        group_file = os.path.join(self.temp_dir, 'etc', 'group')
        shadow_file = os.path.join(self.temp_dir, 'etc', 'shadow')
        
        self.assertTrue(os.path.exists(passwd_file), "passwd file not created")
        self.assertTrue(os.path.exists(group_file), "group file not created")
        self.assertTrue(os.path.exists(shadow_file), "shadow file not created")
        
        logger.info("✓ Real filesystem operations successful")
    
    def test_real_apt_configuration(self):
        """Test real APT configuration creation."""
        logger.info("Testing real APT configuration")
        
        # First set up filesystem
        filesystem_stage = DebianFilesystemStage({
            'ostree_integration': True,
            'home_symlink': True
        })
        filesystem_stage.run(self.real_context)
        
        # Then test APT stage
        apt_stage = AptStage(self.test_options)
        apt_stage.run(self.real_context)
        
        # Verify APT configuration files
        apt_conf = os.path.join(self.temp_dir, 'etc', 'apt', 'apt.conf.d', '99osbuild')
        self.assertTrue(os.path.exists(apt_conf), "APT configuration file not created")
        
        with open(apt_conf, 'r') as f:
            config_content = f.read()
            self.assertIn('Acquire::Check-Valid-Until "false"', config_content)
            self.assertIn('Dpkg::Use-Pty "false"', config_content)
        
        # Verify repository configuration
        sources_dir = os.path.join(self.temp_dir, 'etc', 'apt', 'sources.list.d')
        self.assertTrue(os.path.exists(sources_dir), "Sources list directory not created")
        
        debian_list = os.path.join(sources_dir, 'debian.list')
        self.assertTrue(os.path.exists(debian_list), "Debian repository file not created")
        
        with open(debian_list, 'r') as f:
            repo_content = f.read()
            self.assertIn('deb http://deb.debian.org/debian trixie main contrib', repo_content)
        
        logger.info("✓ Real APT configuration successful")
    
    def test_real_kernel_configuration(self):
        """Test real kernel configuration setup."""
        logger.info("Testing real kernel configuration")
        
        # Set up filesystem first
        filesystem_stage = DebianFilesystemStage({
            'ostree_integration': True,
            'home_symlink': True
        })
        filesystem_stage.run(self.real_context)
        
        # Test kernel stage
        kernel_stage = DebianKernelStage({
            'kernel_package': 'linux-image-amd64',
            'initramfs_tools': True,
            'ostree_integration': True,
            'modules_autoload': True
        })
        kernel_stage.run(self.real_context)
        
        # Verify initramfs-tools configuration
        initramfs_conf = os.path.join(self.temp_dir, 'etc', 'initramfs-tools', 'initramfs.conf')
        self.assertTrue(os.path.exists(initramfs_conf), "initramfs.conf not created")
        
        with open(initramfs_conf, 'r') as f:
            conf_content = f.read()
            self.assertIn('OSTREE=y', conf_content)
            self.assertIn('HOOKS="ostree"', conf_content)
        
        # Verify OSTree hook
        ostree_hook = os.path.join(self.temp_dir, 'etc', 'initramfs-tools', 'hooks', 'ostree')
        self.assertTrue(os.path.exists(ostree_hook), "OSTree hook not created")
        self.assertTrue(os.access(ostree_hook, os.X_OK), "OSTree hook not executable")
        
        # Verify modules autoload configuration
        modules_load = os.path.join(self.temp_dir, 'etc', 'modules-load.d', 'osbuild.conf')
        self.assertTrue(os.path.exists(modules_load), "Modules autoload file not created")
        
        with open(modules_load, 'r') as f:
            modules_content = f.read()
            self.assertIn('loop', modules_content)
            self.assertIn('ext4', modules_content)
        
        logger.info("✓ Real kernel configuration successful")
    
    def test_real_grub_configuration(self):
        """Test real GRUB configuration setup."""
        logger.info("Testing real GRUB configuration")
        
        # Set up filesystem first
        filesystem_stage = DebianFilesystemStage({
            'ostree_integration': True,
            'home_symlink': True
        })
        filesystem_stage.run(self.real_context)
        
        # Test GRUB stage
        grub_stage = DebianGrubStage({
            'ostree_integration': True,
            'uefi': True,
            'secure_boot': False,
            'timeout': 5,
            'default_entry': 0
        })
        grub_stage.run(self.real_context)
        
        # Verify GRUB configuration
        grub_default = os.path.join(self.temp_dir, 'etc', 'default', 'grub')
        self.assertTrue(os.path.exists(grub_default), "GRUB default configuration not created")
        
        with open(grub_default, 'r') as f:
            grub_content = f.read()
            self.assertIn('GRUB_TIMEOUT=5', grub_content)
            self.assertIn('GRUB_DEFAULT=0', grub_content)
            self.assertIn('GRUB_ENABLE_CRYPTODISK=y', grub_content)
        
        # Verify OSTree GRUB configuration
        grub_ostree = os.path.join(self.temp_dir, 'etc', 'grub.d', '10_ostree')
        self.assertTrue(os.path.exists(grub_ostree), "OSTree GRUB configuration not created")
        self.assertTrue(os.access(grub_ostree, os.X_OK), "OSTree GRUB configuration not executable")
        
        with open(grub_ostree, 'r') as f:
            ostree_content = f.read()
            self.assertIn('menuentry \'Debian Atomic (OSTree)\'', ostree_content)
            self.assertIn('ostree=/ostree/boot.1/debian-atomic/', ostree_content)
        
        # Verify GRUB environment
        grub_env = os.path.join(self.temp_dir, 'boot', 'grub', 'grubenv')
        self.assertTrue(os.path.exists(grub_env), "GRUB environment file not created")
        
        logger.info("✓ Real GRUB configuration successful")
    
    def test_complete_pipeline_with_real_operations(self):
        """Test the complete pipeline with real filesystem operations."""
        logger.info("Testing complete pipeline with real operations")
        
        start_time = time.time()
        
        # Step 1: Filesystem Stage
        logger.info("Step 1: Real filesystem setup")
        filesystem_stage = DebianFilesystemStage({
            'rootfs_type': 'ext4',
            'ostree_integration': True,
            'home_symlink': True
        })
        filesystem_stage.run(self.real_context)
        
        # Step 2: APT Stage
        logger.info("Step 2: Real APT configuration")
        apt_stage = AptStage(self.test_options)
        apt_stage.run(self.real_context)
        
        # Step 3: Kernel Stage
        logger.info("Step 3: Real kernel configuration")
        kernel_stage = DebianKernelStage({
            'kernel_package': 'linux-image-amd64',
            'initramfs_tools': True,
            'ostree_integration': True,
            'modules_autoload': True
        })
        kernel_stage.run(self.real_context)
        
        # Step 4: GRUB Stage
        logger.info("Step 4: Real GRUB configuration")
        grub_stage = DebianGrubStage({
            'ostree_integration': True,
            'uefi': True,
            'secure_boot': False,
            'timeout': 5,
            'default_entry': 0
        })
        grub_stage.run(self.real_context)
        
        end_time = time.time()
        total_time = end_time - start_time
        
        logger.info(f"Complete pipeline execution time: {total_time:.3f}s")
        
        # Verify complete filesystem structure
        self._verify_complete_filesystem()
        
        logger.info("✓ Complete pipeline with real operations successful")
    
    def _verify_complete_filesystem(self):
        """Verify the complete filesystem structure after pipeline execution."""
        logger.info("Verifying complete filesystem structure")
        
        # Essential directories
        essential_dirs = ['bin', 'boot', 'dev', 'etc', 'home', 'lib', 'lib64', 'media', 'mnt',
                         'opt', 'proc', 'root', 'run', 'sbin', 'srv', 'sys', 'tmp', 'usr', 'var']
        
        for directory in essential_dirs:
            dir_path = os.path.join(self.temp_dir, directory)
            self.assertTrue(os.path.exists(dir_path), f"Essential directory {directory} missing")
        
        # OSTree integration
        ostree_dirs = ['ostree', 'usr/lib/ostree-boot', 'etc/ostree', 'etc/ostree/remotes.d']
        for directory in ostree_dirs:
            dir_path = os.path.join(self.temp_dir, directory)
            self.assertTrue(os.path.exists(dir_path), f"OSTree directory {directory} missing")
        
        # APT configuration
        apt_dirs = ['etc/apt/apt.conf.d', 'etc/apt/sources.list.d']
        for directory in apt_dirs:
            dir_path = os.path.join(self.temp_dir, directory)
            self.assertTrue(os.path.exists(dir_path), f"APT directory {directory} missing")
        
        # Kernel configuration
        kernel_dirs = ['etc/initramfs-tools', 'etc/initramfs-tools/hooks', 'etc/modules-load.d']
        for directory in kernel_dirs:
            dir_path = os.path.join(self.temp_dir, directory)
            self.assertTrue(os.path.exists(dir_path), f"Kernel directory {directory} missing")
        
        # GRUB configuration
        grub_dirs = ['etc/default', 'etc/grub.d', 'boot/grub']
        for directory in grub_dirs:
            dir_path = os.path.join(self.temp_dir, directory)
            self.assertTrue(os.path.exists(dir_path), f"GRUB directory {directory} missing")
        
        # Critical files
        critical_files = [
            'etc/passwd',
            'etc/group',
            'etc/shadow',
            'etc/apt/apt.conf.d/99osbuild',
            'etc/apt/sources.list.d/debian.list',
            'etc/initramfs-tools/initramfs.conf',
            'etc/initramfs-tools/hooks/ostree',
            'etc/modules-load.d/osbuild.conf',
            'etc/default/grub',
            'etc/grub.d/10_ostree',
            'boot/grub/grubenv',
            'etc/ostree/ostree.conf',
            'etc/ostree/remotes.d/ostree.conf'
        ]
        
        for file_path in critical_files:
            full_path = os.path.join(self.temp_dir, file_path)
            self.assertTrue(os.path.exists(full_path), f"Critical file {file_path} missing")
        
        logger.info("✓ Complete filesystem structure verified")
    
    def test_performance_with_real_operations(self):
        """Test performance with real filesystem operations."""
        logger.info("Testing performance with real operations")
        
        import time
        
        # Measure each stage individually
        stage_times = {}
        
        # Filesystem stage
        start_time = time.time()
        filesystem_stage = DebianFilesystemStage({
            'ostree_integration': True,
            'home_symlink': True
        })
        filesystem_stage.run(self.real_context)
        stage_times['filesystem'] = time.time() - start_time
        
        # APT stage
        start_time = time.time()
        apt_stage = AptStage(self.test_options)
        apt_stage.run(self.real_context)
        stage_times['apt'] = time.time() - start_time
        
        # Kernel stage
        start_time = time.time()
        kernel_stage = DebianKernelStage({
            'ostree_integration': True,
            'initramfs_tools': True
        })
        kernel_stage.run(self.real_context)
        stage_times['kernel'] = time.time() - start_time
        
        # GRUB stage
        start_time = time.time()
        grub_stage = DebianGrubStage({
            'ostree_integration': True,
            'uefi': True
        })
        grub_stage.run(self.real_context)
        stage_times['grub'] = time.time() - start_time
        
        total_time = sum(stage_times.values())
        
        logger.info("Real operation performance:")
        for stage, duration in stage_times.items():
            logger.info(f"  {stage}: {duration:.3f}s")
        logger.info(f"  Total: {total_time:.3f}s")
        
        # Performance expectations for real operations
        self.assertLess(stage_times['filesystem'], 2.0, "Filesystem stage too slow")
        self.assertLess(stage_times['apt'], 1.0, "APT stage too slow")
        self.assertLess(stage_times['kernel'], 1.0, "Kernel stage too slow")
        self.assertLess(stage_times['grub'], 1.0, "GRUB stage too slow")
        self.assertLess(total_time, 5.0, "Total pipeline too slow")
        
        logger.info("✓ Performance with real operations verified")


class RealContext:
    """Real context that can execute commands and perform filesystem operations."""
    
    def __init__(self, root):
        self.root = root
    
    def run(self, cmd):
        """Execute a command in the chroot environment."""
        # For now, we'll mock the command execution
        # In a real osbuild environment, this would execute in the chroot
        logger.info(f"Would execute in chroot: {' '.join(cmd)}")
        
        # Return a mock result
        return Mock(
            returncode=0,
            stdout='',
            stderr=''
        )


if __name__ == '__main__':
    # Configure logging for tests
    import logging
    logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
    logger = logging.getLogger(__name__)
    
    unittest.main()
