# passerelle - uniform access to multiple data sources and services
# Copyright (C) 2023  Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import base64
import binascii
import json
import urllib

import requests
from django.db import models
from django.utils.translation import gettext_lazy as _

from passerelle.base.models import BaseResource, HTTPResource
from passerelle.utils.api import endpoint
from passerelle.utils.jsonresponse import APIError

from . import schemas


class Filr(BaseResource, HTTPResource):
    base_url = models.URLField(
        verbose_name=_('Webservice Base URL'),
    )

    category = _('File Storage')

    class Meta:
        verbose_name = _('Filr')

    def _call(self, path, method='get', json_data=None, data=None, headers=None, params=None):
        kwargs = {}
        if data:
            kwargs['data'] = data
        if json_data:
            kwargs['json'] = json_data
        if headers:
            kwargs['headers'] = headers
        if params:
            kwargs['params'] = params
        try:
            resp = self.requests.request(
                method=method, url=urllib.parse.urljoin(self.base_url, path), **kwargs
            )
        except (requests.Timeout, requests.RequestException) as e:
            raise APIError(str(e))
        try:
            resp.raise_for_status()
        except requests.RequestException as main_exc:
            try:
                err_data = resp.json()
            except (json.JSONDecodeError, requests.exceptions.RequestException):
                err_data = {'response_text': resp.text}
            raise APIError(str(main_exc), data=err_data)

        content_type = resp.headers.get('Content-Type')
        if content_type and content_type.startswith('application/json') and resp.status_code != 204:
            try:
                return resp.json()
            except (json.JSONDecodeError, requests.exceptions.JSONDecodeError) as e:
                raise APIError(str(e))

        return resp.text

    @endpoint(
        perm='can_access',
        description=_('Upload a file'),
        post={
            'request_body': {'schema': {'application/json': schemas.UPLOAD}},
        },
    )
    def upload(self, request, post_data):
        root_folder_id, folder_name = post_data['root_folder_id'], post_data['folder_name']
        try:
            file_content = base64.b64decode(post_data['file']['content'])
        except (TypeError, binascii.Error):
            raise APIError('invalid base64 string', http_status=400)
        filename = post_data.get('filename') or post_data['file']['filename']

        # get or create folder
        folder_id = None
        root_folder_info = self._call('rest/folders/%s/library_folders' % root_folder_id)
        for folder in root_folder_info.get('items', []):
            if folder.get('title') == folder_name:
                folder_id = folder.get('id')
                break
        else:
            folder_info = self._call(
                'rest/folders/%s/library_folders' % root_folder_id,
                method='post',
                json_data={'title': folder_name},
            )
            folder_id = folder_info['id']

        # upload file
        file_info = self._call(
            'rest/folders/%s/library_files' % folder_id,
            method='post',
            params={'file_name': filename},
            headers={'Content-Type': 'application/octet-stream'},
            data=file_content,
        )
        return {'data': {'folder_id': folder_id, 'file_info': file_info}}

    @endpoint(
        name='share-folder',
        perm='can_access',
        description=_('Share a folder to external users'),
        post={
            'request_body': {'schema': {'application/json': schemas.SHARE_FOLDER}},
            'input_example': {
                'folder_id': '1234',
                'emails/0': 'foo@invalid',
                'emails/1': 'bar@invalid',
                'days_to_expire': '30',
            },
        },
    )
    def share_folder(self, request, post_data):
        data = []
        for email in post_data['emails']:
            share_info = self._call(
                'rest/folders/%s/shares' % post_data['folder_id'],
                method='post',
                params={'notify': 'true'},
                json_data={
                    'days_to_expire': post_data['days_to_expire'],
                    'recipient': {'type': 'external_user', 'email': email},
                    'access': {'role': 'VIEWER'},
                },
            )
            data.append(share_info)
        return {'data': data}

    @endpoint(
        name="delete-folder",
        perm='can_access',
        methods=['post'],
        description=_('Delete a folder'),
        post={'request_body': {'schema': {'application/json': schemas.DELETE_FOLDER}}},
    )
    def delete_folder(self, request, post_data):
        delete_infos = self._call('rest/folders/%s' % post_data['folder_id'], method='delete')
        return {'data': delete_infos}
