#!/usr/bin/env python3
"""
Integration Tests for Debian bootc-image-builder

This module contains comprehensive integration tests that test all Debian osbuild stages
together in a complete pipeline to ensure they work correctly together.

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
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 TestFullPipeline(unittest.TestCase):
    """Integration tests for the complete Debian osbuild pipeline."""
    
    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 mock context
        self.mock_context = Mock()
        self.mock_context.root = self.temp_dir
        
        # Mock the context.run method
        self.mock_context.run.return_value = Mock(
            returncode=0,
            stdout='',
            stderr=''
        )
    
    def tearDown(self):
        """Clean up test fixtures."""
        shutil.rmtree(self.temp_dir)
    
    def test_complete_pipeline_execution(self):
        """Test the complete pipeline execution with all stages."""
        logger.info("Testing complete pipeline execution")
        
        # Step 1: Filesystem Stage
        logger.info("Step 1: Executing Debian Filesystem Stage")
        filesystem_stage = DebianFilesystemStage({
            'rootfs_type': 'ext4',
            'ostree_integration': True,
            'home_symlink': True
        })
        filesystem_stage.run(self.mock_context)
        
        # Verify filesystem structure was created
        self.assertTrue(os.path.exists(os.path.join(self.temp_dir, 'etc')))
        self.assertTrue(os.path.exists(os.path.join(self.temp_dir, 'usr')))
        self.assertTrue(os.path.exists(os.path.join(self.temp_dir, 'var')))
        self.assertTrue(os.path.exists(os.path.join(self.temp_dir, 'home')))
        
        # Verify OSTree integration points
        self.assertTrue(os.path.exists(os.path.join(self.temp_dir, 'ostree')))
        self.assertTrue(os.path.exists(os.path.join(self.temp_dir, 'usr', 'lib', 'ostree-boot')))
        self.assertTrue(os.path.exists(os.path.join(self.temp_dir, 'etc', 'ostree')))
        
        # Verify home symlink
        home_path = os.path.join(self.temp_dir, 'home')
        self.assertTrue(os.path.islink(home_path))
        self.assertEqual(os.readlink(home_path), '../var/home')
        
        logger.info("✓ Filesystem stage completed successfully")
        
        # Step 2: APT Stage
        logger.info("Step 2: Executing APT Stage")
        apt_stage = AptStage(self.test_options)
        apt_stage.run(self.mock_context)
        
        # Verify APT configuration was created
        self.assertTrue(os.path.exists(os.path.join(self.temp_dir, 'etc', 'apt', 'apt.conf.d', '99osbuild')))
        self.assertTrue(os.path.exists(os.path.join(self.temp_dir, 'etc', 'apt', 'sources.list.d', 'debian.list')))
        
        logger.info("✓ APT stage completed successfully")
        
        # Step 3: Kernel Stage
        logger.info("Step 3: Executing Debian Kernel Stage")
        kernel_stage = DebianKernelStage({
            'kernel_package': 'linux-image-amd64',
            'initramfs_tools': True,
            'ostree_integration': True,
            'modules_autoload': True
        })
        kernel_stage.run(self.mock_context)
        
        # Verify kernel stage setup
        self.assertTrue(os.path.exists(os.path.join(self.temp_dir, 'etc', 'initramfs-tools')))
        self.assertTrue(os.path.exists(os.path.join(self.temp_dir, 'etc', 'modules-load.d')))
        
        logger.info("✓ Kernel stage completed successfully")
        
        # Step 4: GRUB Stage
        logger.info("Step 4: Executing Debian GRUB Stage")
        grub_stage = DebianGrubStage({
            'ostree_integration': True,
            'uefi': True,
            'secure_boot': False,
            'timeout': 5,
            'default_entry': 0
        })
        grub_stage.run(self.mock_context)
        
        # Verify GRUB configuration was created
        self.assertTrue(os.path.exists(os.path.join(self.temp_dir, 'etc', 'default', 'grub')))
        self.assertTrue(os.path.exists(os.path.join(self.temp_dir, 'etc', 'grub.d', '10_ostree')))
        
        logger.info("✓ GRUB stage completed successfully")
        
        logger.info("✓ Complete pipeline execution successful")
    
    def test_stage_dependencies(self):
        """Test that stages handle dependencies correctly."""
        logger.info("Testing stage dependencies")
        
        # Test that filesystem stage creates required directories for other stages
        filesystem_stage = DebianFilesystemStage({
            'rootfs_type': 'ext4',
            'ostree_integration': True,
            'home_symlink': True
        })
        filesystem_stage.run(self.mock_context)
        
        # Verify that APT stage can find its required directories
        apt_stage = AptStage(self.test_options)
        apt_stage.run(self.mock_context)
        
        # Verify that kernel stage can find its required directories
        kernel_stage = DebianKernelStage({
            'kernel_package': 'linux-image-amd64',
            'initramfs_tools': True,
            'ostree_integration': True
        })
        kernel_stage.run(self.mock_context)
        
        # Verify that GRUB stage can find its required directories
        grub_stage = DebianGrubStage({
            'ostree_integration': True,
            'uefi': True
        })
        grub_stage.run(self.mock_context)
        
        logger.info("✓ Stage dependencies handled correctly")
    
    def test_ostree_integration_consistency(self):
        """Test that OSTree integration is consistent across all stages."""
        logger.info("Testing OSTree integration consistency")
        
        # Execute all stages with OSTree integration
        filesystem_stage = DebianFilesystemStage({
            'ostree_integration': True,
            'home_symlink': True
        })
        filesystem_stage.run(self.mock_context)
        
        apt_stage = AptStage(self.test_options)
        apt_stage.run(self.mock_context)
        
        kernel_stage = DebianKernelStage({
            'ostree_integration': True,
            'initramfs_tools': True
        })
        kernel_stage.run(self.mock_context)
        
        grub_stage = DebianGrubStage({
            'ostree_integration': True,
            'uefi': True
        })
        grub_stage.run(self.mock_context)
        
        # Verify OSTree configuration consistency
        ostree_conf = os.path.join(self.temp_dir, 'etc', 'ostree', 'ostree.conf')
        self.assertTrue(os.path.exists(ostree_conf))
        
        # Verify OSTree boot directory structure
        ostree_boot = os.path.join(self.temp_dir, 'usr', 'lib', 'ostree-boot')
        self.assertTrue(os.path.exists(ostree_boot))
        
        # Verify GRUB OSTree configuration
        grub_ostree = os.path.join(self.temp_dir, 'etc', 'grub.d', '10_ostree')
        self.assertTrue(os.path.exists(grub_ostree))
        
        logger.info("✓ OSTree integration consistent across all stages")
    
    def test_error_handling(self):
        """Test error handling in the pipeline."""
        logger.info("Testing error handling")
        
        # Test with invalid options
        with self.assertRaises(ValueError):
            invalid_apt_stage = AptStage({})  # No packages specified
        
        # Test with missing context
        with self.assertRaises(AttributeError):
            filesystem_stage = DebianFilesystemStage({'ostree_integration': True})
            filesystem_stage.run(None)
        
        logger.info("✓ Error handling working correctly")
    
    def test_performance_optimization(self):
        """Test that the pipeline is optimized for performance."""
        logger.info("Testing performance optimization")
        
        import time
        
        # Measure execution time for each stage
        start_time = time.time()
        
        filesystem_stage = DebianFilesystemStage({
            'ostree_integration': True,
            'home_symlink': True
        })
        filesystem_stage.run(self.mock_context)
        filesystem_time = time.time() - start_time
        
        start_time = time.time()
        apt_stage = AptStage(self.test_options)
        apt_stage.run(self.mock_context)
        apt_time = time.time() - start_time
        
        start_time = time.time()
        kernel_stage = DebianKernelStage({
            'ostree_integration': True,
            'initramfs_tools': True
        })
        kernel_stage.run(self.mock_context)
        kernel_time = time.time() - start_time
        
        start_time = time.time()
        grub_stage = DebianGrubStage({
            'ostree_integration': True,
            'uefi': True
        })
        grub_stage.run(self.mock_context)
        grub_time = time.time() - start_time
        
        total_time = filesystem_time + apt_time + kernel_time + grub_time
        
        logger.info(f"Stage execution times:")
        logger.info(f"  Filesystem: {filesystem_time:.3f}s")
        logger.info(f"  APT: {apt_time:.3f}s")
        logger.info(f"  Kernel: {kernel_time:.3f}s")
        logger.info(f"  GRUB: {grub_time:.3f}s")
        logger.info(f"  Total: {total_time:.3f}s")
        
        # Verify reasonable performance (should complete in under 5 seconds for mock)
        self.assertLess(total_time, 5.0)
        
        logger.info("✓ Performance optimization verified")
    
    def test_filesystem_permissions(self):
        """Test that filesystem permissions are set correctly."""
        logger.info("Testing filesystem permissions")
        
        filesystem_stage = DebianFilesystemStage({
            'ostree_integration': True,
            'home_symlink': True
        })
        filesystem_stage.run(self.mock_context)
        
        # Test critical permission settings
        permissions_to_test = [
            ('/etc/passwd', 0o644),
            ('/etc/group', 0o644),
            ('/etc/shadow', 0o640),
            ('/root', 0o700),
            ('/tmp', 0o1777),
            ('/var/tmp', 0o1777)
        ]
        
        for path, expected_mode in permissions_to_test:
            full_path = os.path.join(self.temp_dir, path.lstrip('/'))
            if os.path.exists(full_path):
                actual_mode = oct(os.stat(full_path).st_mode)[-3:]
                expected_mode_str = oct(expected_mode)[-3:]
                self.assertEqual(actual_mode, expected_mode_str, 
                               f"Permission mismatch for {path}: expected {expected_mode_str}, got {actual_mode}")
        
        logger.info("✓ Filesystem permissions set correctly")
    
    def test_user_and_group_setup(self):
        """Test that users and groups are set up correctly."""
        logger.info("Testing user and group setup")
        
        filesystem_stage = DebianFilesystemStage({
            'ostree_integration': True,
            'home_symlink': True
        })
        filesystem_stage.run(self.mock_context)
        
        # Test passwd file
        passwd_file = os.path.join(self.temp_dir, 'etc', 'passwd')
        self.assertTrue(os.path.exists(passwd_file))
        
        with open(passwd_file, 'r') as f:
            passwd_content = f.read()
            self.assertIn('root:x:0:0:root:/root:/bin/bash', passwd_content)
            self.assertIn('debian:x:1000:1000:Debian User:/home/debian:/bin/bash', passwd_content)
        
        # Test group file
        group_file = os.path.join(self.temp_dir, 'etc', 'group')
        self.assertTrue(os.path.exists(group_file))
        
        with open(group_file, 'r') as f:
            group_content = f.read()
            self.assertIn('root:x:0:', group_content)
            self.assertIn('users:x:100:', group_content)
        
        # Test home directories
        root_home = os.path.join(self.temp_dir, 'var', 'home', 'root')
        debian_home = os.path.join(self.temp_dir, 'var', 'home', 'debian')
        
        self.assertTrue(os.path.exists(root_home))
        self.assertTrue(os.path.exists(debian_home))
        
        logger.info("✓ User and group setup correct")


class TestPipelineConfiguration(unittest.TestCase):
    """Tests for pipeline configuration and options."""
    
    def test_distribution_definition_parsing(self):
        """Test that distribution definitions can be parsed correctly."""
        logger.info("Testing distribution definition parsing")
        
        # Test parsing of debian-13.yaml
        yaml_file = os.path.join(os.path.dirname(__file__), '..', '..', 'bib', 'data', 'defs', 'debian-13.yaml')
        
        if os.path.exists(yaml_file):
            import yaml
            with open(yaml_file, 'r') as f:
                config = yaml.safe_load(f)
            
            # Verify basic structure
            self.assertIn('qcow2', config)
            self.assertIn('desktop', config)
            self.assertIn('server', config)
            self.assertIn('development', config)
            
            # Verify qcow2 configuration
            qcow2_config = config['qcow2']
            self.assertIn('packages', qcow2_config)
            self.assertIn('stages', qcow2_config)
            
            # Verify packages list
            packages = qcow2_config['packages']
            self.assertIn('linux-image-amd64', packages)
            self.assertIn('systemd', packages)
            self.assertIn('initramfs-tools', packages)
            
            logger.info("✓ Distribution definition parsing successful")
        else:
            logger.warning("Distribution definition file not found, skipping test")
    
    def test_stage_option_validation(self):
        """Test that stage options are validated correctly."""
        logger.info("Testing stage option validation")
        
        # Test APT stage validation
        with self.assertRaises(ValueError):
            AptStage({})  # No packages
        
        # Test valid APT stage
        valid_apt = AptStage({'packages': ['linux-image-amd64']})
        self.assertEqual(valid_apt.packages, ['linux-image-amd64'])
        
        # Test kernel stage validation
        valid_kernel = DebianKernelStage({
            'kernel_package': 'linux-image-amd64',
            'ostree_integration': True
        })
        self.assertEqual(valid_kernel.kernel_package, 'linux-image-amd64')
        self.assertTrue(valid_kernel.ostree_integration)
        
        # Test GRUB stage validation
        valid_grub = DebianGrubStage({
            'ostree_integration': True,
            'uefi': True
        })
        self.assertTrue(valid_grub.ostree_integration)
        self.assertTrue(valid_grub.uefi)
        
        # Test filesystem stage validation
        valid_fs = DebianFilesystemStage({
            'ostree_integration': True,
            'home_symlink': True
        })
        self.assertTrue(valid_fs.ostree_integration)
        self.assertTrue(valid_fs.home_symlink)
        
        logger.info("✓ Stage option validation working correctly")


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()
