from typing import Optional

import sqlalchemy
from geoalchemy2 import WKTElement
from xplordb.datamodel.survey import Survey
from xplordb.sqlalchemy.dh.collar import XplordbCollarTable
from xplordb.sqlalchemy.dh.lith import XplordbLithTable
from xplordb.sqlalchemy.dh.survey import XplordbSurveyTable
from xplordb.sqlalchemy.ref.dataset import XplordbDatasetTable
from xplordb.sqlalchemy.ref.person import XplordbPersonTable

from openlog.datamodel.connection.interfaces.assay_interface import AssayInterface
from openlog.datamodel.connection.interfaces.categories_interface import (
    CategoriesInterface,
)
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.datamodel.connection.openlog_connection import (
    Connection,
    OpenLogConnection,
)
from openlog.datamodel.connection.postgres_utils import create_session
from openlog.datamodel.connection.spatialite.database_object import convert_to_db_crs
from openlog.datamodel.connection.sqlalchemy.sqlalchemy_read_interface import (
    SqlAlchemyReadInterface,
)
from openlog.datamodel.connection.updater.database_updater import XplordbDataBaseUpdater
from openlog.datamodel.connection.xplordb.xplordb_assay_interface import (
    XplordbAssayInterface,
)
from openlog.datamodel.connection.xplordb.xplordb_categories_interface import (
    XplordbCategoriesInterface,
)
from openlog.datamodel.connection.xplordb.xplordb_layers_interface import (
    XplordbLayersInterface,
)
from openlog.datamodel.connection.xplordb.xplordb_write_interface import (
    XplordbWriteInterface,
)
from openlog.gui.connection.xplordb_connection_dialog import XplordbConnectionDialog

BASE_SETTINGS_KEY = "/XplordbConnection"


class XplordbConnection(OpenLogConnection):
    """
    OpenLogConnection interface implementation for Xplordb connection.

    """

    def __init__(self, connection: Connection):
        """
        OpenLogConnection interface for xplordb database

        Args:
            connection: connection params
        """
        super().__init__()
        self._connection = connection
        self.session, self.engine = create_session(self._connection)

        self._layers_iface = XplordbLayersInterface(connection)
        self._read_iface = SqlAlchemyReadInterface(
            session=self.session,
            person_base=XplordbPersonTable,
            dataset_base=XplordbDatasetTable,
            collar_base=XplordbCollarTable,
            survey_base=XplordbSurveyTable,
        )
        self._categories_iface = XplordbCategoriesInterface(
            engine=self.engine,
            session=self.session,
            schema="ref",
        )
        self._write_iface = XplordbWriteInterface(
            session=self.session, read_iface=self._read_iface
        )
        self._assay_iface = XplordbAssayInterface(
            engine=self.engine,
            session=self.session,
            categories_iface=self._categories_iface,
        )

        self.survey_base = XplordbSurveyTable

        self._add_default_content_for_categories()

        self.updater = XplordbDataBaseUpdater(self)
        self.default_srid = 4326

    def get_database_name(self):

        return "-".join([self.engine.name, self._connection.database])

    def _add_default_content_for_categories(self):
        try:
            self._categories_iface.categories_table.create(self.engine)
        except sqlalchemy.exc.ProgrammingError:
            # No error if tables already exists
            pass

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

        Returns: (LayersInterface)

        """
        return self._layers_iface

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

        Returns:

        """
        return self._read_iface

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

        Returns:

        """
        return self._categories_iface

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

        Returns:

        """
        return self._write_iface

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

        Returns:

        """
        return self._assay_iface

    def rollback(self) -> None:

        """
        Rollback current changes

        Raises OpenLogConnection.ImportException if rollback fail

        """
        try:
            self.session.rollback()
            self._categories_iface.rollback()
            self._assay_iface.rollback()

        except sqlalchemy.exc.SQLAlchemyError as exc:
            raise OpenLogConnection.ImportException(exc)

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

        Raises OpenLogConnection.ImportException on import failure

        """
        try:
            self.session.commit()
            self._categories_iface.commit()
            self._assay_iface.commit()
        except sqlalchemy.exc.SQLAlchemyError as exc:
            raise OpenLogConnection.ImportException(exc)

    def selected_collar_surveying_available(self) -> bool:
        """
        Return True if the selected collar surveying is available.
        Desurveying is done by postgis for xplordb.

        :return: False.
        """
        return False

    def set_collar_desurveying(self, hole_id: str, geom, planned: bool = False):
        # TODO : dead code ?
        """
        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

        """
        if geom.vertexCount() < 2:
            return
        crs = self.get_layers_iface().get_collar_layer().crs().postgisSrid()

        # Get collar object from session
        collar = self.get_read_iface().get_collar(hole_id)
        if collar.srid != crs:
            geom = convert_to_db_crs(collar.srid, geom, crs)
        # Need to remove Z because of geoalchemy
        geom.addMValue()
        wkte = WKTElement(geom.toCurveType().asWkt(), srid=crs)

        if planned:
            collar.planned_trace = wkte
        else:
            collar.geom_trace = wkte
        # Update xplordb database
        self.commit()

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

        """
        return f"Xplordb : database : {self._connection.database} / user : {self._connection.user}"

    def save_to_qgis_project(self, base_settings_key: str) -> None:
        """
        Save connection to QGIS project

        """
        base_key = base_settings_key + BASE_SETTINGS_KEY
        self._connection.save_to_qgis_project(base_key)

    @staticmethod
    def create_from_qgis_project(
        base_settings_key: str, parent
    ) -> Optional[OpenLogConnection]:
        """
        Create connection from QGIS project

        """
        base_key = base_settings_key + BASE_SETTINGS_KEY
        connection = Connection.from_qgis_project(base_key)

        widget = XplordbConnectionDialog(parent)
        widget.set_connection_model(connection)
        if widget.exec():
            connection = widget.get_connection_model()
            openlog_connection = XplordbConnection(connection)
            openlog_connection._copy_filter_from_project_layers(
                openlog_connection.get_layers_iface().get_collar_layer()
            )
            openlog_connection._copy_filter_from_project_layers(
                openlog_connection.get_layers_iface().get_collar_trace_layer()
            )
            return openlog_connection
        else:
            return None
