import requests
from typing import List, Optional
from .constant import RipeSource, API_ENDPOINT
from . import models
from . import exceptions
from . import utils

class RipeRestApiClient:
    source: RipeSource
    __session: requests.Session

    def __init__(self, source: RipeSource, basic_auth: str = ""):
        self.source = source
        self.__session = requests.Session()
        self.__session.headers.update({
            "Accept": "application/json"
        })
        if basic_auth:
            self.__session.headers.update({"Authorization": f"Basic {basic_auth}"})

    def __request(self, method: str, path: str, **kwargs) -> models.WhoisResources:
        resp = self.__session.request(method, f"{API_ENDPOINT[self.source]}/{path}", **kwargs)
        if resp.status_code != 200:
            raise exceptions.RipeRestApiServerError(resp.status_code, models.WhoisResources.ripe_deserialize(resp.json()).errormessages)
        return models.WhoisResources.ripe_deserialize(resp.json())

    def get_object(
            self,
            pk: models.Attributes,
            unfiltered: bool = False,
            managed_attributes: bool = False,
            abuse_contact: bool = False,
            resource_holder: bool = False
    ) -> models.WhoisResources:
        obj_type, lookup_key = utils.get_lookup_key_from_attributes(pk)
        resp = self.__request("GET", f"{self.source}/{obj_type}/{lookup_key}", params={
            "unfiltered": unfiltered,
            "managed-attributes": managed_attributes,
            "abuse-contact": abuse_contact,
            "resource-holder": resource_holder
        })
        if resp.objects is None or resp.objects.object is None or len(resp.objects.object) != 1:
            raise exceptions.RipeRestApiUnexpectedResponseError(response=resp)
        return resp

    def search(
            self,
            query: str,
            sources: List[RipeSource | str] = [],
            inverse_attributes: Optional[List[str]] = None,
            include_tags: Optional[List[str]] = None,
            exclude_tags: Optional[List[str]] = None,
            type_filters: Optional[List[str]] = None,
            flags: Optional[List[str]] = None,
            managed_attributes: bool = False,
            abuse_contact: bool = False,
            resource_holder: bool = False,
            limit: Optional[int] = None,
            offset: Optional[int] = None
    ) -> models.WhoisResources:
        if len(sources) == 0:
            sources = [self.source]
        resp = self.__request("GET", "search", params={
            "query-string": query,
            "source": sources,
            "inverse-attribute": inverse_attributes,
            "include-tag": include_tags,
            "exclude-tag": exclude_tags,
            "type-filter": type_filters,
            "flags": flags,
            "managed-attributes": managed_attributes,
            "abuse-contact": abuse_contact,
            "resource-holder": resource_holder,
            "limit": limit,
            "offset": offset
        })
        return resp

    def create_object(self, obj: models.Object) -> models.WhoisResources:
        if not obj.attributes:
            raise exceptions.RipeRestApiUnexpectedInputError()
        obj_type, _ = utils.get_lookup_key_from_attributes(obj.attributes)
        resp = self.__request("POST", f"{self.source}/{obj_type}", json=models.WhoisResources(
            objects=models.Objects(object=[obj])
        ).ripe_serialize())
        if resp.objects is None or resp.objects.object is None or len(resp.objects.object) != 1:
            raise exceptions.RipeRestApiUnexpectedResponseError(response=resp)
        return resp

    def update_object(self, obj: models.Object) -> models.WhoisResources:
        if not obj.attributes:
            raise exceptions.RipeRestApiUnexpectedInputError()
        obj_type, lookup_key = utils.get_lookup_key_from_attributes(obj.attributes)
        resp = self.__request("PUT", f"{self.source}/{obj_type}/{lookup_key}", json=models.WhoisResources(
            objects=models.Objects(object=[obj])
        ).ripe_serialize())
        if resp.objects is None or resp.objects.object is None or len(resp.objects.object) != 1:
            raise exceptions.RipeRestApiUnexpectedResponseError(response=resp)
        return resp

    def delete_object(self, pk: models.Attributes) -> models.WhoisResources:
        obj_type, lookup_key = utils.get_lookup_key_from_attributes(pk)
        resp = self.__request("DELETE", f"{self.source}/{obj_type}/{lookup_key}")
        if resp.objects is None or resp.objects.object is None or len(resp.objects.object) != 1:
            raise exceptions.RipeRestApiUnexpectedResponseError(response=resp)
        return resp
