import pytest
import tempfile
import os
from pathlib import Path
from unittest.mock import patch, MagicMock
import yaml
from elevator_traffic_analysis.__main__ import (
    get_available_scenarios,
    get_default_output_path,
    get_default_plot_path,
    get_default_plot_path_from_results,
    run_simulation,
    generate_plot,
    main
)


class TestGetAvailableScenarios:
    """Tests for get_available_scenarios function"""

    def test_get_available_scenarios_returns_list(self):
        """
        Given: The scenarios directory exists
        When: get_available_scenarios is called
        Then: A list is returned
        """
        scenarios = get_available_scenarios()
        assert isinstance(scenarios, list)

    def test_get_available_scenarios_sorted(self):
        """
        Given: Multiple scenario files exist in the scenarios directory
        When: get_available_scenarios is called
        Then: The scenarios are returned in sorted order
        """
        scenarios = get_available_scenarios()
        if scenarios:
            assert scenarios == sorted(scenarios)

    def test_get_available_scenarios_contains_yaml_files(self):
        """
        Given: Scenario YAML files exist in the scenarios directory
        When: get_available_scenarios is called
        Then: At least one scenario is found
        """
        scenarios = get_available_scenarios()
        # Should have some scenarios in the project
        assert len(scenarios) > 0


class TestGetDefaultOutputPath:
    """Tests for get_default_output_path function"""

    def test_get_default_output_path_simple(self):
        """
        Given: A simple scenario filename
        When: get_default_output_path is called
        Then: The output path has the correct format with _out suffix in scenarios_out directory
        """
        scenario_path = "test_scenario.yaml"
        output_path = get_default_output_path(scenario_path)

        assert output_path.endswith("test_scenario_out.yaml")
        assert "scenarios_out" in output_path

    def test_get_default_output_path_with_directory(self):
        """
        Given: A scenario path with directory components
        When: get_default_output_path is called
        Then: The output path is generated correctly in scenarios_out directory
        """
        scenario_path = "/path/to/scenarios/my_scenario.yaml"
        output_path = get_default_output_path(scenario_path)

        assert output_path.endswith("my_scenario_out.yaml")
        assert "scenarios_out" in output_path

    def test_get_default_output_path_creates_directory(self):
        """
        Given: The scenarios_out directory may not exist
        When: get_default_output_path is called
        Then: The output directory is created automatically
        """
        scenario_path = "test_scenario.yaml"
        output_path = get_default_output_path(scenario_path)

        output_dir = Path(output_path).parent
        assert output_dir.exists()


class TestGetDefaultPlotPath:
    """Tests for get_default_plot_path function"""

    def test_get_default_plot_path_simple(self):
        """
        Given: A simple scenario filename
        When: get_default_plot_path is called
        Then: The plot path has the correct format with _plot suffix in scenarios_plots directory
        """
        scenario_path = "test_scenario.yaml"
        plot_path = get_default_plot_path(scenario_path)

        assert plot_path.endswith("test_scenario_plot.png")
        assert "scenarios_plots" in plot_path

    def test_get_default_plot_path_with_directory(self):
        """
        Given: A scenario path with directory components
        When: get_default_plot_path is called
        Then: The plot path is generated correctly in scenarios_plots directory
        """
        scenario_path = "/path/to/scenarios/my_scenario.yaml"
        plot_path = get_default_plot_path(scenario_path)

        assert plot_path.endswith("my_scenario_plot.png")
        assert "scenarios_plots" in plot_path

    def test_get_default_plot_path_creates_directory(self):
        """
        Given: The scenarios_plots directory may not exist
        When: get_default_plot_path is called
        Then: The plot directory is created automatically
        """
        scenario_path = "test_scenario.yaml"
        plot_path = get_default_plot_path(scenario_path)

        plot_dir = Path(plot_path).parent
        assert plot_dir.exists()


class TestGetDefaultPlotPathFromResults:
    """Tests for get_default_plot_path_from_results function"""

    def test_get_default_plot_path_from_results_simple(self):
        """
        Given: A simple results filename
        When: get_default_plot_path_from_results is called
        Then: The plot path is generated correctly with _plot suffix
        """
        results_path = "test_results.yaml"
        plot_path = get_default_plot_path_from_results(results_path)

        assert plot_path.endswith("test_results_plot.png")
        assert "scenarios_plots" in plot_path

    def test_get_default_plot_path_from_results_removes_out_suffix(self):
        """
        Given: A results filename with _out suffix
        When: get_default_plot_path_from_results is called
        Then: The _out suffix is removed from the plot filename
        """
        results_path = "test_scenario_out.yaml"
        plot_path = get_default_plot_path_from_results(results_path)

        assert plot_path.endswith("test_scenario_plot.png")
        assert "_out_plot" not in plot_path


class TestRunSimulation:
    """Tests for run_simulation function"""

    def test_run_simulation_file_not_found(self):
        """
        Given: A non-existent scenario file path
        When: run_simulation is called
        Then: The function returns False
        """
        result = run_simulation("nonexistent.yaml", "output.yaml")
        assert result is False

    def test_run_simulation_with_valid_scenario(self):
        """
        Given: A valid scenario configuration file
        When: run_simulation is called
        Then: The simulation runs successfully and output file is created
        """
        config = {
            'n_floors': 5,
            'n_elevators': 1,
            'elevator_capacity': 10,
            'elevator_logic': 'first_come_first_served',
            'passenger_schedule': {
                0: [
                    {'passenger_id': 'P__0', 'appear_floor': 'F__0', 'destination_floor': 'F__3'}
                ]
            }
        }

        with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
            yaml.dump(config, f)
            scenario_file = f.name

        with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
            output_file = f.name

        try:
            result = run_simulation(scenario_file, output_file, verbose=False)
            assert result is True
            assert os.path.exists(output_file)
        finally:
            os.unlink(scenario_file)
            if os.path.exists(output_file):
                os.unlink(output_file)

    def test_run_simulation_with_invalid_yaml(self):
        """
        Given: A scenario file with invalid YAML syntax
        When: run_simulation is called
        Then: The function returns False without crashing
        """
        with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
            f.write("invalid yaml content: [unclosed bracket")
            scenario_file = f.name

        with tempfile.NamedTemporaryFile(suffix='.yaml', delete=False) as f:
            output_file = f.name

        try:
            result = run_simulation(scenario_file, output_file)
            assert result is False
        finally:
            os.unlink(scenario_file)
            if os.path.exists(output_file):
                os.unlink(output_file)

    def test_run_simulation_verbose_mode(self):
        """
        Given: A valid scenario configuration and verbose flag enabled
        When: run_simulation is called with verbose=True
        Then: The simulation runs successfully with verbose output
        """
        config = {
            'n_floors': 3,
            'n_elevators': 1,
            'elevator_logic': 'first_come_first_served',
            'passenger_schedule': {
                0: [
                    {'passenger_id': 'P__0', 'appear_floor': 'F__0', 'destination_floor': 'F__2'}
                ]
            }
        }

        with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
            yaml.dump(config, f)
            scenario_file = f.name

        with tempfile.NamedTemporaryFile(suffix='.yaml', delete=False) as f:
            output_file = f.name

        try:
            result = run_simulation(scenario_file, output_file, verbose=True)
            assert result is True
        finally:
            os.unlink(scenario_file)
            if os.path.exists(output_file):
                os.unlink(output_file)


class TestGeneratePlot:
    """Tests for generate_plot function"""

    def test_generate_plot_file_not_found(self):
        """
        Given: A non-existent results file path
        When: generate_plot is called
        Then: The function returns False
        """
        result = generate_plot("nonexistent.yaml")
        assert result is False

    def test_generate_plot_with_valid_results(self):
        """
        Given: A valid simulation results file
        When: generate_plot is called
        Then: The plot is generated successfully and saved to the output file
        """
        results = {
            'scenario_config': {
                'n_floors': 5,
                'n_elevators': 1,
                'elevator_capacity': 10,
                'elevator_logic': 'first_come_first_served'
            },
            'analysis': {
                'total_ticks': 10,
                'total_passengers': 1,
                'ticks': {
                    0: {
                        'elevators': {
                            'E__0': {
                                'current_floor': 'F__0',
                                'target_floor': None,
                                'state': 'IDLE',
                                'onboard_passengers_count': 0,
                                'onboard_passengers': [],
                                'capacity': 10
                            }
                        },
                        'floors': {
                            'F__0': {
                                'waiting_passengers_count': 0,
                                'waiting_passengers': [],
                                'dismbarked_passengers_count': 0,
                                'dismbarked_passengers': []
                            }
                        }
                    }
                }
            }
        }

        with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
            yaml.dump(results, f)
            results_file = f.name

        with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as f:
            plot_file = f.name

        try:
            result = generate_plot(results_file, plot_file, show_plot=False, verbose=False)
            assert result is True
            assert os.path.exists(plot_file)
        finally:
            os.unlink(results_file)
            if os.path.exists(plot_file):
                os.unlink(plot_file)

    def test_generate_plot_verbose_mode(self):
        """
        Given: A valid results file and verbose flag enabled
        When: generate_plot is called with verbose=True
        Then: The plot is generated successfully with verbose output
        """
        results = {
            'scenario_config': {
                'n_floors': 5,
                'n_elevators': 2,
                'elevator_capacity': 10,
                'elevator_logic': 'shortest_seek_time_first'
            },
            'analysis': {
                'total_ticks': 5,
                'total_passengers': 2,
                'ticks': {
                    0: {
                        'elevators': {
                            'E__0': {
                                'current_floor': 'F__0',
                                'state': 'IDLE',
                                'onboard_passengers_count': 0,
                                'onboard_passengers': [],
                                'capacity': 10
                            }
                        },
                        'floors': {
                            'F__0': {
                                'waiting_passengers_count': 0,
                                'waiting_passengers': [],
                                'dismbarked_passengers_count': 0,
                                'dismbarked_passengers': []
                            }
                        }
                    }
                }
            }
        }

        with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
            yaml.dump(results, f)
            results_file = f.name

        try:
            result = generate_plot(results_file, show_plot=False, verbose=True)
            assert result is True
        finally:
            os.unlink(results_file)


class TestMain:
    """Tests for main function"""

    def test_main_no_arguments(self):
        """
        Given: No command-line arguments are provided
        When: main is called
        Then: The help message is printed and exit code 1 is returned
        """
        with patch('sys.argv', ['prog']):
            result = main()
            assert result == 1

    def test_main_list_scenarios(self):
        """
        Given: The list-scenarios command is provided
        When: main is called
        Then: Available scenarios are listed and exit code 0 is returned
        """
        with patch('sys.argv', ['prog', 'list-scenarios']):
            result = main()
            assert result == 0

    def test_main_run_nonexistent_scenario(self):
        """
        Given: A non-existent scenario name is provided
        When: main is called with the run command
        Then: An error message is displayed and exit code 1 is returned
        """
        with patch('sys.argv', ['prog', 'run', 'nonexistent_scenario_xyz']):
            result = main()
            assert result == 1

    def test_main_run_with_custom_scenario(self):
        """
        Given: A custom scenario file path is provided
        When: main is called with the run command
        Then: The simulation runs successfully and exit code 0 is returned
        """
        config = {
            'n_floors': 3,
            'n_elevators': 1,
            'elevator_logic': 'first_come_first_served',
            'passenger_schedule': {
                0: [
                    {'passenger_id': 'P__0', 'appear_floor': 'F__0', 'destination_floor': 'F__2'}
                ]
            }
        }

        with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
            yaml.dump(config, f)
            scenario_file = f.name

        with tempfile.NamedTemporaryFile(suffix='.yaml', delete=False) as f:
            output_file = f.name

        try:
            with patch('sys.argv', ['prog', 'run', scenario_file, '-o', output_file]):
                result = main()
                assert result == 0
                assert os.path.exists(output_file)
        finally:
            os.unlink(scenario_file)
            if os.path.exists(output_file):
                os.unlink(output_file)

    def test_main_run_with_plot(self):
        """
        Given: A scenario file and plot generation flag are provided
        When: main is called with the run command and --plot flag
        Then: Both simulation results and plot are generated successfully
        """
        config = {
            'n_floors': 3,
            'n_elevators': 1,
            'elevator_logic': 'first_come_first_served',
            'passenger_schedule': {
                0: [
                    {'passenger_id': 'P__0', 'appear_floor': 'F__0', 'destination_floor': 'F__2'}
                ]
            }
        }

        with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
            yaml.dump(config, f)
            scenario_file = f.name

        with tempfile.NamedTemporaryFile(suffix='.yaml', delete=False) as f:
            output_file = f.name

        with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as f:
            plot_file = f.name

        try:
            with patch('sys.argv', ['prog', 'run', scenario_file, '-o', output_file,
                                   '--plot', '--plot-output', plot_file]):
                result = main()
                assert result == 0
                assert os.path.exists(output_file)
                assert os.path.exists(plot_file)
        finally:
            os.unlink(scenario_file)
            if os.path.exists(output_file):
                os.unlink(output_file)
            if os.path.exists(plot_file):
                os.unlink(plot_file)

    def test_main_plot_command(self):
        """
        Given: A results file path is provided
        When: main is called with the plot command
        Then: The plot is generated successfully and exit code 0 is returned
        """
        results = {
            'scenario_config': {
                'n_floors': 5,
                'n_elevators': 1,
                'elevator_capacity': 10,
                'elevator_logic': 'first_come_first_served'
            },
            'analysis': {
                'total_ticks': 5,
                'total_passengers': 1,
                'ticks': {
                    0: {
                        'elevators': {
                            'E__0': {
                                'current_floor': 'F__0',
                                'state': 'IDLE',
                                'onboard_passengers_count': 0,
                                'onboard_passengers': [],
                                'capacity': 10
                            }
                        },
                        'floors': {
                            'F__0': {
                                'waiting_passengers_count': 0,
                                'waiting_passengers': [],
                                'dismbarked_passengers_count': 0,
                                'dismbarked_passengers': []
                            }
                        }
                    }
                }
            }
        }

        with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
            yaml.dump(results, f)
            results_file = f.name

        with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as f:
            plot_file = f.name

        try:
            with patch('sys.argv', ['prog', 'plot', '-i', results_file, '-o', plot_file]):
                result = main()
                assert result == 0
                assert os.path.exists(plot_file)
        finally:
            os.unlink(results_file)
            if os.path.exists(plot_file):
                os.unlink(plot_file)

    def test_main_plot_nonexistent_results(self):
        """
        Given: A non-existent results file path is provided
        When: main is called with the plot command
        Then: An error message is displayed and exit code 1 is returned
        """
        with patch('sys.argv', ['prog', 'plot', '-i', 'nonexistent.yaml']):
            result = main()
            assert result == 1

    def test_main_run_verbose(self):
        """
        Given: A scenario file and verbose flag are provided
        When: main is called with the run command and -v flag
        Then: The simulation runs successfully with verbose output
        """
        config = {
            'n_floors': 3,
            'n_elevators': 1,
            'elevator_logic': 'first_come_first_served',
            'passenger_schedule': {
                0: [
                    {'passenger_id': 'P__0', 'appear_floor': 'F__0', 'destination_floor': 'F__2'}
                ]
            }
        }

        with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
            yaml.dump(config, f)
            scenario_file = f.name

        with tempfile.NamedTemporaryFile(suffix='.yaml', delete=False) as f:
            output_file = f.name

        try:
            with patch('sys.argv', ['prog', 'run', scenario_file, '-o', output_file, '-v']):
                result = main()
                assert result == 0
        finally:
            os.unlink(scenario_file)
            if os.path.exists(output_file):
                os.unlink(output_file)
