"""Mappls Realverse API v1. Python 3.10+, no dependencies; server use only."""
import json
from urllib.request import Request, urlopen
from urllib.error import HTTPError
from urllib.parse import quote, urlencode

class RealverseError(Exception):
    def __init__(self, message, status):
        super().__init__(message)
        self.status = status

class RealverseClient:
    def __init__(self, base_url, api_key, timeout=30):
        self.base_url = base_url.rstrip('/')
        self.api_key = api_key
        self.timeout = timeout

    def _request(self, path, body=None, method=None):
        headers = {'Authorization': 'Bearer ' + self.api_key}
        data = None
        if body is not None:
            headers['Content-Type'] = 'application/octet-stream' if isinstance(body, bytes) else 'application/json'
            data = body if isinstance(body, bytes) else json.dumps(body).encode()
        req = Request(self.base_url + '/api/v1/' + path, data=data, headers=headers, method=method)
        try:
            with urlopen(req, timeout=self.timeout) as response:
                return json.load(response)
        except HTTPError as error:
            try:
                message = json.load(error).get('error', str(error))
            except (ValueError, AttributeError):
                message = str(error)
            raise RealverseError(message, error.code) from error

    def mappls_config(self):
        return self._request('mappls/config')

    def mappls_search(self, **options):
        return self._request('mappls/autosuggest?' + urlencode(options))

    def mappls_nearby(self, **options):
        return self._request('mappls/nearby?' + urlencode(options))

    def mappls_reverse(self, lat, lon):
        return self._request('mappls/reverse?' + urlencode({'lat': lat, 'lon': lon}))

    def mappls_place(self, pin):
        return self._request('mappls/place?' + urlencode({'pin': pin}))

    def mappls_route(self, **options):
        return self._request('mappls/route?' + urlencode(options))

    def list_scenarios(self):
        return self._request('scenarios')

    def capabilities(self):
        return self._request('capabilities')

    def list_comments(self, scenario, place_id, offset=0):
        from urllib.parse import urlencode
        return self._request(self._comments_path(scenario, place_id) + '?' + urlencode({'offset': offset}))

    @staticmethod
    def _comments_path(scenario, place_id, comment_id=None):
        path = 'scenarios/' + quote(scenario, safe='') + '/places/' + quote(place_id, safe='') + '/comments'
        return path + '/' + quote(comment_id, safe='') if comment_id is not None else path

    def create_comment(self, scenario, place_id, text, parent_id=None, note=''):
        return self._request(self._comments_path(scenario, place_id), {'text': text, 'expectedVersion': 0, 'note': note, **({'parentId': parent_id} if parent_id is not None else {})})

    def update_comment(self, scenario, place_id, comment_id, text, expected_version, note=''):
        return self._request(self._comments_path(scenario, place_id, comment_id), {'text': text, 'expectedVersion': expected_version, 'note': note}, 'PATCH')

    def delete_comment(self, scenario, place_id, comment_id, expected_version, note=''):
        return self._request(self._comments_path(scenario, place_id, comment_id), {'expectedVersion': expected_version, 'note': note}, 'DELETE')

    def list_comment_contributions(self, status='', offset=0):
        from urllib.parse import urlencode
        return self._request('comment-contributions?' + urlencode({'status': status, 'offset': offset}))

    def review_comment(self, contribution_id, action, note):
        return self._request('comment-contributions/' + quote(contribution_id, safe='') + '/review', {'action': action, 'note': note})

    def get_scenario(self, scenario):
        return self._request('scenarios/' + quote(scenario, safe=''))

    def list_places(self, scenario):
        return self._request('scenarios/' + quote(scenario, safe='') + '/places')

    def get_place(self, scenario, place):
        return self._request('scenarios/' + quote(scenario, safe='') + '/places/' + quote(place, safe=''))

    def list_tours(self, scenario):
        return self._request('scenarios/' + quote(scenario, safe='') + '/tours')

    def create_embed_session(self, scenario, origin=None, ttl_seconds=900, place=None):
        return self._request('embed-sessions', {'scenario': scenario, **({'origin': origin} if origin is not None else {}), 'ttlSeconds': ttl_seconds, **({'place': place} if place is not None else {})})

    def create_place(self, scenario, place, note=''):
        return self._request('scenarios/' + quote(scenario, safe='') + '/places', {'place': place, 'expectedVersion': 0, 'note': note})

    def update_place(self, scenario, place_id, place, expected_version, note=''):
        return self._request('scenarios/' + quote(scenario, safe='') + '/places/' + quote(place_id, safe=''), {'place': place, 'expectedVersion': expected_version, 'note': note}, 'PATCH')

    def delete_place(self, scenario, place_id, expected_version, note=''):
        return self._request('scenarios/' + quote(scenario, safe='') + '/places/' + quote(place_id, safe=''), {'expectedVersion': expected_version, 'note': note}, 'DELETE')

    def import_places(self, scenario, places, note=''):
        return self._request('scenarios/' + quote(scenario, safe='') + '/import', {'places': places, 'note': note})

    def list_contributions(self, scenario='', status='', offset=0):
        from urllib.parse import urlencode
        return self._request('contributions?' + urlencode({'scenario': scenario, 'status': status, 'offset': offset}))

    def review_contribution(self, contribution_id, action, note):
        return self._request('contributions/' + quote(contribution_id, safe='') + '/review', {'action': action, 'note': note})

    def list_media(self, scenario):
        return self._request('scenarios/' + quote(scenario, safe='') + '/media')

    def upload_media(self, scenario, filename, content_bytes):
        return self._request('scenarios/' + quote(scenario, safe='') + '/media?filename=' + quote(filename, safe=''), bytes(content_bytes), 'POST')

    def delete_unused_media(self, scenario, media_id):
        return self._request('scenarios/' + quote(scenario, safe='') + '/media/' + quote(media_id, safe=''), {}, 'DELETE')
