# Part of Odoo. See LICENSE file for full copyright and licensing details.

import base64
import json
import requests
from datetime import datetime, timezone

try:
    from google.oauth2 import service_account
    from google.auth.transport.requests import Request
except ImportError:
    service_account = Request = None

from odoo import models, fields, api, _
from odoo.exceptions import ValidationError, UserError

from .ir_attachment import get_cloud_storage_google_credential


class CloudStorageSettings(models.TransientModel):
    """
    Instructions:
    cloud_storage_google_bucket_name: if changed and the old bucket name
        are still in use, you should promise the current service account
        has the permission to access the old bucket.
    """
    _inherit = 'res.config.settings'

    cloud_storage_provider = fields.Selection(selection_add=[('google', 'Google Cloud Storage')])

    cloud_storage_google_bucket_name = fields.Char(
        string='Google Bucket Name',
        config_parameter='cloud_storage_google_bucket_name')
    # Google Service Account Key in JSON format
    cloud_storage_google_service_account_key = fields.Binary(
        string='Google Service Account Key', store=False
    )
    cloud_storage_google_account_info = fields.Char(
        string='Google Service Account Info',
        compute='_compute_cloud_storage_google_account_info',
        store=True,
        readonly=False,
        config_parameter='cloud_storage_google_account_info',
    )

    def get_values(self):
        res = super().get_values()
        if account_info := self.env['ir.config_parameter'].get_param('cloud_storage_google_account_info'):
            res['cloud_storage_google_service_account_key'] = base64.b64encode(account_info.encode())
        return res

    @api.onchange('cloud_storage_google_service_account_key')
    def _compute_cloud_storage_google_account_info(self):
        for setting in self:
            key = setting.with_context(bin_size=False).cloud_storage_google_service_account_key
            setting.cloud_storage_google_account_info = base64.b64decode(key) if key else False

    def _setup_cloud_storage_provider(self):
        ICP = self.env['ir.config_parameter']
        if ICP.get_param('cloud_storage_provider') != 'google':
            return super()._setup_cloud_storage_provider()
        # check bucket access
        bucket_name = ICP.get_param('cloud_storage_google_bucket_name')
        # use different blob names in case the credentials are allowed to
        # overwrite an existing blob created by previous tests
        blob_name = f'0/{datetime.now(timezone.utc)}.txt'

        IrAttachment = self.env['ir.attachment']
        # check blob create permission
        upload_url = IrAttachment._generate_cloud_storage_google_signed_url(bucket_name, blob_name, method='PUT', expiration=IrAttachment._cloud_storage_upload_url_time_to_expiry)
        upload_response = requests.put(upload_url, data=b'', timeout=5)
        if upload_response.status_code != 200:
            raise ValidationError(_('The account info is not allowed to upload blobs to the bucket.\n%s', str(upload_response.text)))

        # check blob read permission
        download_url = IrAttachment._generate_cloud_storage_google_signed_url(bucket_name, blob_name, method='GET', expiration=IrAttachment._cloud_storage_download_url_time_to_expiry)
        download_response = requests.get(download_url, timeout=5)
        if download_response.status_code != 200:
            raise ValidationError(_('The account info is not allowed to download blobs from the bucket.\n%s', str(upload_response.text)))

        # CORS management is not allowed in the Google Cloud console.
        # configure CORS on bucket to allow .pdf preview and direct upload
        cors = [{
            'origin': ['*'],
            'method': ['GET', 'PUT'],
            'responseHeader': ['Content-Type'],
            'maxAgeSeconds': IrAttachment._cloud_storage_download_url_time_to_expiry,
        }]
        credential = get_cloud_storage_google_credential(self.env).with_scopes(['https://www.googleapis.com/auth/devstorage.full_control'])
        credential.refresh(Request())
        url = f"https://storage.googleapis.com/storage/v1/b/{bucket_name}?fields=cors"
        headers = {
            'Authorization': f'Bearer {credential.token}',
            'Content-Type': 'application/json'
        }
        data = json.dumps({'cors': cors})
        patch_response = requests.patch(url, data=data, headers=headers, timeout=5)
        if patch_response.status_code != 200:
            raise ValidationError(_("The account info is not allowed to set the bucket's CORS.\n%s", str(patch_response.text)))

    def _get_cloud_storage_configuration(self):
        ICP = self.env['ir.config_parameter'].sudo()
        if ICP.get_param('cloud_storage_provider') != 'google':
            return super()._get_cloud_storage_configuration()
        configuration = {
            'bucket_name': ICP.get_param('cloud_storage_google_bucket_name'),
            'account_info': ICP.get_param('cloud_storage_google_account_info'),
        }
        return configuration if all(configuration.values()) else {}

    def _check_cloud_storage_uninstallable(self):
        if self.env['ir.config_parameter'].get_param('cloud_storage_provider') != 'google':
            return super()._check_cloud_storage_uninstallable()
        cr = self.env.cr
        cr.execute(
            """
                SELECT type
                FROM ir_attachment
                WHERE type = 'cloud_storage'
                AND url LIKE 'https://storage.googleapis.com/%'
                LIMIT 1
            """
        )
        if cr.fetchone():
            raise UserError(_('Some Google attachments are in use, please migrate cloud storages before disable the provider'))