"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 24.01.22

"""
import json
from abc import abstractmethod

from django.contrib.gis import geos
from django.contrib.gis.geos import GEOSGeometry
from django.core.paginator import Paginator

from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
from konova.utils.message_templates import DATA_UNSHARED


class AbstractModelAPISerializer:
    model = None
    lookup = None
    properties_data = None

    rpp = None
    page_number = None
    paginator = None

    class Meta:
        abstract = True

    def __init__(self, *args, **kwargs):
        self.lookup = {
            "id": None,  # must be set
            "deleted__isnull": True,
            "users__in": [],  # must be set
        }
        super().__init__(*args, **kwargs)

    @abstractmethod
    def _model_to_geo_json(self, entry):
        """ Defines the model as geo json

        Args:
            entry (): The found entry from the database

        Returns:

        """
        raise NotImplementedError("Must be implemented in subclasses")

    @abstractmethod
    def _extend_properties_data(self, entry):
        """ Defines the 'properties' part of geo json

        Args:
            entry (): The found entry from the database

        Returns:

        """
        raise NotImplementedError("Must be implemented in subclasses")

    def prepare_lookup(self, _id, user):
        """ Updates lookup dict for db fetching

        Args:
            _id (str): The object's id
            user (User): The user requesting for

        Returns:

        """
        if _id is None:
            # Return all objects
            del self.lookup["id"]
        else:
            # Return certain object
            self.lookup["id"] = _id
        self.lookup["users__in"] = [user]

    def fetch_and_serialize(self):
        """ Serializes the model entry according to the given lookup data

        Args:

        Returns:
            serialized_data (dict)
        """
        entries = self.model.objects.filter(**self.lookup).order_by("id")
        self.paginator = Paginator(entries, self.rpp)
        requested_entries = self.paginator.page(self.page_number)

        serialized_data = {}
        for entry in requested_entries.object_list:
            serialized_data[str(entry.id)] = self._model_to_geo_json(entry)
        return serialized_data

    @abstractmethod
    def update_model_from_json(self, id, json_model, user):
        """ Updates an instance from given json data

        Args:
            id (str): The instance's to be updated
            json_model (dict): JSON data
            user (User): The performing user

        Returns:

        """
        raise NotImplementedError("Must be implemented in subclasses")

    @abstractmethod
    def create_model_from_json(self, json_model, user):
        """ Creates a new instance from given json data

        Args:
            json_model (dict): JSON data
            user (User): The performing user

        Returns:

        """
        raise NotImplementedError("Must be implemented in subclasses")

    def _create_geometry_from_json(self, geojson) -> GEOSGeometry:
        """ Creates a GEOSGeometry object based on the given geojson

        Args:
            geojson (str|dict): The geojson as str or dict

        Returns:
            geometry (GEOSGeometry)
        """
        if isinstance(geojson, dict):
            geojson = json.dumps(geojson)
        geometry = geos.fromstr(geojson)
        if geometry.srid != DEFAULT_SRID_RLP:
            geometry.transform(DEFAULT_SRID_RLP)
        return geometry

    def _get_obj_from_db(self, id, user):
        """ Returns the object from database

        Fails if id not found or user does not have shared access

        Args:
            id (str): The object's id
            user (User): The API user

        Returns:

        """
        obj = self.model.objects.get(
            id=id,
            deleted__isnull=True,
        )
        is_shared = obj.is_shared_with(user)
        if not is_shared:
            raise PermissionError(DATA_UNSHARED)
        return obj

    @abstractmethod
    def _initialize_objects(self, json_model, user):
        """ Initializes all needed objects from the json_model data

        Does not persist data to the DB!

        Args:
            json_model (dict): The json data
            user (User): The API user

        Returns:
            obj (Intervention)
        """
        raise NotImplementedError("Must be implemented in subclasses")