from dataclasses import dataclass
from pathlib import Path
from typing import Dict, List

from qgis.core import QgsLineString, QgsProject, QgsVectorLayer
from xplordb.datamodel.survey import Survey

from openlog.datamodel.connection.interfaces.assay_interface import AssayInterface
from openlog.datamodel.connection.interfaces.categories_interface import (
    CategoriesInterface,
)
from openlog.datamodel.connection.interfaces.imports_interface import ImportsInterface
from openlog.datamodel.connection.interfaces.layers_interface import LayersInterface
from openlog.datamodel.connection.interfaces.read_interface import ReadInterface
from openlog.datamodel.connection.interfaces.write_interface import WriteInterface
from openlog.toolbelt import PlgLogger

NAME_KEY = "/name"
DB_KEY = "/db"
USER_KEY = "/user"
HOST_KEY = "/host"
PORT_KEY = "/port"
PASSWORD_KEY = "/password"
SERVICE_KEY = "/service"
QGS_CONFIG_ID_KEY = "/qgs_config_id"
OPTIONAL_PARAMS_KEY = "/optional_params"
LIST_KEY = "/list"


@dataclass
class Connection:
    user: str
    database: str
    host: str
    port: str
    name: str = ""
    qgs_config_id: str = ""
    password: str = ""
    service: str = ""

    optional_params = {}

    def get_postgres_args(self) -> Dict:
        """
         Get postgres dict of arguments for psycopg connection

        Returns: (Dict) postgres dict of arguments

        """
        args = {}
        if self.service:
            args["service"] = self.service
        if self.database:
            args["dbname"] = self.database
        if self.host:
            args["host"] = self.host
        if self.port:
            args["port"] = self.port
        if self.user:
            args["user"] = self.user
        if self.password:
            args["password"] = self.password
        return args

    @classmethod
    def from_qgis_project(cls, base_settings_key):
        connection = Connection(
            name=QgsProject.instance().readEntry(
                "OpenLog", base_settings_key + NAME_KEY
            )[0],
            host=QgsProject.instance().readEntry(
                "OpenLog", base_settings_key + HOST_KEY
            )[0],
            port=QgsProject.instance().readEntry(
                "OpenLog", base_settings_key + PORT_KEY
            )[0],
            database=QgsProject.instance().readEntry(
                "OpenLog", base_settings_key + DB_KEY
            )[0],
            user=QgsProject.instance().readEntry(
                "OpenLog", base_settings_key + USER_KEY
            )[0],
            qgs_config_id=QgsProject.instance().readEntry(
                "OpenLog", base_settings_key + QGS_CONFIG_ID_KEY
            )[0],
            service=QgsProject.instance().readEntry(
                "OpenLog", base_settings_key + SERVICE_KEY
            )[0],
        )

        optional_params = QgsProject.instance().readEntry(
            "OpenLog", base_settings_key + OPTIONAL_PARAMS_KEY
        )[0]
        for param in optional_params:
            connection.optional_params[param] = QgsProject.instance().readEntry(
                "OpenLog", base_settings_key + OPTIONAL_PARAMS_KEY + "/" + param
            )[0]

        return connection

    def save_to_qgis_project(self, base_settings_key: str) -> None:
        QgsProject.instance().writeEntry(
            "OpenLog", base_settings_key + NAME_KEY, self.host
        )
        QgsProject.instance().writeEntry(
            "OpenLog", base_settings_key + HOST_KEY, self.host
        )
        QgsProject.instance().writeEntry(
            "OpenLog", base_settings_key + DB_KEY, self.database
        )
        QgsProject.instance().writeEntry(
            "OpenLog", base_settings_key + PORT_KEY, self.port
        )
        QgsProject.instance().writeEntry(
            "OpenLog", base_settings_key + USER_KEY, self.user
        )
        QgsProject.instance().writeEntry(
            "OpenLog", base_settings_key + QGS_CONFIG_ID_KEY, self.qgs_config_id
        )
        QgsProject.instance().writeEntry(
            "OpenLog", base_settings_key + SERVICE_KEY, self.service
        )
        if self.optional_params:
            QgsProject.instance().writeEntry(
                "OpenLog",
                base_settings_key + OPTIONAL_PARAMS_KEY + LIST_KEY,
                list(self.optional_params.keys()),
            )
            for param in self.optional_params:
                QgsProject.instance().writeEntry(
                    "OpenLog",
                    base_settings_key + OPTIONAL_PARAMS_KEY + "/" + param,
                    self.optional_params[param],
                )


class OpenLogConnection:
    """
    Interface for OpenLogConnection.

    Several interfaces must be available :
    - get_layers_iface : LayersInterface : define several methods for OpenLogConnection layers (collar and collar trace)
    - get_read_iface : ReadInterface : read data from connection : get_available_person_codes / get_collar / get_surveys_from_collars
    - get_categories_iface : CategoriesInterface: define several methods for OpenLogConnection categories management
    - get_write_iface : WriteInterface : write data into connection (person, dataset, collar, survey and liths)
    - get_assay_iface : AssayInterface : read / write assay from/into connection

    Can be used to :
    - commit / rollback current connection

    By default, all functions are not implemented and raises InvalidInterface exception.
    """

    class ImportException(Exception):
        pass

    class InvalidInterface(Exception):
        pass

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

        self.log = PlgLogger().log
        # is symbology stored in database
        self.symbology_db_stored = False
        self.default_srid = None

    def advanced_imports_supported(self) -> bool:
        """
        Return True if this connection support import profiles and automated imports.
        """
        return False

    def desurvey_collars(self, collar_ids) -> None:
        """
        Desurvey collars and update database.
        Must be overriden.
        Args:
            - collar_ids: List[str] names of collars to be desurveyed
        """
        return None

    def get_geometry_fields(self) -> dict:
        """
        Map of display geometry fields and their corresponding SRID projected geometries
        Return a dictionnary containing display_geometry_field : real_geometry_field.
        """
        return {
            "effective_geom": "proj_effective_geom",
            "planned_geom": "proj_planned_geom",
            "geom": "proj_geom",
            "planned_loc": "proj_planned_loc",
            "geom_trace": "proj_geom_trace",
            "planned_trace": "proj_planned_trace",
        }

    def get_srids_attributes(self) -> tuple:
        """
        Return a tuple defining trace table columns defining original SRID and database SRID.
        If SRID is not defined inside table, return EPSG codes.
        """

        return None

    def get_database_name(self) -> str:
        """
        Return engine + name of database.
        """
        raise OpenLogConnection.InvalidInterface()

    def get_layers_iface(self) -> LayersInterface:
        """
        Returns LayersInterface for all layer related methods

        Returns: (LayersInterface)

        """
        return LayersInterface()

    def get_read_iface(self) -> ReadInterface:
        """
        Returns ReadInterface for all read related methods (person, dataset, collar, survey and liths)

        Returns:

        """
        return ReadInterface()

    def get_categories_iface(self) -> CategoriesInterface:
        """
        Returns CategoriesInterface for all categories related methods

        Returns:

        """
        return CategoriesInterface()

    def get_write_iface(self) -> WriteInterface:
        """
        Returns WriteInterface for all write related methods (person, dataset, collar, survey and liths)

        Returns:

        """
        return WriteInterface()

    def get_imports_iface(self) -> ImportsInterface:
        """
        Returns ImportsInterface.
        """
        return ImportsInterface()

    def get_assay_iface(self) -> AssayInterface:
        """
        Returns AssayInterface for all read/write assay related methods

        Returns:

        """
        return AssayInterface()

    def rollback(self) -> None:
        """
        Rollback current changes

        raises OpenLogConnection.InvalidInterface if not implemented

        """
        raise OpenLogConnection.InvalidInterface()

    def commit(self) -> None:
        """
        Commit current changes

        raises OpenLogConnection.InvalidInterface if not implemented

        """
        raise OpenLogConnection.InvalidInterface()

    def selected_collar_surveying_available(self) -> bool:
        """
        Return True if the collar surveying is available
        :return: A boolean value.
        """
        return True

    def collar_and_survey_edition_available(self) -> bool:
        """
        Returns True if surveys and collar attributes can be edited.
        """
        return True

    def set_collar_desurveying(
        self, hole_id: str, geom: QgsLineString, planned: bool = False
    ):
        """
        Define desurveying for a collar

        :param hole_id: The collar id of the hole
        :type hole_id: str
        :param geom: The geometry of the desurveying
        """
        raise OpenLogConnection.InvalidInterface()

    def get_mainwindow_title(self) -> str:
        """
        Returns string for QGIS mainwindow title definition for connection
        raises OpenLogConnection.InvalidInterface if not implemented

        """
        raise OpenLogConnection.InvalidInterface()

    def save_to_qgis_project(self, base_settings_key: str) -> None:
        """
        Save connection to QGIS project
        raises OpenLogConnection.InvalidInterface if not implemented

        """
        raise OpenLogConnection.InvalidInterface()

    @staticmethod
    def create_from_qgis_project(base_settings_key: str, parent):
        """
        Save connection to QGIS project
        raises OpenLogConnection.InvalidInterface if not implemented

        """
        raise OpenLogConnection.InvalidInterface()

    @staticmethod
    def _copy_filter_from_project_layers(input_layer: QgsVectorLayer):
        if input_layer:
            for layer in QgsProject.instance().mapLayersByName(input_layer.name()):
                if layer != input_layer:
                    input_layer.setSubsetString(layer.subsetString())
                break
