from mvp.view import *
from mvp.model import *
from mvp.presenter import *

from PyQt6.QtWidgets import QComboBox, QDateEdit, QLineEdit, QFileDialog, QApplication
from PyQt6 import QtCore
from PyQt6.QtCore import QEvent, QCoreApplication, QObject, QThread, pyqtSignal
from functions import (
    highlight_invalid_field,
    unhighlight_valid_field,
    validate_inputs,
)
from logger import logger
import threading


class MainPresenter:
    def __init__(self, view, optimization_model, fly_model, settings_model):
        self.view = view
        self.optimization_model = optimization_model
        self.fly_model = fly_model
        self.settings_model = settings_model

        self.initialize_tabs()
        self.view.show_maximized()

        # Initialize connections (signals & slots)
        self.view.showSettingsRequested.connect(self.show_settings)
        self.view.showHelpRequested.connect(self.show_help)
        self.view.showSaveRequested.connect(self.save_dialog)
        self.view.showLoadRequested.connect(self.load_dialog)
        self.optimize_tab_view.calculationCompleted.connect(self.share_balloons_data)

    def show_settings(self):
        self.settings_view = SettingsWindow()
        self.settings_presenter = SettingsPresenter(
            self.settings_view, self.settings_model
        )
        self.settings_view.show()

    def show_help(self):
        # Load HTML content from the external file
        html_file_path = "data/tutorial.html"
        with open(html_file_path, "r") as file:
            html_content = file.read()

        # Show the help window
        self.help_view = HelpWindow(html_content)
        self.help_view.show()

    def save_dialog(self):
        filename, _ = QFileDialog.getSaveFileName(
            self.view, "Save Configuration", "", "JSON Files (*.json)"
        )
        if filename:
            try:
                self.optimization_model.save_parameters(filename)
                QMessageBox.information(
                    self.view,
                    "Save Successful",
                    "The configuration has been saved successfully.",
                )
                logger.info("Configuration saved successfully.")
            except Exception as e:
                QMessageBox.critical(self.view, "Save Failed", str(e))
                logger.error("Configuration save failed: %s", str(e))

    def load_dialog(self):
        filename, _ = QFileDialog.getOpenFileName(
            self.view, "Load Configuration", "", "JSON Files (*.json)"
        )
        if filename:
            try:
                self.optimization_model.load_parameters(filename)
                self.optimize_tab_presenter.update_view_from_model()  # Update the view based on loaded data
                QMessageBox.information(
                    self.view,
                    "Load Successful",
                    "The configuration has been loaded successfully.",
                )
                logger.info("Configuration loaded successfully.")
            except Exception as e:
                QMessageBox.critical(self.view, "Load Failed", str(e))
                logger.error("Configuration load failed: %s", str(e))

    def share_balloons_data(self):
        print("Sharing Balloons Data")
        # Get the balloon data from the optimization model
        data = self.optimization_model.get_balloon_output_for_simulation()
        # Set the balloon data in the fly model
        self.fly_model.set_balloon_data(data)
        self.fly_tab_presenter.show_balloon()

    def initialize_tabs(self):
        # Initialize Optimize Tab
        self.optimize_tab_view = OptimizeTabView()
        self.optimize_tab_presenter = OptimizeTabPresenter(
            self.optimize_tab_view, self.optimization_model, self.settings_model
        )

        self.view.add_tab(self.optimize_tab_view, "Optimize")
        # Way to disable tabs.
        # self.optimize_tab_view.setEnabled(False)

        # Initialize Design Tab
        # Initialize Fly Tab
        self.fly_tab_view = FlyTabView()
        self.fly_tab_presenter = FlyTabPresenter(
            self.fly_tab_view,
            self.fly_model,
            self.settings_model,
        )
        self.view.add_tab(self.fly_tab_view, "Fly")


class OptimizeTabPresenter:
    def __init__(self, view, parameter_model, settings_model):
        self.view = view
        self.optimization_model = parameter_model
        self.settings_model = settings_model

        # Populate the input groups with fields
        self.add_groups()

        # Show empty output and disabled
        self.show_output()
        self.update_view_from_model()

        # Initialize connections (signals & slots)
        self.view.calculateOptimizeRequested.connect(self.start_optimization)
        self.view.calculationCompleted.connect(self.on_calculation_complete)
        self.view.calculationFailed.connect(self.on_calculation_incomplete)
        self.view.dropdownSelectionChanged.connect(self.update_plot)
        self.view.saveBalloonRequested.connect(self.save_dialog)
        # self.view.terminationRequested.connect(
        #     self.parameter_model.terminate_optimization
        # )

    def start_optimization(self):
        is_valid, validated_inputs = validate_inputs(
            self.view.input_widgets, self.optimization_model.optimize_validation_rules
        )
        if is_valid:
            self.store_validated_inputs(validated_inputs)
            self.view.input_container.setEnabled(False)
            self.show_progress_dialog()
            task_thread = threading.Thread(target=self.optimization_thread)
            task_thread.start()
        else:
            # Show a message box informing the user to correct the inputs
            QMessageBox.warning(
                self.view, "Invalid Input", "Please correct the highlighted fields."
            )
            logger.warning("Invalid input detected. Optimization not started.")

    def optimization_thread(self):
        logger.info("Starting optimization thread")
        # self.optimization_model.long_running_calculation()
        # self.view.calculationCompleted.emit()
        try:
            self.optimization_model.long_running_calculation()
            self.view.calculationCompleted.emit()
        except Exception as error:
            self.view.calculationFailed.emit(str(error))

    def show_progress_dialog(self):
        self.progress_dialog = ProgressDialog(self.view)
        self.progress_dialog.show()

    def on_calculation_incomplete(self, error_message):
        # Close the progress dialog and notify the user that the calculation is complete
        if hasattr(self, "progress_dialog"):
            self.progress_dialog.close()
            QMessageBox.critical(
                self.view,
                "Optimization Error",
                f"An error occurred: {error_message}",
            )
        logger.error("Optimization process failed: %s", error_message)
        # Enable UI components
        self.view.input_container.setEnabled(True)

    def on_calculation_complete(self):
        # Close the progress dialog and notify the user that the calculation is complete
        if hasattr(self, "progress_dialog"):
            self.progress_dialog.close()
            QMessageBox.information(
                self.view,
                "Optimization Complete",
                "The optimization process has completed successfully.",
            )
        logger.info("Optimization process completed successfully.")
        # Enable UI components
        self.view.input_container.setEnabled(True)
        self.view.output_container.setEnabled(True)
        # Refresh the output display
        self.show_output()
        # Set the dropdown to automatically select the first result plot
        self.view.dropdown_plot.setCurrentIndex(0)
        self.update_plot(self.view.dropdown_plot.itemText(0))

    def update_view_from_model(self):
        for key in self.optimization_model.optimize_input:
            value = self.optimization_model.get_parameter("optimize_input", key)
            if key in self.view.input_widgets:
                widget = self.view.input_widgets[key]
                if isinstance(widget, QLineEdit):
                    widget.setText("" if value is None else str(value))
                elif isinstance(widget, QComboBox):
                    index = widget.findText(
                        str(value), QtCore.Qt.MatchFlag.MatchFixedString
                    )
                    if index >= 0:
                        widget.setCurrentIndex(index)
                elif isinstance(widget, QDateEdit):
                    if value:
                        qdate = QDate(value.year, value.month, value.day)
                        widget.setDate(qdate)

    def store_validated_inputs(self, validated_inputs):
        # Loop through all validated inputs and update the parameter model
        for key, value in validated_inputs.items():
            # Identify the category for each key based on parameter model's definitions
            if key in self.optimization_model.optimize_input:
                self.optimization_model.set_parameter("optimize_input", key, value)
            elif key in self.optimization_model.design_input:
                self.optimization_model.set_parameter("design_input", key, value)
            elif key in self.optimization_model.fly_input:
                self.optimization_model.set_parameter("fly_input", key, value)

    def add_groups(self):
        # Iterate through each group and populate it using definitions from the model
        self.view.populate_input_group(
            self.view.mission_group, self.optimization_model.optimize_definition_mission
        )
        self.view.populate_input_group(
            self.view.balloon_group, self.optimization_model.optimize_definition_balloon
        )
        self.view.populate_input_group(
            self.view.payload_group, self.optimization_model.optimize_definition_payload
        )
        self.view.populate_input_group(
            self.view.solar_group,
            self.optimization_model.optimize_definition_solar_panel,
        )
        self.view.populate_input_group(
            self.view.battery_group, self.optimization_model.optimize_definition_battery
        )
        self.view.populate_input_group(
            self.view.other_group, self.optimization_model.optimize_definition_others
        )

    def show_output(self):
        self.view.populate_output_form(
            self.view.general_output_layout,
            self.optimization_model.optimize_output_general_params,
            self.optimization_model.optimize_output_general_params_names,
        )
        self.view.populate_output_form(
            self.view.balloon1_output_layout,
            self.optimization_model.optimize_output_balloon_1_params,
            self.optimization_model.output_balloon_param_names,
        )

        self.view.populate_output_form(
            self.view.balloon2_output_layout,
            self.optimization_model.optimize_output_balloon_2_params,
            self.optimization_model.output_balloon_param_names,
        )

        self.view.populate_output_form(
            self.view.solar_panel_output_layout,
            self.optimization_model.optimize_output_solar_panel_param,
            self.optimization_model.optimize_output_solar_panel_param_names,
        )

        self.view.populate_output_form(
            self.view.battery_output_layout,
            self.optimization_model.optimize_output_battery_param,
            self.optimization_model.optimize_output_battery_param_names,
        )
        self.view.populate_output_form(
            self.view.other_output_layout,
            self.optimization_model.optimize_output_other_param,
            self.optimization_model.optimize_output_other_param_names,
        )
        self.view.fill_message_output(self.optimization_model.optimize_output_message)

        if (
            self.optimization_model.optimize_input["balloon_system_type"]
            == "Balloon in Balloon - Air Ballast"
        ):
            image_path = "images/CA1.png"
        elif (
            self.optimization_model.optimize_input["balloon_system_type"]
            == "Balloon in Balloon - Compressed Helium"
        ):
            image_path = "images/CL2.png"
        elif (
            self.optimization_model.optimize_input["balloon_system_type"]
            == "Tandem Balloon - Air Ballast"
        ):
            image_path = "images/CA2.png"
        elif (
            self.optimization_model.optimize_input["balloon_system_type"]
            == "Tandem Balloon - Compressed Helium"
        ):
            image_path = "images/CL1.png"
        else:
            print("No image path found.")
            image_path = None

        if image_path:
            print("Setting image path")
            self.view.set_balloon_image(image_path)

    def update_plot(self, selection):
        if selection == "Balloon 1":
            data = self.optimization_model.optimize_output_balloon_1_shape
            self.view.canvas.plot_balloon(data["radius"], data["height"], selection)
        elif selection == "Balloon 2":
            data = self.optimization_model.optimize_output_balloon_2_shape
            self.view.canvas.plot_balloon(data["radius"], data["height"], selection)
        elif selection == "Balloon 1 Optimization Shapes":
            data = self.optimization_model.optimize_output_iter_balloon
            self.view.canvas.plot_balloon_iterations(
                data["balloon1_radius"],
                data["balloon1_height"],
                "Balloon 1 Optimization Shapes",
            )
        elif selection == "Balloon 2 Optimization Shapes":
            data = self.optimization_model.optimize_output_iter_balloon
            self.view.canvas.plot_balloon_iterations(
                data["balloon2_radius"],
                data["balloon2_height"],
                "Balloon 2 Optimization Shapes",
            )
        elif selection == "Balloon 1 Stress":
            data = self.optimization_model.optimize_output_balloon_1_stress
            self.view.canvas.plot_balloon_stress(
                skin_length=data["skin_length"],
                stress_meridional=data["sigma_meridional"],
                stress_film=data["sigma_film"],
                stress_tape=data["sigma_tape"],
                stress_total=data["sigma_total"],
                title="Balloon 1:",
            )
        elif selection == "Balloon 2 Stress":
            data = self.optimization_model.optimize_output_balloon_2_stress
            self.view.canvas.plot_balloon_stress(
                skin_length=data["skin_length"],
                stress_meridional=data["sigma_meridional"],
                stress_film=data["sigma_film"],
                stress_tape=data["sigma_tape"],
                stress_total=data["sigma_total"],
                title="Balloon 2:",
            )
        elif selection == "Optimization Balance":
            data = self.optimization_model.optimize_output_iter_balance
            self.view.canvas.plot_weight_balance(data["balance"])
        elif selection == "Solar Panel Power Curve":
            data = self.optimization_model.optimize_output_solar_panel_power
            self.view.canvas.plot_solar_power_curve(data["power_curve"])
        elif selection == "Volumes over Altitude":
            data = self.optimization_model.output_balloon_arrays
            self.view.canvas.plot_balloon_volumes(
                data["altitudes"], data["balloon_1_volume"], data["balloon_2_volume"]
            )
        elif selection == "Gas Weights over Altitude":
            data = self.optimization_model.output_balloon_arrays
            self.view.canvas.plot_balloon_gas_weights(
                data["altitudes"],
                data["balloon_1_gas_weight"],
                data["balloon_2_gas_weight"],
            )
        elif selection == "Energy Balance over 3 Days":
            data = self.optimization_model.optimize_output_energy_balance
            self.view.canvas.plot_energy_balance(
                data["charge_state"],
                data["battery_capacity"],
                data["min_discharge_level"],
                data["power_balance"],
                data["solar_power_array"],
                data["payload_power_array"],
                data["altitude_power_array"],
                data["heating_auxiliary_power_array"],
            )
        else:
            return  # Or handle error

    def save_dialog(self):
        file_path, _ = QFileDialog.getSaveFileName(
            self.view, "Save Balloon Configuration", "", "Pickle Files (*.pkl)"
        )
        if file_path:
            try:
                self.optimization_model.save_balloon_simulation(file_path)
                QMessageBox.information(
                    self.view,
                    "Save Successful",
                    "The balloon configuration has been saved successfully.",
                )
                logger.info("Balloon configuration saved successfully.")
            except Exception as e:
                QMessageBox.critical(self.view, "Save Failed", str(e))
                logger.error("Balloon configuration save failed: %s", str(e))


class FlyTabPresenter:

    def __init__(self, view, fly_model, settings_model):
        self.view = view
        self.fly_model = fly_model
        self.settings_model = settings_model
        # Populate the input group with fields
        self.view.populate_input_group(
            self.view.flight_group, self.fly_model.fly_input_definitions
        )
        self.view.populate_output_form(
            self.view.balloon_layout,
            self.fly_model.balloon_parameters,
            self.fly_model.balloon_parameters_definitions,
        )
        # Initialize connections (signals & slots)
        self.view.loadBalloonRequested.connect(self.load_dialog)
        self.view.loadControllerRequested.connect(
            self.warning_not_implemented
        )  # Load controller not yet implemented
        self.view.calculateFlyRequested.connect(self.start_fly)
        # self.view.dropdownSelectionChanged.connect()

    def show_balloon(self):
        # Update the view with the balloon data
        self.view.populate_output_form(
            self.view.balloon_layout,
            self.fly_model.balloon_parameters,
            self.fly_model.balloon_parameters_definitions,
        )

    def start_fly(self):
        balloon_check = self.fly_model.check_balloon_data()
        if not balloon_check:
            QMessageBox.warning(
                self.view,
                "Invalid Balloon Data",
                "Please ensure that the balloon data is available.",
            )
            return
        else:
            self.fly_model.update_validation_rules()
            logger.info("Balloon data is valid.")
            print("Balloon data is valid.")

            is_valid, validated_inputs = validate_inputs(
                self.view.input_widgets, self.fly_model.fly_validation_rules
            )
            if is_valid:
                logger.info("Fly form is valid. Proceeding with submission.")
                print("Fly form is valid. Proceeding with submission.")
                self.store_validated_inputs(validated_inputs)
                logger.info("Validated inputs stored.")
                self.show_progress_dialog()

                # Disable the input container
                self.view.input_container.setEnabled(False)

                # Start the calculation in a separate thread
                self.worker = FlyCalculationThread(self.fly_model, self.view)
                self.worker.progress.connect(self.progress_dialog.update_progress)
                self.worker.message.connect(self.progress_dialog.update_message)
                self.worker.finished.connect(self.on_calculation_complete)
                self.worker.error.connect(self.on_calculation_incomplete)
                self.progress_dialog.canceled.connect(self.worker.cancel)
                self.worker.start()

            else:
                # Show a message box informing the user to correct the inputs
                logger.warning("Invalid input detected. Fly calculation not started.")
                QMessageBox.warning(
                    self.view, "Invalid Input", "Please correct the highlighted fields."
                )

    def store_validated_inputs(self, validated_inputs):
        for key, value in validated_inputs.items():
            self.fly_model.set_parameter("fly_input", key, value)

    def show_progress_dialog(self):
        self.progress_dialog = ProgressFlyDialog(self.view)
        self.progress_dialog.show()

    def on_calculation_complete(self):
        self.progress_dialog.show_completion_message()

        # Re-enable the input container
        self.view.input_container.setEnabled(True)

    def on_calculation_incomplete(self, error_message):
        # Close the progress dialog and notify the user that the calculation is incomplete
        if hasattr(self, "progress_dialog"):
            self.progress_dialog.close()
            QMessageBox.critical(
                self.view,
                "Fly Error",
                f"An error occurred: {error_message}",
            )

        # Re-enable the input container
        self.view.input_container.setEnabled(True)

    def load_dialog(self):
        filename, _ = QFileDialog.getOpenFileName(
            self.view, "Load Balloon Configuration", "", "Pickle Files (*.pkl)"
        )
        if filename:
            try:
                balloon_data = self.fly_model.load_balloon_simulation(filename)
                self.fly_model.set_balloon_data(balloon_data)
                self.show_balloon()
                QMessageBox.information(
                    self.view,
                    "Load Successful",
                    "The balloon configuration has been loaded successfully.",
                )
            except Exception as e:
                QMessageBox.critical(self.view, "Load Failed", str(e))

    def warning_not_implemented(self):
        QMessageBox.warning(
            self.view,
            "Not Implemented",
            "This feature is not yet implemented.",
        )


class FlyCalculationThread(QThread):
    progress = pyqtSignal(int)
    message = pyqtSignal(str)
    finished = pyqtSignal()
    error = pyqtSignal(str)

    def __init__(self, fly_model, parent=None):
        super().__init__(parent)
        self.fly_model = fly_model
        self._canceled = False

    def run(self):
        try:
            self.message.emit("Starting weather forecast calculation...")
            # Example of updating progress at different stages
            self.progress.emit(0)
            # Run the actual weather forecast calculation
            self.fly_model.get_path_weather_forecast()
            self.message.emit("Weather forecast data obtained.")
            self.progress.emit(100)
            self.finished.emit()
        except Exception as e:
            self.error.emit(str(e))

    def cancel(self):
        self._canceled = True


class CalculationCompleteEvent(QEvent):
    EventType = QEvent.Type(QEvent.registerEventType())

    def __init__(self):
        super().__init__(CalculationCompleteEvent.EventType)


class SettingsPresenter:
    def __init__(self, view, model):
        self.model = model
        self.view = view

        # Populate view
        self.populate_settings()

        # Initialize connections (signals & slots)
        self.view.saveSettingsRequested.connect(self.save_settings)

    def populate_settings(self):
        # Ensure we pull fresh values from the model for each setting
        zp_definitions = self.model.get_all_settings_definitions(
            "settings_definitions_zero_pressure"
        )
        sp_definitions = self.model.get_all_settings_definitions(
            "settings_definitions_super_pressure"
        )
        others_definitions = self.model.get_all_settings_definitions(
            "settings_definitions_others"
        )
        # Create key-value pairs for settings with their current values and corresponding labels
        zp_settings = {
            key: (zp_definitions[key], self.model.get_setting(key))
            for key in zp_definitions
        }
        sp_settings = {
            key: (sp_definitions[key], self.model.get_setting(key))
            for key in sp_definitions
        }
        others_settings = {
            key: (others_definitions[key], self.model.get_setting(key))
            for key in others_definitions
        }

        self.view.populate_settings_group(self.view.optimization_ZP_layout, zp_settings)
        self.view.populate_settings_group(self.view.optimization_SP_layout, sp_settings)
        self.view.populate_settings_group(
            self.view.optimization_others_layout, others_settings
        )

    def save_settings(self):
        is_valid, validated_inputs = validate_inputs(
            self.view.input_widgets, self.model.validation_rules
        )

        if is_valid:
            self.store_validated_inputs(validated_inputs)
            # Proceed with further processing like optimization
            print("Settings form is valid. Proceeding with submission.")
            print("Validated inputs:", validated_inputs)
            QMessageBox.information(
                self.view,
                "Settings Saved",
                "Your settings have been successfully saved.",
            )
            self.view.close()

        else:
            # Show a message box informing the user to correct the inputs
            QMessageBox.warning(
                self.view, "Invalid Input", "Please correct the highlighted fields."
            )

    def store_validated_inputs(self, validated_inputs):
        for key, value in validated_inputs.items():
            self.model.set_setting(key, value)


class FilePresenter:
    def __init__(self, parameter_model, settings_model):
        self.parameter_model = parameter_model
        self.settings_model = settings_model
        # Handle file save/load operations
