# -*- coding: utf-8 -*-

import os
import collections
import peewee
import werkzeug
import jinja2
import flask

#import poobrains
from poobrains import app
import poobrains.helpers
import poobrains.storage
import poobrains.form
import poobrains.auth

class UploadForm(poobrains.auth.AutoForm):
   
    upload = poobrains.form.fields.File()
    filename = poobrains.form.fields.Value()
    force = poobrains.form.fields.Checkbox(label='Rename file if exists')


    def __init__(self, *args, **kwargs):

        super(UploadForm, self).__init__(*args, **kwargs)

        if self.mode == 'add':
            self.fields['upload'].required = True

        del self.controls['preview']


    def bind(self, values, files):

        super(UploadForm, self).bind(values, files)
        
        upload_file = self.fields['upload'].value
        if upload_file:
            self.fields['filename'].value = werkzeug.utils.secure_filename(upload_file.filename)
        else:
            self.fields['filename'].value = ''

        extension = self.fields['filename'].value.split('.')[-1]

        if  not '*' in self.instance.extension_whitelist and\
        self.fields['filename'].value != '' and\
        not extension in self.instance.extension_whitelist:
            raise poobrains.errors.CompoundError(
                [poobrains.errors.ValidationError(
                    'Invalid file extension: %s. Try one of %s.' % (extension, list(self.instance.extension_whitelist))
                )]
            )


    def process(self, submit):

        force = self.fields['force'].value
        upload_file = self.fields['upload'].value
        filename = self.fields['filename'].value

        if filename is not '':

            file_path = os.path.join(self.instance.path, filename)

            if os.path.exists(file_path) and self.mode == 'add':

                if force:
                    extension = self.fields['filename'].value.split('.')[-1]
                    filename = '%s.%s' % (self.fields['name'].value, extension) # name field autogenerated and filled by AutoForm
                    self.fields['filename'].value = filename # needed so that super call fills in the right name
                    file_path = os.path.join(self.instance.path, filename)

                else:
                    try:
                        conflict = self.model.select().where(self.model.filename == filename).get()
                        flask.flash(jinja2.Markup(u'A file already exists at the same place on the filesystem. See if <a href="%s">%s</a> is the same file.' % (conflict.url('raw'), conflict.name)))

                    except self.model.DoesNotExist:
                        
                        flask.flash(u'A file unknown to this site already exists at the same place on the filesystem. /dev/null has been informed.')
                        app.logger.error('Unknown file clutter: %s/upload/%s/%s' % (app.root_path, self.instance.path, filename))

                    return self

            try:
                upload_file.save(file_path)

                if self.mode == 'edit':

                    try:
                        os.remove(self.instance.file_path)
                    except OSError as e:
                        app.logger.debug(u"Could not delete old file '%s' for %s '%s': " % (self.filename, self.__class__.__name__, self.name))

            except IOError as e:

                flask.flash(u"Failed saving file '%s'." % filename, 'error')
                app.logger.error(u"Failed saving file: %s\n%s: %s / %s / %s" % (filename, type(e).__name__, str(e), e.strerror, e.filename))
                return self # stop handling, show form within same request

        elif self.mode == 'edit':

            self.fields['filename'].value = self.instance.filename # to avoid super call removing the current filename if no new file was supplied

        try:
            return super(UploadForm, self).process(submit, exceptions=True)

        except peewee.DatabaseError as e:
            flask.flash(u"Could not save file metadata for file '%s'. Deleting file, sorry if it was big. ¯\_(ツ)_/¯" % filename)
            flask.flash(str(e))
            app.logger.error(u"Failed saving file metadata: %s\n%s: %s" % (filename, type(e).__name__, str(e)))
            os.remove(file_path)

        return self


class File(poobrains.auth.NamedOwned):

    file_size = None
    form_add = UploadForm
    form_edit = UploadForm
    path = None
    extension_whitelist = set(['*'])

    filename = poobrains.storage.fields.CharField()

    class Meta:

        modes = collections.OrderedDict([
            ('add', 'create'),
            ('teaser', 'read'),
            ('full', 'read'),
            ('raw', 'read'),
            ('inline', 'read'),
            ('edit', 'update'),
            ('delete', 'delete')
        ])

    def __init__(self, *args, **kwargs):

        super(File, self).__init__(*args, **kwargs)
        self.path = os.path.join(app.site_path, 'upload', self.__class__.__name__.lower())

    def __setattr__(self, name, value):

        if name == 'filename' and isinstance(value, str):
            value = werkzeug.utils.secure_filename(value)

        return super(File, self).__setattr__(name, value)


    @property
    def file_size(self):
        return os.path.getsize(self.file_path)


    @poobrains.auth.protected
    @poobrains.helpers.themed
    def view(self, mode=None, handle=None):

        if mode == 'raw':
            
            response = flask.send_from_directory(self.path, self.filename)
            response.headers['Content-Disposition'] = u'filename="%s"' % self.filename
            
            # Disable "public" mode caching downstream (nginx, varnish) in order to hopefully not leak restricted content
            response.cache_control.public = False
            response.cache_control.private = True
            response.cache_control.max_age = app.config['CACHE_LONG']

            return response
        
        else:
            return poobrains.helpers.ThemedPassthrough(super(File, self).view(mode=mode, handle=handle)) # FIXME: themed and protected called twice


    @property
    def file_path(self):
        return os.path.join(self.path, self.filename)

    
    @property
    def file_size_pretty(self):
        size = self.file_size if self.file_size != None else 0

        return poobrains.helpers.pretty_bytes(size) 


    def delete_instance(self, *args, **kwargs):

        r = super(File, self).delete_instance(*args, **kwargs)

        if r:
            try:
                os.remove(os.path.join(self.path, self.filename))
            except OSError as e:
                flask.flash(u"Could not delete %s '%s'." % (self.__class__.__name__, self.filename))

        return r


class Image(File):
    extension_whitelist = set(['gif', 'jpg', 'png', 'svg'])

class Audio(File):
    extension_whitelist = set(['mp3', 'flac', 'ogg', 'wav'])

class Video(File):
    extension_whitelist = set(['mp4', 'webm', 'ogg'])

# setup upload routes for "raw" mode
#FIXME: Won't this mean subclasses in projects using poobrains won't have this?
for cls in set([File]).union(File.class_children()):
    rule = os.path.join("/upload/", cls.__name__.lower(), '<handle>')
    app.site.add_view(cls, rule, mode='raw')
