import asyncio
import concurrent.futures
import qasync
import pandas as pd
from PySide6 import QtWidgets
from PySide6.QtCore import Slot, QAbstractTableModel, Qt, QModelIndex
from PySide6.QtWidgets import QFileDialog, QMessageBox
from astropy.coordinates import EarthLocation

from pybrotlib import BROT, Telemetry
from .pointing.pointing import Pointing
from .qt.pointingmodel_ui import Ui_PointingModel


class PandasModel(QAbstractTableModel):
    # source: https://learndataanalysis.org/source-code-create-a-python-desktop-app-to-view-csv-files-with-pyqt6/ (08.08.2025)
    def __init__(self, df, parent=None):
        super().__init__(parent)
        self.df = df

    def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole):
        if role != Qt.ItemDataRole.DisplayRole:
            return None
        if orientation == Qt.Orientation.Horizontal:
            try:
                return self.df.columns.tolist()[section]
            except IndexError:
                return None
        elif orientation == Qt.Orientation.Vertical:
            try:
                return self.df.index.tolist()[section]
            except IndexError:
                return None

    def rowCount(self, parent=QModelIndex()):
        return self.df.shape[0]

    def columnCount(self, parent=QModelIndex()):
        return self.df.shape[1]

    def data(self, index, role=Qt.ItemDataRole.DisplayRole):
        if role != Qt.ItemDataRole.DisplayRole:
            return None
        if not index.isValid():
            return None
        return str(self.df.iloc[index.row(), index.column()])


class QPointingModel(QtWidgets.QWidget, Ui_PointingModel):
    executor = concurrent.futures.ThreadPoolExecutor(max_workers=3)

    def __init__(
        self, telemetry: Telemetry, brot: BROT, location: EarthLocation, pointing_model: Pointing, parent=None
    ):
        QtWidgets.QWidget.__init__(self, parent)
        self.setupUi(self)
        self.telemetry = telemetry
        self.brot = brot
        self.location = location
        self.pointing_model = pointing_model
        self.data = pd.DataFrame()
        self.model = pd.DataFrame()

    def _update_coords_table(self):
        self.tableCoords.setModel(PandasModel(self.data))

    def _update_model_table(self):
        self.tableModelParams.setModel(PandasModel(self.model))

    @Slot()
    def on_buttonAddCurrentCoord_clicked(self):
        row = self.pointing_model.get_telescope_data(self.telemetry)
        self.data = pd.concat([self.data, pd.DataFrame([row])], ignore_index=True)
        self._update_coords_table()

    @Slot()
    def on_buttonDeleteSelectedCoord_clicked(self):
        row = self.tableCoords.currentIndex().row()
        if row >= 0:
            self.data.drop(row, inplace=True)
            self.data.reset_index(drop=True, inplace=True)
            self._update_coords_table()

    @Slot()
    def on_buttonSaveCoords_clicked(self):
        filename, _ = QFileDialog.getSaveFileName(self, "Save File", "./pointing/", "Pointing (*.csv)")
        try:
            self.data.to_csv(filename, encoding="utf-8", index=False, header=True)
        except Exception as error:
            print(error)
            QMessageBox.critical(
                self, "Saving Pointing File", "Could not save " + filename + "\nSee Terminal for error."
            )

    @Slot()
    def on_buttonLoadCoords_clicked(self):
        filename, _ = QFileDialog.getOpenFileName(self, "Open File", "./pointing/", "Pointing (*.csv)")
        try:
            self.data = pd.read_csv(filename)
            self._update_coords_table()
        except Exception as error:
            print(error)
            QMessageBox.critical(
                self, "Loading Pointing File", "Could not load " + filename + "\nSee Terminal for error."
            )

    @Slot()
    def on_buttonSaveModel_clicked(self):
        filename, _ = QFileDialog.getSaveFileName(self, "Save File", "./pointing/", "Pointing params(*.csv)")
        try:
            self.model.to_csv(filename, encoding="utf-8", index=False, header=True)
        except Exception as error:
            print(error)
            QMessageBox.critical(
                self, "Saving Pointing File", "Could not save " + filename + "\nSee Terminal for error."
            )

    @Slot()
    def on_buttonLoadModel_clicked(self):
        filename, _ = QFileDialog.getOpenFileName(self, "Open File", "./pointing/", "Pointing params(*.csv)")
        try:
            self.model = pd.read_csv(filename)
        except Exception as error:
            print(error)
            QMessageBox.critical(
                self, "Loading Pointing File", "Could not load " + filename + "\nSee Terminal for error."
            )

    @qasync.asyncSlot()
    async def on_buttonFitModel_clicked(self):
        await self.fit_pointing_model(self.location.lat.degree)

    async def fit_pointing_model(self, latitude) -> None:
        loop = asyncio.get_running_loop()
        self.model = await loop.run_in_executor(self.executor, self.pointing_model.fit_model, self.data, latitude)
        self._update_model_table()
