#!/usr/bin/python
# -*- coding: utf-8 -*-

# thumbor aws extensions
# https://github.com/thumbor/thumbor-aws

# Licensed under the MIT license:
# http://www.opensource.org/licenses/mit-license
# Copyright (c) 2021 Bernardo Heynemann heynemann@gmail.com

from json import dumps, loads
from typing import Any
from urllib.parse import unquote

from thumbor import storages
from thumbor.engines import BaseEngine
from thumbor.utils import logger

from thumbor_aws.s3_client import S3Client


class Storage(storages.BaseStorage, S3Client):
    @property
    def root_path(self) -> str:
        """Defines the path prefix for all storage images in S3"""
        return self.context.config.AWS_STORAGE_ROOT_PATH.rstrip("/")

    async def put(self, path: str, file_bytes: bytes) -> str:
        content_type = BaseEngine.get_mimetype(file_bytes)
        normalized_path = self.normalize_path(path)
        logger.debug("[STORAGE] putting at %s", normalized_path)
        path = await self.upload(
            normalized_path,
            file_bytes,
            content_type,
            self.context.config.AWS_DEFAULT_LOCATION,
        )
        return path

    async def put_crypto(self, path: str) -> str:
        if not self.context.config.STORES_CRYPTO_KEY_FOR_EACH_IMAGE:
            return

        if not self.context.server.security_key:
            raise RuntimeError(
                "STORES_CRYPTO_KEY_FOR_EACH_IMAGE can't be "
                "True if no SECURITY_KEY specified"
            )

        normalized_path = self.normalize_path(path)
        crypto_path = f"{normalized_path}.txt"
        key = self.context.server.security_key.encode()
        s3_path = await self.upload(
            crypto_path,
            key,
            "application/text",
            self.context.config.AWS_DEFAULT_LOCATION,
        )

        logger.debug("Stored crypto at %s", crypto_path)

        return s3_path

    async def put_detector_data(self, path: str, data: Any) -> str:
        normalized_path = self.normalize_path(path)
        filepath = f"{normalized_path}.detectors.txt"
        details = dumps(data)
        return await self.upload(
            filepath,
            details,
            "application/json",
            self.context.config.AWS_DEFAULT_LOCATION,
        )

    async def get(self, path: str) -> bytes:
        normalized_path = self.normalize_path(path)
        status, body, _ = await self.get_data(normalized_path)
        if status != 200:
            return None

        return body

    async def get_crypto(self, path: str) -> str:
        normalized_path = self.normalize_path(path)
        crypto_path = f"{normalized_path}.txt"
        status, body, _ = await self.get_data(crypto_path)
        if status != 200:
            return None

        return body.decode("utf-8")

    async def get_detector_data(self, path: str) -> Any:
        normalized_path = self.normalize_path(path)
        detector_path = f"{normalized_path}.detectors.txt"
        status, body, _ = await self.get_data(detector_path)
        if status != 200:
            return None

        return loads(body)

    async def exists(self, path: str) -> bool:
        normalized_path = self.normalize_path(path)
        print(normalized_path)
        return await self.object_exists(normalized_path)

    async def remove(self, path: str):
        exists = await self.exists(path)
        if not exists:
            return

        async with self.get_client() as client:
            normalized_path = self.normalize_path(path)
            response = await client.delete_object(
                Bucket=self.bucket_name,
                Key=normalized_path,
            )
            status = self.get_status_code(response)
            if status >= 300:
                raise RuntimeError(
                    f"Failed to remove {normalized_path}: Status {status}"
                )

    def normalize_path(self, path: str) -> str:
        """Returns the path used for storage"""
        path = unquote(path).lstrip("/")
        return f"{self.root_path}/{path}"
