from typing import Optional

import sqlalchemy
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.sqlalchemy.sqlachemy_categories_interface import (
    SqlAlchemyCategoriesInterface,
)
from openlog.datamodel.connection.sqlalchemy.sqlalchemy_read_interface import (
    SqlAlchemyReadInterface,
)
from openlog.datamodel.connection.xplordb.xplordb_assay_interface import (
    XplordbAssayInterface,
)
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 = SqlAlchemyCategoriesInterface(
            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()

    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.

        For xplordb there is no manual desurveying, it's done in database

        :return: False.
        """
        return False

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

        """
        return self.tr(
            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
