#!/usr/bin/env python3

"""
BDD tests for frontend_tkinter_project_creator module.

This file is part of ArduPilot Methodic Configurator. https://github.com/ArduPilot/MethodicConfigurator

SPDX-FileCopyrightText: 2024-2026 Amilcar do Carmo Lucas <amilcar.lucas@iav.de>

SPDX-License-Identifier: GPL-3.0-or-later
"""

from collections.abc import Generator
from unittest.mock import MagicMock, patch

import pytest

from ardupilot_methodic_configurator.data_model_vehicle_project_creator import (
    VehicleProjectCreationError,
)
from ardupilot_methodic_configurator.frontend_tkinter_base_window import BaseWindow
from ardupilot_methodic_configurator.frontend_tkinter_project_creator import VehicleProjectCreatorWindow

# pylint: disable=redefined-outer-name, unused-argument, duplicate-code

# ==================== FIXTURES ====================


@pytest.fixture
def mock_project_manager() -> MagicMock:
    """Fixture providing a mock VehicleProjectManager with realistic test data."""
    manager = MagicMock()

    # Set up typical method return values
    manager.get_recently_used_dirs.return_value = ("/path/to/templates", "/path/to/projects", "/path/to/last/vehicle")
    manager.get_default_vehicle_name.return_value = "MyVehicle"
    manager.get_file_parameters_list.return_value = ["01_file.param", "02_file.param"]

    return manager


@pytest.fixture
def configured_creator_window(mock_project_manager) -> VehicleProjectCreatorWindow:
    """Fixture providing a properly configured VehicleProjectCreatorWindow for behavior testing."""
    with (
        patch("tkinter.Toplevel"),
        patch.object(BaseWindow, "_setup_application_icon"),
        patch.object(BaseWindow, "_setup_theme_and_styling"),
        patch.object(BaseWindow, "_get_dpi_scaling_factor", return_value=1.0),
        patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.ttk.Label"),
        patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.ttk.LabelFrame"),
        patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.ttk.Button"),
        patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.ttk.Checkbutton"),
        patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.show_tooltip"),
        patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.DirectorySelectionWidgets"),
        patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.PathEntryWidget"),
        patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.NewVehicleProjectSettings") as mock_settings,
        patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.tk.BooleanVar") as mock_bool_var,
        patch("tkinter.Tk") as mock_tk,
    ):
        # Setup mock settings
        mock_settings.get_all_settings_metadata.return_value = {
            "copy_vehicle_image": MagicMock(label="Copy vehicle image", enabled=True, tooltip="Copy vehicle image"),
        }
        mock_settings.get_default_values.return_value = {"copy_vehicle_image": False}

        # Setup mock BooleanVar
        mock_bool_var.return_value = MagicMock()

        mock_root = MagicMock()
        mock_tk.return_value = mock_root

        window = VehicleProjectCreatorWindow(mock_project_manager)

        # Ensure the window has required attributes set by BaseWindow
        if not hasattr(window, "root"):
            window.root = mock_root
        if not hasattr(window, "main_frame"):
            window.main_frame = MagicMock()

        return window


@pytest.fixture
def mock_messagebox() -> Generator[MagicMock, None, None]:
    """Fixture providing a mock messagebox for testing error dialogs."""
    with patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.messagebox") as mock:
        yield mock


@pytest.fixture
def mock_template_overview_window() -> Generator[MagicMock, None, None]:
    """Fixture providing a mock TemplateOverviewWindow for testing template selection."""
    with patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.TemplateOverviewWindow") as mock:
        yield mock


@pytest.fixture
def mock_sys_exit() -> Generator[MagicMock, None, None]:
    """Fixture providing a mock sys_exit to prevent actual process termination."""
    with patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.sys_exit") as mock:
        yield mock


# ==================== TEST CLASSES ====================


class TestVehicleProjectCreatorWindow:
    """Test user workflows for vehicle project creator window."""

    def test_user_can_initialize_window_with_project_settings(self, configured_creator_window) -> None:
        """
        User can see a properly initialized window with dynamic project settings.

        GIVEN: A user wants to create a new vehicle configuration project
        WHEN: The VehicleProjectCreatorWindow is created
        THEN: The window should display project settings dynamically loaded from metadata
        AND: All UI components should be properly configured and visible
        """
        # Arrange: Window is already configured through fixture
        window = configured_creator_window

        # Assert: Window properties are set correctly
        window.root.title.assert_called_once()
        window.root.geometry.assert_called_once()
        window.root.protocol.assert_called_once_with("WM_DELETE_WINDOW", window.close_and_quit)

        # Assert: Project manager methods were called for initialization
        window.project_manager.get_recently_used_dirs.assert_called_once()
        window.project_manager.get_default_vehicle_name.assert_called_once()

        # Assert: Settings variables are initialized
        assert hasattr(window, "new_project_settings_vars")
        assert isinstance(window.new_project_settings_vars, dict)

    def test_user_can_create_new_vehicle_from_template_successfully(self, configured_creator_window) -> None:
        """
        User can successfully create a new vehicle configuration from a template.

        GIVEN: A user has configured all required project settings
        WHEN: They click "Create vehicle configuration directory from template"
        THEN: The project manager should create the new vehicle project
        AND: The window should close after successful creation
        """
        # Arrange: Configure successful project creation
        window = configured_creator_window
        window.template_dir = MagicMock()
        window.template_dir.get_selected_directory.return_value = "/path/to/template"
        window.new_base_dir = MagicMock()
        window.new_base_dir.get_selected_directory.return_value = "/path/to/base"
        window.new_dir = MagicMock()
        window.new_dir.get_selected_directory.return_value = "MyNewVehicle"

        # Act: User creates new vehicle from template
        window.create_new_vehicle_from_template()

        # Assert: Project manager creates the project and window closes
        window.project_manager.create_new_vehicle_from_template.assert_called_once()
        window.root.destroy.assert_called_once()

    def test_user_sees_error_when_project_creation_fails(self, configured_creator_window, mock_messagebox) -> None:
        """
        User receives clear error feedback when project creation fails.

        GIVEN: A user attempts to create a new vehicle project
        WHEN: The project creation fails due to VehicleProjectCreationError
        THEN: An error dialog should be displayed with appropriate message
        AND: The window should remain open for user to correct the issue
        """
        # Arrange: Configure project creation to fail
        window = configured_creator_window
        window.template_dir = MagicMock()
        window.template_dir.get_selected_directory.return_value = "/invalid/template"
        window.new_base_dir = MagicMock()
        window.new_base_dir.get_selected_directory.return_value = "/invalid/base"
        window.new_dir = MagicMock()
        window.new_dir.get_selected_directory.return_value = "InvalidName"

        error = VehicleProjectCreationError("Creation Failed", "The project creation failed due to invalid parameters.")
        window.project_manager.create_new_vehicle_from_template.side_effect = error

        # Act: User attempts to create project that fails
        window.create_new_vehicle_from_template()

        # Assert: Error dialog is shown and window stays open
        mock_messagebox.showerror.assert_called_once_with(error.title, error.message)
        window.root.destroy.assert_not_called()

    def test_user_can_close_window_and_quit_application(self, configured_creator_window, mock_sys_exit) -> None:
        """
        User can close the window and quit the application cleanly.

        GIVEN: A user has the project creator window open
        WHEN: They close the window using the close button or Alt+F4
        THEN: The application should terminate gracefully
        """
        # Arrange: Window is configured and ready
        window = configured_creator_window

        # Act: User closes the window
        window.close_and_quit()

        # Assert: Application exits cleanly
        mock_sys_exit.assert_called_once_with(0)

    def test_window_creates_dynamic_settings_checkboxes(self, mock_project_manager) -> None:
        """
        User can see and interact with dynamically created project settings checkboxes.

        GIVEN: A user opens the project creator window
        WHEN: The window is initialized with project settings metadata
        THEN: Checkboxes should be created dynamically based on available settings
        AND: Each checkbox should have proper labels and tooltips
        """
        # Arrange: Configure project manager and mock settings
        with (
            patch("tkinter.Toplevel"),
            patch.object(BaseWindow, "_setup_application_icon"),
            patch.object(BaseWindow, "_setup_theme_and_styling"),
            patch.object(BaseWindow, "_get_dpi_scaling_factor", return_value=1.0),
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.ttk.Label"),
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.ttk.LabelFrame"),
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.ttk.Button"),
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.ttk.Checkbutton") as mock_checkbox,
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.show_tooltip"),
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.DirectorySelectionWidgets"),
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.PathEntryWidget"),
            patch(
                "ardupilot_methodic_configurator.frontend_tkinter_project_creator.NewVehicleProjectSettings"
            ) as mock_settings,
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.tk.BooleanVar"),
            patch("tkinter.Tk") as mock_tk,
        ):
            # Setup mock settings with multiple options
            mock_settings.get_all_settings_metadata.return_value = {
                "copy_vehicle_image": MagicMock(label="Copy vehicle image", enabled=True, tooltip="Copy vehicle image"),
                "blank_component_data": MagicMock(label="Blank component data", enabled=True, tooltip="Use blank data"),
            }
            mock_settings.get_default_values.return_value = {"copy_vehicle_image": False, "blank_component_data": False}

            mock_root = MagicMock()
            mock_tk.return_value = mock_root

            # Act: Create window which should create checkboxes dynamically
            window = VehicleProjectCreatorWindow(mock_project_manager)
            window.root = mock_root
            window.main_frame = MagicMock()

            # Assert: Checkboxes were created for each setting
            assert mock_checkbox.call_count >= 2  # At least two checkboxes created
            assert hasattr(window, "new_project_settings_widgets")
            assert isinstance(window.new_project_settings_widgets, dict)

    def test_window_adapts_size_based_on_settings_count(self, mock_project_manager) -> None:
        """
        User sees window size that adapts based on the number of available settings.

        GIVEN: Different numbers of project settings are available
        WHEN: The window is created
        THEN: The window height should adjust to accommodate all settings
        AND: The layout should be properly sized for usability
        """
        # Arrange: Configure different numbers of settings
        with (
            patch("tkinter.Toplevel"),
            patch.object(BaseWindow, "_setup_application_icon"),
            patch.object(BaseWindow, "_setup_theme_and_styling"),
            patch.object(BaseWindow, "_get_dpi_scaling_factor", return_value=1.0),
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.ttk.Label"),
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.ttk.LabelFrame"),
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.ttk.Button"),
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.ttk.Checkbutton"),
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.show_tooltip"),
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.DirectorySelectionWidgets"),
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.PathEntryWidget"),
            patch(
                "ardupilot_methodic_configurator.frontend_tkinter_project_creator.NewVehicleProjectSettings"
            ) as mock_settings,
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.tk.BooleanVar"),
            patch("tkinter.Tk") as mock_tk,
        ):
            # Setup mock settings with multiple options
            mock_settings.get_all_settings_metadata.return_value = {
                "copy_vehicle_image": MagicMock(label="Copy vehicle image", enabled=True, tooltip="Copy vehicle image"),
                "blank_component_data": MagicMock(label="Blank component data", enabled=True, tooltip="Use blank data"),
                "use_fc_params": MagicMock(label="Use FC params", enabled=True, tooltip="Use FC parameters"),
            }
            mock_settings.get_default_values.return_value = {
                "copy_vehicle_image": False,
                "blank_component_data": False,
                "use_fc_params": False,
            }

            mock_root = MagicMock()
            mock_tk.return_value = mock_root

            # Act: Create window
            window = VehicleProjectCreatorWindow(mock_project_manager)

            # Assert: Window geometry was set with appropriate size
            # Expected height = 250 + (3 settings * 23) = 319
            window.root.geometry.assert_called_once_with("800x319")

    def test_user_can_select_template_through_template_overview(
        self, configured_creator_window, mock_template_overview_window
    ) -> None:
        """
        User can select a template through the TemplateOverviewWindow interface.

        GIVEN: A user wants to select a vehicle template
        WHEN: They click on template selection and the TemplateOverviewWindow opens
        THEN: The template selection should work properly
        AND: The selected template path should be returned correctly
        """
        # Arrange: Configure template overview window
        window = configured_creator_window
        mock_overview = MagicMock()
        mock_template_overview_window.return_value = mock_overview

        # Mock the template selection callback
        window.template_dir = MagicMock()
        template_callback = window.template_dir.on_directory_selected_callback

        # Act: Simulate template selection callback
        if template_callback and callable(template_callback):
            result = template_callback(window.template_dir)

            # Assert: TemplateOverviewWindow was created and used
            assert result  # Should return a template directory path


class TestVehicleProjectCreatorWindowIntegration:
    """Test integration scenarios for vehicle project creator window."""

    def test_user_workflow_from_startup_to_project_creation(self, mock_project_manager, mock_template_overview_window) -> None:
        """
        User can complete full workflow from window startup to successful project creation.

        GIVEN: A user starts the project creation workflow
        WHEN: They configure all settings and create the project
        THEN: All components should work together seamlessly
        AND: The user should successfully create a new vehicle project
        """
        # Arrange: Set up complete workflow scenario
        with (
            patch("tkinter.Toplevel"),
            patch.object(BaseWindow, "_setup_application_icon"),
            patch.object(BaseWindow, "_setup_theme_and_styling"),
            patch.object(BaseWindow, "_get_dpi_scaling_factor", return_value=1.0),
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.ttk.Label"),
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.ttk.LabelFrame"),
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.ttk.Button"),
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.ttk.Checkbutton"),
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.show_tooltip"),
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.DirectorySelectionWidgets"),
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.PathEntryWidget"),
            patch(
                "ardupilot_methodic_configurator.frontend_tkinter_project_creator.NewVehicleProjectSettings"
            ) as mock_settings,
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.tk.BooleanVar"),
            patch("tkinter.Tk") as mock_tk,
        ):
            # Setup mock settings
            mock_settings.get_all_settings_metadata.return_value = {
                "copy_vehicle_image": MagicMock(label="Copy vehicle image", enabled=True, tooltip="Copy vehicle image"),
            }
            mock_settings.get_default_values.return_value = {"copy_vehicle_image": False}

            mock_root = MagicMock()
            mock_tk.return_value = mock_root

            # Act: Complete workflow simulation
            window = VehicleProjectCreatorWindow(mock_project_manager)
            window.root = mock_root
            window.main_frame = MagicMock()

            # Configure window for project creation
            window.template_dir = MagicMock()
            window.template_dir.get_selected_directory.return_value = "/templates/copter"
            window.new_base_dir = MagicMock()
            window.new_base_dir.get_selected_directory.return_value = "/projects"
            window.new_dir = MagicMock()
            window.new_dir.get_selected_directory.return_value = "TestVehicle"

            # User creates project
            window.create_new_vehicle_from_template()

            # Assert: Workflow completed successfully
            mock_project_manager.create_new_vehicle_from_template.assert_called_once()
            window.root.destroy.assert_called_once()

    def test_user_workflow_with_flight_controller_connected(self, mock_project_manager) -> None:
        """
        User can create project when flight controller is connected with specific vehicle type.

        GIVEN: A user has a flight controller connected with a specific vehicle type
        WHEN: They create the project creator window
        THEN: The window should be configured for the connected vehicle type
        AND: Vehicle type-specific features should be available
        """
        # Arrange: Set up workflow with connected flight controller
        with (
            patch("tkinter.Toplevel"),
            patch.object(BaseWindow, "_setup_application_icon"),
            patch.object(BaseWindow, "_setup_theme_and_styling"),
            patch.object(BaseWindow, "_get_dpi_scaling_factor", return_value=1.0),
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.ttk.Label"),
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.ttk.LabelFrame"),
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.ttk.Button"),
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.ttk.Checkbutton"),
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.show_tooltip"),
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.DirectorySelectionWidgets"),
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.PathEntryWidget"),
            patch(
                "ardupilot_methodic_configurator.frontend_tkinter_project_creator.NewVehicleProjectSettings"
            ) as mock_settings,
            patch("ardupilot_methodic_configurator.frontend_tkinter_project_creator.tk.BooleanVar"),
            patch("tkinter.Tk") as mock_tk,
        ):
            # Setup mock settings for connected flight controller
            mock_settings.get_all_settings_metadata.return_value = {
                "use_fc_params": MagicMock(label="Use FC params", enabled=True, tooltip="Use flight controller params"),
            }
            mock_settings.get_default_values.return_value = {"use_fc_params": False}

            mock_root = MagicMock()
            mock_tk.return_value = mock_root

            # Act: Create window with flight controller connected
            _window = VehicleProjectCreatorWindow(mock_project_manager)

            # Assert: Window was created with flight controller configuration
            # The NewVehicleProjectSettings.get_all_settings_metadata should be called with fc_connected=True
            assert mock_settings.get_all_settings_metadata.called
