import mimetypes
from hashlib import sha256
from io import StringIO

import boto3
from django.conf import settings
from django.contrib.staticfiles.storage import ManifestFilesMixin, StaticFilesStorage
from django.core.cache import cache
from django.core.files.base import ContentFile
from django.core.files.storage import Storage
from django.utils.deconstruct import deconstructible
from pipeline.storage import PipelineMixin


class PipelineManifestStorage(PipelineMixin, ManifestFilesMixin, StaticFilesStorage):
    pass


class S3HeadCacheMixin:
    def get_cache_key(self, name):
        return "storages_meta__{}".format(sha256(name.encode()).hexdigest())

    def get_head(self, name, use_cache=True):
        if use_cache:
            data = cache.get(self.get_cache_key(name))
            if data:
                return data
        data = self.s3client.head_object(Bucket=self.bucket, Key=name)
        cache.set(self.get_cache_key(name), data)
        return data

    def reset_head(self, name):
        cache.delete(self.get_cache_key(name))


@deconstructible
class S3MediaStorage(Storage, S3HeadCacheMixin):
    def __init__(self):
        self.s3client = boto3.client("s3", region_name=settings.AWS_REGION)
        self.bucket = settings.MEDIA_BUCKET

    def delete(self, name):
        self.s3client.delete_object(Bucket=self.bucket, Key=name)

    def exists(self, name):
        try:
            self.get_head(name, use_cache=False)
            return True
        except Exception:
            return False

    def size(self, name):
        return self.get_head(name)["ContentLength"]

    def url(self, name):
        return "https://s3-{aws_region}.amazonaws.com/{bucket}/{key}".format(
            aws_region=settings.AWS_REGION, bucket=self.bucket, key=name
        )

    def get_modified_time(self, name):
        return self.get_head(name)["LastModified"]

    def _open(self, name, mode="rb"):
        response = self.s3client.get_object(Bucket=self.bucket, Key=name)
        return ContentFile(response["Body"].read())

    def _save(self, name, content):
        if isinstance(content.file, StringIO):
            content = ContentFile(content.read().encode("utf-8"), content.name)
        self.s3client.put_object(
            Bucket=self.bucket,
            Key=name,
            Body=content,
            ACL="public-read",
            ContentType=mimetypes.guess_type(name)[0] or "binary/octet-stream",
        )
        self.reset_head(name)
        return name
