import json

from qgis.PyQt.QtWidgets import QMessageBox
from sqlalchemy import and_

from openlog.__about__ import __version__


class ConfigSaver:
    """
    Class designed to save or restore visualization configuration (AssayColumnVisualizationConfig).
    Configuration is serialized as a JSON and saved to database or a file.
    """

    class InvalidSaver(Exception):
        pass

    def _serialize(self, config) -> dict:
        """
        Construct a dictionnary with key-value corresponding to config's main attributes.
        Mandatory informations to save :
            - variable name
            - column name
            - openlog version.

        Must be overriden for each AssayColumnVisualizationConfig subclass
        """
        d = {}
        d["assay_name"] = config.assay_name
        d["column"] = config.column.name
        d["openlog_version"] = __version__
        return d

    def _deserialize(self, config, dict: dict) -> None:
        """
        Modify config's attributes in-place according to dictionnary.
        Must be overriden for each AssayColumnVisualizationConfig subclass
        """
        raise ConfigSaver.InvalidSaver()

    def save_to_db(self, config) -> None:
        """
        Save serialized config in database as string in a JSON format.
        """
        # serialize and insert/update
        d = self._serialize(config)
        assay_iface = config.assay_iface
        assay_column = config.column
        assay_name = config.assay_name
        try:
            assay_iface.session.query(assay_iface.assay_column_definition_base).filter(
                and_(
                    assay_iface.assay_column_definition_base.name == assay_column.name,
                    assay_iface.assay_column_definition_base.assay == assay_name,
                )
            ).update({"symbology": json.dumps(d)})
            assay_iface.session.commit()
        except Exception:
            assay_iface.session.rollback()

    def save_to_file(self, config, file: str) -> None:
        """
        Save serialized config in a JSON file.
        """
        # serialize and export
        d = self._serialize(config)
        with open(file, "w") as f:
            f.write(json.dumps(d))

    def _is_config_valid(self, config, d: dict) -> bool:
        """
        Check assay and column names.
        """
        return config.assay_name == d.get("assay_name") and config.column.name == d.get(
            "column"
        )

    def load_from_file(self, config, file: str) -> None:
        """
        Deserialize JSON file to a config.
        """
        # read and deserialize
        with open(file, "r") as f:
            d = json.loads(f.read())
            if self._is_config_valid(config, d):
                self._deserialize(config, d)
            else:
                QMessageBox.warning(
                    None,
                    "Invalid file",
                    "Assay or column name doesn't match.",
                )

    def get_database_symbology(self, config) -> dict:
        """
        Return dictionnary of symbology parameters.
        """
        # read and deserialize
        assay_iface = config.assay_iface
        if assay_iface is None:
            return
        assay_column = config.column
        assay_name = config.assay_name
        result = (
            assay_iface.session.query(assay_iface.assay_column_definition_base)
            .filter(
                and_(
                    assay_iface.assay_column_definition_base.name == assay_column.name,
                    assay_iface.assay_column_definition_base.assay == assay_name,
                )
            )
            .first()
        )
        if hasattr(result, "symbology") and result.symbology is not None:
            d = json.loads(result.symbology)
            return d

    def load_from_db(self, config) -> None:
        """
        Deserialize config from database.
        """
        # read and deserialize
        d = self.get_database_symbology(config)
        if d is not None:
            self._deserialize(config, d)

    def load_from_string(self, config, s: str) -> None:
        """
        Deserialize config from AssayColumn instance.
        """
        try:
            self._deserialize(config, json.loads(s))
        except:
            pass
