"""
Build Orchestrator

Orchestrates the build process with real-time monitoring and progress tracking.
"""

import asyncio
import datetime
import logging
import os
import subprocess
from pathlib import Path
from typing import Callable, Optional

import psutil

try:
    from git import GitCommandError, InvalidGitRepositoryError, Repo

    GIT_AVAILABLE = True
except ModuleNotFoundError:
    GIT_AVAILABLE = False
    Repo = None
    GitCommandError = InvalidGitRepositoryError = Exception

from ..models.config import BuildConfiguration
from ..models.device import PCIDevice
from ..models.progress import BuildProgress, BuildStage, ValidationResult

# TODO: Import repo manager - may need to be moved to proper package location
try:
    from ...file_management.repo_manager import RepoManager
except ImportError:
    RepoManager = None

# Git repository information
PCILEECH_FPGA_REPO = "https://github.com/ufrisk/pcileech-fpga.git"
REPO_CACHE_DIR = Path(os.path.expanduser("~/.cache/pcileech-fw-generator/repos"))

# Progress parsing tokens for easier maintenance
LOG_PROGRESS_TOKENS = {
    "Running synthesis": (BuildStage.VIVADO_SYNTHESIS, 25, "Running synthesis"),
    "Running implementation": (
        BuildStage.VIVADO_SYNTHESIS,
        50,
        "Running implementation",
    ),
    "Generating bitstream": (BuildStage.VIVADO_SYNTHESIS, 75, "Generating bitstream"),
}

logger = logging.getLogger(__name__)


class BuildOrchestrator:
    """Orchestrates the build process with real-time monitoring"""

    def __init__(self):
        self._current_progress: Optional[BuildProgress] = None
        self._build_process: Optional[asyncio.subprocess.Process] = None
        self._progress_callback: Optional[Callable[[BuildProgress], None]] = None
        self._is_building = False
        self._should_cancel = False

    async def start_build(
        self,
        device: PCIDevice,
        config: BuildConfiguration,
        progress_callback: Optional[Callable[[BuildProgress], None]] = None,
    ) -> bool:
        """Start build with progress monitoring"""
        if self._is_building:
            raise RuntimeError("Build already in progress")

        self._is_building = True
        self._should_cancel = False
        self._progress_callback = progress_callback

        # Initialize progress tracking
        self._current_progress = BuildProgress(
            stage=BuildStage.ENVIRONMENT_VALIDATION,
            completion_percent=0.0,
            current_operation="Initializing build process",
        )

        try:
            # Define build stages as ordered list of (stage, coro, start_msg, end_msg) tuples
            build_stages = [
                (
                    BuildStage.ENVIRONMENT_VALIDATION,
                    lambda: self._validate_environment(),
                    "Validating environment",
                    "Environment validation complete",
                ),
                (
                    BuildStage.ENVIRONMENT_VALIDATION,
                    lambda: self._validate_pci_config(device, config),
                    "Validating PCI configuration values",
                    "PCI configuration validation complete",
                ),
            ]

            # Add donor module check if needed
            if config.donor_dump and not config.local_build:
                build_stages.append(
                    (
                        BuildStage.ENVIRONMENT_VALIDATION,
                        lambda: self._check_donor_module(config),
                        "Checking donor_dump module status",
                        "Donor module check complete",
                    )
                )

            # Add remaining stages
            build_stages.extend(
                [
                    (
                        BuildStage.DEVICE_ANALYSIS,
                        lambda: self._analyze_device(device),
                        "Analyzing device configuration",
                        "Device analysis complete",
                    ),
                    (
                        BuildStage.REGISTER_EXTRACTION,
                        lambda: self._extract_registers(device),
                        "Extracting device registers",
                        "Register extraction complete",
                    ),
                ]
            )

            # Add behavior profiling if enabled
            if config.behavior_profiling:
                build_stages.append(
                    (
                        BuildStage.REGISTER_EXTRACTION,
                        lambda: self._run_behavior_profiling(device, config),
                        "Starting behavior profiling",
                        "Behavior profiling complete",
                    )
                )

            # Add final stages
            build_stages.extend(
                [
                    (
                        BuildStage.SYSTEMVERILOG_GENERATION,
                        lambda: self._generate_systemverilog(device, config),
                        "Generating SystemVerilog",
                        "SystemVerilog generation complete",
                    ),
                    (
                        BuildStage.VIVADO_SYNTHESIS,
                        lambda: self._run_vivado_synthesis(device, config),
                        "Starting Vivado synthesis",
                        "Vivado synthesis complete",
                    ),
                    (
                        BuildStage.BITSTREAM_GENERATION,
                        lambda: self._generate_bitstream(config),
                        "Generating bitstream",
                        "Bitstream generation complete",
                    ),
                ]
            )

            # Execute all stages using the helper
            for stage, coro, start_msg, end_msg in build_stages:
                await self._run_stage(stage, coro, start_msg, end_msg)

            # Build complete
            self._current_progress.completion_percent = 100.0
            self._current_progress.current_operation = "Build completed successfully"
            await self._notify_progress()

            return True

        except Exception as e:
            if self._current_progress:
                self._current_progress.add_error(f"Build failed: {str(e)}")
                await self._notify_progress()
            raise
        finally:
            self._is_building = False

    async def _run_stage(
        self, stage: BuildStage, coro: Callable, start_msg: str, end_msg: str
    ) -> None:
        """Helper to run a stage with progress tracking"""
        if self._should_cancel:
            raise asyncio.CancelledError("Build cancelled")

        await self._update_progress(stage, 0, start_msg)
        await coro()
        await self._update_progress(stage, 100, end_msg)
        self._current_progress.mark_stage_complete(stage)

    async def cancel_build(self) -> None:
        """Cancel the current build"""
        self._should_cancel = True
        if self._build_process:
            try:
                self._build_process.terminate()
                # Wait a bit for graceful termination
                await asyncio.sleep(2)
                if self._build_process.returncode is None:
                    self._build_process.kill()
            except (psutil.Error, asyncio.CancelledError) as e:
                logger.exception(f"Error during build cancellation: {e}")

    def get_current_progress(self) -> Optional[BuildProgress]:
        """Get current build progress"""
        return self._current_progress

    def is_building(self) -> bool:
        """Check if build is in progress"""
        return self._is_building

    async def _update_progress(
        self, stage: BuildStage, percent: float, operation: str
    ) -> None:
        """Update progress and notify callback"""
        if self._current_progress:
            self._current_progress.stage = stage
            self._current_progress.completion_percent = percent
            self._current_progress.current_operation = operation

            # Update resource usage
            await self._update_resource_usage()

            await self._notify_progress()

    async def _notify_progress(self) -> None:
        """Notify progress callback"""
        if self._progress_callback and self._current_progress:
            try:
                self._progress_callback(self._current_progress)
            except Exception as e:
                logger.exception(f"Progress callback error: {e}")

    async def _update_resource_usage(self) -> None:
        """Update system resource usage"""
        try:
            cpu_percent = psutil.cpu_percent(interval=0.1)
            memory = psutil.virtual_memory()
            disk = psutil.disk_usage("/")

            if self._current_progress:
                self._current_progress.update_resource_usage(
                    cpu=cpu_percent,
                    memory=memory.used / (1024**3),  # GB
                    disk_free=disk.free / (1024**3),  # GB
                )
        except (psutil.Error, OSError) as e:
            logger.warning(f"Resource monitoring failed: {e}")

    async def _validate_environment(self) -> None:
        """Validate build environment"""
        # Get current configuration
        app = self._get_app()
        config = getattr(app, "current_config", None)
        local_build = config and config.local_build

        # For local builds, we have more relaxed requirements
        if not local_build:
            # Check if running as root (only needed for non-local builds)
            if os.geteuid() != 0:
                raise RuntimeError("Root privileges required for device binding")

            # Check if Podman is available (only needed for non-local builds)
            try:
                result = await self._run_shell("podman --version", monitor=False)
                if result.returncode != 0:
                    raise RuntimeError("Podman not available")
            except FileNotFoundError:
                raise RuntimeError("Podman not found in PATH")

            # Check if container image exists
            result = await self._run_shell(
                "podman images pcileech-fw-generator --format '{{.Repository}}'",
                monitor=False,
            )
            if "pcileech-fw-generator" not in result.stdout:
                # Container image not found, try to build it
                if self._current_progress:
                    self._current_progress.current_operation = (
                        "Building container image 'pcileech-fw-generator'"
                    )
                    await self._notify_progress()

                try:
                    logger.info(
                        "Container image 'pcileech-fw-generator' not found. Building it now..."
                    )
                    build_result = await self._run_shell(
                        "podman build -t pcileech-fw-generator:latest .", monitor=False
                    )
                    if build_result.returncode != 0:
                        raise RuntimeError(
                            f"Failed to build container image: {build_result.stderr}"
                        )
                    logger.info("Container image built successfully")
                except Exception as e:
                    raise RuntimeError(
                        f"Container image 'pcileech-fw-generator' not found and build failed: {str(e)}"
                    )
        else:
            # For local builds, just check if Python and build.py are available
            if not Path("src/build.py").exists():
                raise RuntimeError("build.py not found in src directory")

            # Check if donor info file exists if specified
            if (
                config
                and config.donor_info_file
                and not Path(config.donor_info_file).exists()
            ):
                if self._current_progress:
                    self._current_progress.add_warning(
                        f"Donor info file not found: {config.donor_info_file}"
                    )

        # Check output directory
        output_dir = Path("output")
        output_dir.mkdir(exist_ok=True)

        # Ensure pcileech-fpga repository is available
        await self._ensure_git_repo()

    async def _ensure_git_repo(self) -> None:
        """Ensure that the pcileech-fpga git repository is available"""
        if self._current_progress:
            self._current_progress.current_operation = (
                "Checking pcileech-fpga repository"
            )
            await self._notify_progress()

        # Create cache directory if it doesn't exist
        repo_dir = REPO_CACHE_DIR / "pcileech-fpga"
        os.makedirs(REPO_CACHE_DIR, exist_ok=True)

        # Use RepoManager if available, otherwise fallback
        if RepoManager is not None:
            repo_path = RepoManager.ensure_repo(repo_url=PCILEECH_FPGA_REPO)
            if self._current_progress:
                self._current_progress.current_operation = (
                    f"PCILeech FPGA repository ensured at {repo_path}"
                )
                await self._notify_progress()
            return

        # Fallback: Check if GitPython is available
        if not GIT_AVAILABLE or Repo is None:
            # Create directory as fallback
            os.makedirs(repo_dir, exist_ok=True)
            if self._current_progress:
                self._current_progress.add_warning(
                    "GitPython not available. Using fallback directory."
                )
                self._current_progress.current_operation = (
                    f"Using fallback directory at {repo_dir}"
                )
                await self._notify_progress()
            return

        # Check if repository already exists
        try:
            repo = Repo(repo_dir)
            # Ensure directory exists (for test compatibility)
            os.makedirs(repo_dir, exist_ok=True)
            if self._current_progress:
                self._current_progress.current_operation = (
                    f"PCILeech FPGA repository found at {repo_dir}"
                )
                await self._notify_progress()

            # Check if repository needs update (older than 7 days)
            try:
                last_update_file = repo_dir / ".last_update"
                update_needed = True

                if last_update_file.exists():
                    with open(last_update_file, "r") as f:
                        try:
                            last_update = datetime.datetime.fromisoformat(
                                f.read().strip()
                            )
                            days_since_update = (
                                datetime.datetime.now() - last_update
                            ).days
                            update_needed = days_since_update >= 7
                        except (ValueError, TypeError):
                            update_needed = True

                if update_needed:
                    if self._current_progress:
                        self._current_progress.current_operation = (
                            "Updating PCILeech FPGA repository"
                        )
                        await self._notify_progress()

                    try:
                        # Pull latest changes
                        origin = repo.remotes.origin
                        origin.pull()

                        # Update last update timestamp
                        with open(last_update_file, "w") as f:
                            f.write(datetime.datetime.now().isoformat())

                        if self._current_progress:
                            self._current_progress.current_operation = (
                                "PCILeech FPGA repository updated successfully"
                            )
                            await self._notify_progress()
                    except GitCommandError as e:
                        if self._current_progress:
                            self._current_progress.add_warning(
                                f"Failed to update repository: {str(e)}"
                            )
            except (OSError, IOError) as e:
                if self._current_progress:
                    self._current_progress.add_warning(
                        f"Error checking repository update status: {str(e)}"
                    )

        except (InvalidGitRepositoryError, GitCommandError):
            # Repository doesn't exist or is corrupted, clone it
            if self._current_progress:
                self._current_progress.current_operation = (
                    f"Cloning PCILeech FPGA repository to {repo_dir}"
                )
                await self._notify_progress()

            try:
                # Remove existing directory if it exists but is not a valid git repo
                if os.path.exists(repo_dir):
                    import shutil

                    shutil.rmtree(repo_dir)

                # Clone repository
                repo = Repo.clone_from(PCILEECH_FPGA_REPO, repo_dir)

                # Create last update timestamp
                with open(repo_dir / ".last_update", "w") as f:
                    f.write(datetime.datetime.now().isoformat())

                if self._current_progress:
                    self._current_progress.current_operation = (
                        "PCILeech FPGA repository cloned successfully"
                    )
                    await self._notify_progress()
            except (GitCommandError, OSError) as e:
                if self._current_progress:
                    self._current_progress.add_error(
                        f"Failed to clone repository: {str(e)}"
                    )
                raise RuntimeError(
                    f"Failed to clone PCILeech FPGA repository: {str(e)}"
                )

    async def _check_donor_module(self, config: BuildConfiguration) -> None:
        """
        Check if donor_dump kernel module is properly installed

        Args:
            config: Current build configuration
        """
        # Skip check if donor_dump is disabled or using local build
        if not config.donor_dump or config.local_build:
            return

        try:
            # TODO: Import donor_dump_manager - may need proper package structure
            import sys
            from pathlib import Path

            sys.path.append(str(Path(__file__).parent.parent.parent.parent))
            from file_management.donor_dump_manager import DonorDumpManager

            # Create manager and check status
            manager = DonorDumpManager()
            module_status = manager.check_module_installation()

            # Update progress with module status
            if self._current_progress:
                status = module_status["status"]
                details = module_status["details"]

                if status != "installed":
                    # Add warning about module status
                    self._current_progress.add_warning(
                        f"Donor module status: {details}"
                    )

                    # Add first issue and fix to progress
                    issues = module_status.get("issues", [])
                    fixes = module_status.get("fixes", [])

                    if issues:
                        self._current_progress.add_warning(f"Issue: {issues[0]}")
                    if fixes:
                        self._current_progress.add_warning(f"Suggested fix: {fixes[0]}")

                    # If auto_install_headers is enabled, try to fix common issues
                    if (
                        config.auto_install_headers
                        and status == "not_built"
                        and "headers" in str(issues)
                    ):
                        self._current_progress.current_operation = (
                            "Attempting to install kernel headers"
                        )
                        await self._notify_progress()

                        # Get kernel version and distribution info
                        kernel_version = module_status.get("raw_status", {}).get(
                            "kernel_version", ""
                        )

                        if kernel_version:
                            # Try to install headers using the manager's method
                            # which handles different Linux distributions
                            try:
                                self._current_progress.add_warning(
                                    "Detecting Linux distribution and installing appropriate headers..."
                                )

                                # Install headers
                                headers_installed = manager.install_kernel_headers(
                                    kernel_version
                                )

                                if headers_installed:
                                    self._current_progress.add_warning(
                                        "Kernel headers installed successfully"
                                    )

                                    # Try to build module
                                    self._current_progress.current_operation = (
                                        "Building donor_dump module"
                                    )
                                    await self._notify_progress()

                                    # Build module
                                    try:
                                        manager.build_module(force_rebuild=True)
                                        self._current_progress.add_warning(
                                            "Donor module built successfully"
                                        )
                                    except Exception as build_error:
                                        self._current_progress.add_error(
                                            f"Failed to build module: {str(build_error)}"
                                        )
                                        # Add more detailed error information
                                        if "ModuleBuildError" in str(type(build_error)):
                                            self._current_progress.add_warning(
                                                "This may be due to kernel version mismatch or missing build tools."
                                            )
                                            self._current_progress.add_warning(
                                                "Try installing build-essential package: sudo apt-get install build-essential"
                                            )
                                else:
                                    self._current_progress.add_error(
                                        "Failed to install kernel headers automatically"
                                    )
                                    # Add manual instructions
                                    distro = manager._detect_linux_distribution()
                                    install_cmd = manager._get_header_install_command(
                                        distro, kernel_version
                                    )
                                    self._current_progress.add_warning(
                                        f"Please try installing headers manually: {install_cmd}"
                                    )
                            except Exception as e:
                                self._current_progress.add_error(
                                    f"Failed to install kernel headers: {str(e)}"
                                )
                                self._current_progress.add_warning(
                                    "You may need to install kernel headers manually for your distribution"
                                )
                else:
                    # Module is properly installed
                    self._current_progress.current_operation = (
                        "Donor module is properly installed"
                    )
                    await self._notify_progress()

        except ImportError as e:
            logger.exception(f"Failed to import donor_dump_manager: {e}")
            if self._current_progress:
                self._current_progress.add_error(
                    f"Failed to check donor module status: {str(e)}"
                )
                await self._notify_progress()
        except Exception as e:
            logger.exception(f"Error checking donor module: {e}")
            if self._current_progress:
                self._current_progress.add_error(
                    f"Failed to check donor module status: {str(e)}"
                )
                await self._notify_progress()

    async def _validate_pci_config(
        self, device: PCIDevice, config: BuildConfiguration
    ) -> None:
        """
        Validate PCI configuration values against donor card

        Args:
            device: The PCIe device to validate
            config: Current build configuration
        """
        try:
            # Skip validation for local builds without donor info file
            if config.local_build and not config.donor_info_file:
                if self._current_progress:
                    self._current_progress.add_warning(
                        "Skipping PCI configuration validation - no donor info file provided"
                    )
                return

            # TODO: Import build module - may need proper package structure
            import sys
            from pathlib import Path

            sys.path.append(str(Path(__file__).parent.parent.parent.parent))
            from build import validate_donor_info

            # For local builds with donor info file, load and validate the file
            if config.local_build and config.donor_info_file:
                import json

                try:
                    with open(config.donor_info_file, "r") as f:
                        donor_info = json.load(f)

                    # Validate the donor info
                    if self._current_progress:
                        self._current_progress.current_operation = (
                            "Validating donor info file"
                        )
                        await self._notify_progress()

                    # Perform validation
                    validate_donor_info(donor_info)

                    # Compare with device info
                    validation_results = []

                    # Check vendor ID
                    if device.vendor_id and "vendor_id" in donor_info:
                        device_vendor = device.vendor_id.lower().replace("0x", "")
                        donor_vendor = donor_info["vendor_id"].lower().replace("0x", "")
                        if device_vendor != donor_vendor:
                            validation_results.append(
                                ValidationResult(
                                    field="vendor_id",
                                    expected=donor_vendor,
                                    actual=device_vendor,
                                    status="mismatch",
                                )
                            )

                    # Check device ID
                    if device.device_id and "device_id" in donor_info:
                        device_id = device.device_id.lower().replace("0x", "")
                        donor_id = donor_info["device_id"].lower().replace("0x", "")
                        if device_id != donor_id:
                            validation_results.append(
                                ValidationResult(
                                    field="device_id",
                                    expected=donor_id,
                                    actual=device_id,
                                    status="mismatch",
                                )
                            )

                    # Check subsystem vendor ID
                    if device.subsystem_vendor and "subvendor_id" in donor_info:
                        device_subvendor = device.subsystem_vendor.lower().replace(
                            "0x", ""
                        )
                        donor_subvendor = (
                            donor_info["subvendor_id"].lower().replace("0x", "")
                        )
                        if device_subvendor != donor_subvendor:
                            validation_results.append(
                                ValidationResult(
                                    field="subvendor_id",
                                    expected=donor_subvendor,
                                    actual=device_subvendor,
                                    status="mismatch",
                                )
                            )

                    # Check subsystem device ID
                    if device.subsystem_device and "subsystem_id" in donor_info:
                        device_subsystem = device.subsystem_device.lower().replace(
                            "0x", ""
                        )
                        donor_subsystem = (
                            donor_info["subsystem_id"].lower().replace("0x", "")
                        )
                        if device_subsystem != donor_subsystem:
                            validation_results.append(
                                ValidationResult(
                                    field="subsystem_id",
                                    expected=donor_subsystem,
                                    actual=device_subsystem,
                                    status="mismatch",
                                )
                            )

                    # Add validation results to progress
                    if validation_results:
                        if self._current_progress:
                            self._current_progress.add_warning(
                                f"Found {len(validation_results)} PCI configuration mismatches"
                            )
                            for result in validation_results:
                                self._current_progress.add_warning(
                                    f"PCI mismatch: {result.field} - expected {result.expected}, got {result.actual}"
                                )
                    else:
                        if self._current_progress:
                            self._current_progress.current_operation = (
                                "PCI configuration values match donor card"
                            )
                            await self._notify_progress()

                except FileNotFoundError:
                    if self._current_progress:
                        self._current_progress.add_error(
                            f"Donor info file not found: {config.donor_info_file}"
                        )
                except json.JSONDecodeError:
                    if self._current_progress:
                        self._current_progress.add_error(
                            f"Invalid JSON in donor info file: {config.donor_info_file}"
                        )
                except Exception as e:
                    logger.exception(f"Error validating PCI configuration: {e}")
                    if self._current_progress:
                        self._current_progress.add_error(
                            f"Error validating PCI configuration: {str(e)}"
                        )

            # For non-local builds with donor_dump, validation happens during
            # donor_dump extraction
            elif not config.local_build and config.donor_dump:
                if self._current_progress:
                    self._current_progress.current_operation = (
                        "PCI validation will be performed during donor extraction"
                    )
                    await self._notify_progress()

        except ImportError as e:
            logger.exception(f"Failed to import build module: {e}")
            if self._current_progress:
                self._current_progress.add_error(
                    f"Failed to validate PCI configuration: {str(e)}"
                )
                await self._notify_progress()
        except Exception as e:
            logger.exception(f"Error validating PCI configuration: {e}")
            if self._current_progress:
                self._current_progress.add_error(
                    f"Failed to validate PCI configuration: {str(e)}"
                )
                await self._notify_progress()

    def _get_app(self):
        """Get the parent app instance"""
        # Find the app instance in the widget tree
        widget = getattr(self, "_progress_callback", None)
        while widget and not isinstance(widget, object):
            widget = getattr(widget, "app", None)

        return widget

    async def _analyze_device(self, device: PCIDevice) -> None:
        """Analyze device configuration"""
        # TODO: Import existing functions - may need proper package structure
        import sys

        sys.path.append(str(Path(__file__).parent.parent.parent.parent))
        from generate import get_current_driver, get_iommu_group

        # Get current device state
        current_driver = await asyncio.get_event_loop().run_in_executor(
            None, get_current_driver, device.bdf
        )

        iommu_group = await asyncio.get_event_loop().run_in_executor(
            None, get_iommu_group, device.bdf
        )

        # Validate VFIO device path
        vfio_device = f"/dev/vfio/{iommu_group}"
        if not os.path.exists(vfio_device) and self._current_progress:
            self._current_progress.add_warning(f"VFIO device {vfio_device} not found")

    async def _extract_registers(self, device: PCIDevice) -> None:
        """Extract device registers"""
        # This would integrate with existing register extraction logic
        await asyncio.sleep(1)  # Simulate register extraction

    async def _run_behavior_profiling(
        self, device: PCIDevice, config: BuildConfiguration
    ) -> None:
        """Run behavior profiling on the device"""
        # TODO: Import behavior profiler - may need proper package structure
        import sys
        from pathlib import Path

        sys.path.append(str(Path(__file__).parent.parent.parent))
        from device_clone.behavior_profiler import BehaviorProfiler

        # Log the start of profiling
        if self._current_progress:
            self._current_progress.current_operation = f"Profiling device {device.bdf}"
            await self._notify_progress()

        # Run the profiling in a separate thread to avoid blocking the event loop
        def run_profiling():
            try:
                # Use enable_ftrace=True for real hardware, but it requires root privileges
                enable_ftrace = not config.disable_ftrace and os.geteuid() == 0
                profiler = BehaviorProfiler(
                    bdf=device.bdf, debug=True, enable_ftrace=enable_ftrace
                )
                profile = profiler.capture_behavior_profile(
                    duration=config.profile_duration
                )
                return profile
            except Exception as e:
                if self._current_progress:
                    self._current_progress.add_error(
                        f"Behavior profiling failed: {str(e)}"
                    )
                return None

        # Execute profiling in a thread pool
        loop = asyncio.get_event_loop()
        profile = await loop.run_in_executor(None, run_profiling)

        # Update progress with results
        if profile and self._current_progress:
            self._current_progress.current_operation = (
                f"Analyzed {profile.total_accesses} register accesses"
            )
            self._current_progress.add_warning(
                f"Found {len(profile.timing_patterns)} timing patterns"
            )
            self._current_progress.add_warning(
                f"Identified {len(profile.state_transitions)} state transitions"
            )

    async def _generate_systemverilog(
        self, device: PCIDevice, config: BuildConfiguration
    ) -> None:
        """Generate SystemVerilog code"""
        # This would integrate with existing SystemVerilog generation
        await asyncio.sleep(2)  # Simulate SystemVerilog generation

    async def _run_vivado_synthesis(
        self, device: PCIDevice, config: BuildConfiguration
    ) -> None:
        """Run Vivado synthesis in container or locally"""
        # Convert config to CLI args
        cli_args = config.to_cli_args()

        # Build command parts
        build_cmd_parts = [
            f"python3 src/build.py --bdf {device.bdf} --board {config.board_type}"
        ]

        if cli_args.get("advanced_sv"):
            build_cmd_parts.append("--advanced-sv")
        if cli_args.get("device_type") != "generic":
            build_cmd_parts.append(f"--device-type {cli_args['device_type']}")
        if cli_args.get("enable_variance"):
            build_cmd_parts.append("--enable-variance")
        if cli_args.get("enable_behavior_profiling"):
            build_cmd_parts.append("--enable-behavior-profiling")
            build_cmd_parts.append(
                f"--profile-duration {cli_args['behavior_profile_duration']}"
            )

        # Add donor dump options
        if cli_args.get("use_donor_dump"):
            build_cmd_parts.append("--use-donor-dump")
        # Only add donor_info_file when explicitly provided and not empty
        donor_info_file = cli_args.get("donor_info_file")
        if (
            donor_info_file
            and isinstance(donor_info_file, str)
            and donor_info_file.strip()
        ):
            build_cmd_parts.append(f"--donor-info-file {donor_info_file}")
        if cli_args.get("skip_board_check"):
            build_cmd_parts.append("--skip-board-check")

        # Add Vivado execution flag to actually run Vivado
        build_cmd_parts.append("--run-vivado")

        build_cmd = " ".join(build_cmd_parts)

        if config.local_build:
            # Run locally
            if self._current_progress:
                self._current_progress.current_operation = "Running local build"
                await self._notify_progress()

            # Run the build command directly
            await self._run_shell(build_cmd.split())
        else:
            # Run in container
            # Get IOMMU group for VFIO device
            import sys

            sys.path.append(str(Path(__file__).parent.parent.parent.parent))
            from generate import get_iommu_group

            iommu_group = await asyncio.get_event_loop().run_in_executor(
                None, get_iommu_group, device.bdf
            )
            vfio_device = f"/dev/vfio/{iommu_group}"

            # Construct container command
            container_cmd = [
                "podman",
                "run",
                "--rm",
                "-it",
                "--privileged",
                f"--device={vfio_device}",
                "--device=/dev/vfio/vfio",
                "-v",
                f"{os.getcwd()}/output:/app/output",
                "pcileech-fw-generator:latest",
                f"python3 /app/src/build.py --bdf {device.bdf} --board {config.board_type}",
            ]

            # Add the same options to the container command
            for option in build_cmd_parts[
                1:
            ]:  # Skip the first part (python3 src/build.py)
                container_cmd.append(option)

            # Run container with progress monitoring
            await self._run_shell(container_cmd)

    async def _generate_bitstream(self, config: BuildConfiguration) -> None:
        """Generate final bitstream"""
        # This would be part of the Vivado synthesis step
        await asyncio.sleep(1)  # Simulate bitstream generation

    async def _run_shell(self, cmd, monitor=True) -> subprocess.CompletedProcess:
        """Combined shell command runner with optional monitoring"""
        if isinstance(cmd, list):
            cmd_str = " ".join(cmd)
        else:
            cmd_str = cmd

        if not monitor:
            # Simple command execution without monitoring
            process = await asyncio.create_subprocess_shell(
                cmd_str,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE,
            )
            stdout, stderr = await process.communicate()

            return subprocess.CompletedProcess(
                args=cmd_str,
                returncode=process.returncode if process.returncode is not None else 0,
                stdout=stdout.decode("utf-8"),
                stderr=stderr.decode("utf-8"),
            )

        # Monitored command execution
        self._build_process = await asyncio.create_subprocess_shell(
            cmd_str,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
        )

        # Monitor process output for progress updates
        while True:
            if self._build_process.stdout:
                line = await self._build_process.stdout.readline()
                if not line:
                    break

                line_str = line.decode("utf-8").strip()
                if line_str:
                    # Update progress based on output using LOG_PROGRESS_TOKENS
                    for pattern, (stage, percent, msg) in LOG_PROGRESS_TOKENS.items():
                        if pattern in line_str:
                            await self._update_progress(stage, percent, msg)
                            break

            # Check if process has completed
            if self._build_process.returncode is not None:
                break

            await asyncio.sleep(0.1)

        # Wait for process to complete
        await self._build_process.wait()

        if self._build_process.returncode != 0:
            error_msg = ""
            if self._build_process.stderr:
                stderr = await self._build_process.stderr.read()
                error_msg = stderr.decode("utf-8")
            if self._current_progress:
                self._current_progress.add_error(f"Build command failed: {error_msg}")
            raise RuntimeError(
                f"Build command failed with code {self._build_process.returncode}"
            )

        # Return a CompletedProcess-like object for compatibility
        return subprocess.CompletedProcess(
            args=cmd_str,
            returncode=self._build_process.returncode,
            stdout="",  # stdout was consumed during monitoring
            stderr="",  # stderr will be read if there's an error
        )
