import logging

from ..exceptions import NdffLibError

log = logging.getLogger(__name__)


class DataSource:
    """
    A DataSource is a class which (using the information from the config directories)
    is able to load / connect to a Datasource and can hand over NdffObservation
    objects one by one, either as an iterator, OR based on ID

    This class actually defines the 'interface' of the Datasources to get records from
    """

    @staticmethod
    def create_from_settings(settings=None):
        """
        Static method to create the current available types of Datasources: Postgres and CSV.
        For all other datasources we use the QGIS route.

        The creation of the Datasource is mostly a responsibility of the NdffConnector instance.

        The exact way to read in data or set up a connection is done in the different Subclasses of this base

        :param settings:
        :return:
        """
        # type of DataSource in settings:
        if settings and "datasource_type" in settings:
            if settings["datasource_type"] == "postgres":
                from .postgres import PostgresDataSource

                ds = PostgresDataSource(settings)
            elif settings["datasource_type"] == "csv":
                from .csv import CsvDataSource

                ds = CsvDataSource(settings)
            # because we do not want the library to have dependencies to Qt/Qgs classes, the
            # QgisDataSource is removed and changed by letting QGIS hand over a generic (feature)iterator to the
            # NDFFConnector
            # elif settings['datasource_type'] == 'qgis':
            #     ds = QgisDataSource(settings)
            else:
                raise NdffLibError(
                    f"Not Yet Implemented DataSource type: {settings['datasource_type']}"
                )
        else:
            ds = DataSource({})
        ds.settings = settings
        return ds

    def __init__(self, settings: dict = None):
        """
        Constructor in which the subclasses read the settings and use it to read or connect to data

        :param dict settings: Dictionary with (for subclass specific) parameters, see subclasses
        """
        if settings:
            self.settings = settings
        else:
            self.settings = {}
        self.ndff_config_directory = None  # NOT YET DEFINED...

    def __repr__(self) -> str:
        """
        Reimplement __repr__ to have a more readable human representation
        :return: str representation of this Datasource
        """
        return f"""Datasource: {self.__class__} {type(self)}
settings: {self.settings}
"""

    def _set_settings(self) -> None:
        """
        To set subclass-depending properties based on the self.settings.

        To be implemented by subclasses
        """
        pass

    def get_records(self):
        """
        Get all records as iterator, so the client should call 'next()' over the result

        :return: a generic 'record', can be of every type depending on Datasource
        """
        # log.warning(f'Not Implemented: "get_records" should be implemented in {self.__class__}')
        for record in []:
            yield record

    def get_record_by_id(self, id=None, id_name="id"):
        """
        Optional method to search for a record in the full dataset by id

        Note: not implemented in current versions

        :param id: the value of the record for the id_name key
        :param id_name: the key of the record which holds the id
        :return: an array with one record or [] if nothing found
        """
        log.critical(
            f'get_record_by_id(id={id}, id_name={id_name}) Not Implemented: "get_records" should be implemented in {self.__class__}'
        )
        return []
