# -*- coding: utf-8 -*-
import json
import boto3
import binascii

from hashlib import md5
from base64 import b64decode, b64encode
from flask_mongoengine import DynamicDocument
from mongoengine import signals
from mongoengine.fields import StringField
from mongoengine import ValidationError
from filetype.types.archive import Gz
from filetype.types.image import Jpeg, Png, Gif, Tiff

MAX_BYTES = 200 * 1024
BUCKET = "mpcontribs-attachments"
S3_ATTACHMENTS_URL = f"https://{BUCKET}.s3.amazonaws.com"
SUPPORTED_FILETYPES = (Gz, Jpeg, Png, Gif, Tiff)
SUPPORTED_MIMES = [t().mime for t in SUPPORTED_FILETYPES]

s3_client = boto3.client("s3")


class Attachments(DynamicDocument):
    name = StringField(required=True, help_text="file name")
    md5 = StringField(regex=r"^[a-z0-9]{32}$", unique=True, help_text="md5 sum")
    mime = StringField(required=True, choices=SUPPORTED_MIMES, help_text="attachment mime type")
    content = StringField(required=True, help_text="base64-encoded attachment content")
    meta = {"collection": "attachments", "indexes": ["name", "mime", "md5"]}

    @classmethod
    def post_init(cls, sender, document, **kwargs):
        if document.id and document.content:
            if not document.md5:
                # TODO AttributeError: _changed_fields
                # document.reload("md5")
                raise ValueError("Please also request md5 field to retrieve attachment content!")

            retr = s3_client.get_object(Bucket=BUCKET, Key=document.md5)
            document.content = b64encode(retr["Body"].read()).decode("utf-8")

    @classmethod
    def pre_delete(cls, sender, document, **kwargs):
        s3_client.delete_object(Bucket=BUCKET, Key=document.md5)

    @classmethod
    def pre_save_post_validation(cls, sender, document, **kwargs):
        from mpcontribs.api.attachments.views import AttachmentsResource

        # b64 decode
        try:
            content = b64decode(document.content, validate=True)
        except binascii.Error:
            raise ValidationError(f"Attachment {document.name} not base64 encoded!")

        # check size
        size = len(content)

        if size > MAX_BYTES:
            raise ValidationError(
                f"Attachment {document.name} too large ({size} > {MAX_BYTES})!"
            )

        # md5
        resource = AttachmentsResource()
        d = resource.serialize(document, fields=["mime", "content"])
        s = json.dumps(d, sort_keys=True).encode("utf-8")
        document.md5 = md5(s).hexdigest()

        # save to S3 and unset content
        s3_client.put_object(
            Bucket=BUCKET,
            Key=document.md5,
            ContentType=document.mime,
            Body=content,
        )
        document.content = size  # set to something useful to distinguish in post_init


signals.post_init.connect(Attachments.post_init, sender=Attachments)
signals.pre_delete.connect(Attachments.pre_delete, sender=Attachments)
signals.pre_save_post_validation.connect(Attachments.pre_save_post_validation, sender=Attachments)

@@ -377,16 +377,61 @@
 , Tiff%0A%0A
+from mpcontribs.api.config import API_CNAME%0A%0A
 MAX_BYTE
@@ -678,16 +678,68 @@
 %22s3%22)%0A%0A%0A
+def get_key(md5):%0A    return f%22%7BAPI_CNAME%7D/%7Bmd5%7D%22%0A%0A%0A
 class At
@@ -1564,32 +1564,40 @@
 ket=BUCKET, Key=
+get_key(
 document.md5)%0A  
@@ -1593,16 +1593,17 @@
 ent.md5)
+)
 %0A       
@@ -1791,24 +1791,32 @@
 BUCKET, Key=
+get_key(
 document.md5
@@ -1816,16 +1816,17 @@
 ent.md5)
+)
 %0A%0A    @c
@@ -2739,16 +2739,24 @@
     Key=
+get_key(
 document
@@ -2759,16 +2759,17 @@
 ment.md5
+)
 ,%0A      

use API_CNAME for attachment keys in S3