# -*- coding: utf-8 -*-
import unittest
import os
import shutil
import json
import uuid
from urllib.parse import parse_qs
from depot.middleware import _FileIter
from depot.manager import DepotManager
from webtest import TestApp
from depot._compat import u_, unquote, PY2


FILE_CONTENT = b'HELLO WORLD'


class RootController:
    UPLOADED_FILES = []

    def index(self):
        return dict(files=self.UPLOADED_FILES)

    def clear_files(self):
        self.UPLOADED_FILES = []
        return dict(files=self.UPLOADED_FILES)

    def depotskipped(self):
        return dict(ok=True)

    def depot(self):
        # this should never be called
        return dict(ok=False)

    def create_file(self, lang=('en',)):
        lang = lang[0]
        fname = {'en': 'hello.txt',
                 'ru': u_('Крупный'),
                 'it': u_('àèìòù')}.get(lang, 'unknown')

        self.UPLOADED_FILES += [DepotManager.get().create(FILE_CONTENT,
                                                          filename=fname)]
        return dict(files=self.UPLOADED_FILES,
                    uploaded_to=DepotManager.get_default(),
                    last=self.UPLOADED_FILES[-1])


class WSGIApplication:
    def __init__(self, controller):
        self._controller = controller

    def __call__(self, env, start_response):
        path = env['PATH_INFO'].strip('/') or 'index'
        try:
            action = getattr(self._controller, path)
        except AttributeError:
            start_response('404 Not Found', [('Content-Type','text/plain')])
            return [b'Not Found']
        if env['REQUEST_METHOD'] == 'POST':
            query_string = env['wsgi.input'].read().decode('utf-8')
        else:
            query_string = env['QUERY_STRING']
        resp = json.dumps(action(**parse_qs(query_string))).encode('utf-8')
        start_response('200 OK', [('Content-Type','application/json')])
        return [resp]


class BaseWSGITests(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.wsgi_app = WSGIApplication(RootController())

    def make_app(self, **options):
        wsgi_app = DepotManager.make_middleware(self.wsgi_app, **options)
        return TestApp(wsgi_app)



class TestWSGIMiddleware(BaseWSGITests):
    def setUp(self):
        DepotManager._clear()
        DepotManager.configure('default', {'depot.storage_path': './lfs'})

    def tearDown(cls):
        shutil.rmtree('./lfs', ignore_errors=True)

    def test_invalid_mountpoint(self):
        try:
            DepotManager.make_middleware(self.wsgi_app, mountpoint='hello')
        except ValueError as err:
            assert 'mountpoint must be an absolute path' in str(err)
        else:
            assert False, 'Should have raised ValueError'
    
    def test_subdirectory_mountpoint(self):
        wsgi_app = DepotManager.make_middleware(self.wsgi_app, mountpoint='/hello/lfs')
        app = TestApp(wsgi_app)
        new_file = app.post('/create_file').json
        uploaded_file = app.get(DepotManager.url_for('%(uploaded_to)s/%(last)s' % new_file))
        assert uploaded_file.body == FILE_CONTENT

    def test_serving_files(self):
        app = self.make_app()
        new_file = app.post('/create_file').json

        uploaded_file = app.get(DepotManager.url_for('%(uploaded_to)s/%(last)s' % new_file))
        assert uploaded_file.body == FILE_CONTENT

    def test_headers_are_there(self):
        app = self.make_app()
        new_file = app.post('/create_file').json

        uploaded_file = app.get(DepotManager.url_for('%(uploaded_to)s/%(last)s' % new_file))
        assert uploaded_file.headers['Content-Type'] == 'text/plain'
        assert 'ETag' in uploaded_file.headers
        assert 'Last-Modified' in uploaded_file.headers
        assert 'Expires' in uploaded_file.headers

    def test_caching_unmodified(self):
        app = self.make_app()
        new_file = app.post('/create_file').json

        uploaded_file = app.get(DepotManager.url_for('%(uploaded_to)s/%(last)s' % new_file))
        last_modified = uploaded_file.headers['Last-Modified']

        unmodified_file = app.get('/depot/default/%(last)s' % new_file,
                                  headers=[('If-Modified-Since', last_modified)],
                                  status=304)
        assert 'Not Modified' in unmodified_file.status, unmodified_file

    def test_caching_etag(self):
        app = self.make_app()
        new_file = app.post('/create_file').json

        uploaded_file = app.get(DepotManager.url_for('%(uploaded_to)s/%(last)s' % new_file))
        etag = uploaded_file.headers['ETag']

        unmodified_file = app.get('/depot/default/%(last)s' % new_file,
                                  headers=[('If-None-Match', etag)],
                                  status=304)
        assert 'Not Modified' in unmodified_file.status, unmodified_file

    def test_post_is_forwarded_to_app(self):
        app = self.make_app()
        new_file = app.post('/create_file').json

        file_path = DepotManager.url_for('%(uploaded_to)s/%(last)s' % new_file)
        uploaded_file = app.post(file_path, status=404)
        assert 'Not Found' in uploaded_file.status, uploaded_file

    def test_forwards_to_app(self):
        app = self.make_app()
        new_file = app.post('/create_file').json

        files = app.get('/').json
        assert new_file['last'] in files['files'], (new_file, files)

    def test_forwards_to_app_begins_with_endpoint(self):
        app = self.make_app()

        resp = app.get('/depotskipped').json
        assert resp['ok'] == True

    def test_404_on_nofile(self):
        app = self.make_app()

        missing = app.get('/depot', status=404)
        assert 'Not Found' in missing.status

    def test_404_on_missing_file(self):
        app = self.make_app()
        missing = app.get('/depot/default/hello', status=404)
        assert 'Not Found' in missing.status
        missing = app.get('/depot/nodepot', status=404)
        assert 'Not Found' in missing.status
        missing = app.get('/depot/nodepot/hello', status=404)
        assert 'Not Found' in missing.status

    def test_invalid_unmodified_header(self):
        app = self.make_app()
        new_file = app.post('/create_file').json

        uploaded_file = app.get(DepotManager.url_for('%(uploaded_to)s/%(last)s' % new_file))

        unmodified_file = app.get('/depot/default/%(last)s' % new_file,
                                  headers=[('If-Modified-Since', 'HELLO WORLD')],
                                  status=400)
        assert 'Bad Request' in unmodified_file.status, unmodified_file

    def test_serving_files_with_wsgifilewrapper(self):
        app = self.make_app(replace_wsgi_filewrapper=True)
        new_file = app.post('/create_file').json

        uploaded_file = app.get(DepotManager.url_for('%(uploaded_to)s/%(last)s' % new_file))
        assert uploaded_file.body == FILE_CONTENT
        assert uploaded_file.request.environ['wsgi.file_wrapper'] is _FileIter

    def test_serving_files_content_disposition(self):
        app = self.make_app()
        new_file = app.post('/create_file', params={'lang': 'ru'}).json

        uploaded_file = app.get(DepotManager.url_for('%(uploaded_to)s/%(last)s' % new_file))
        content_disposition = uploaded_file.headers['Content-Disposition']

        if PY2:
            assert content_disposition == "inline;filename=\"unknown\";filename*=utf-8''%D0%9A%D1%80%D1%83%D0%BF%D0%BD%D1%8B%D0%B9", content_disposition
        else:
            assert content_disposition == "inline;filename=\"Krupnyy\";filename*=utf-8''%D0%9A%D1%80%D1%83%D0%BF%D0%BD%D1%8B%D0%B9", content_disposition


        new_file = app.post('/create_file', params={'lang': 'it'}).json
        uploaded_file = app.get(DepotManager.url_for('%(uploaded_to)s/%(last)s' % new_file))
        content_disposition = uploaded_file.headers['Content-Disposition']
        _, asciiname, uniname = content_disposition.split(';')
        assert asciiname == 'filename="aeiou"', asciiname
        assert u_(unquote(uniname[17:])) == u_('àèìòù'), unquote(uniname[17:])


class TestS3TestWSGIMiddleware(BaseWSGITests):
    def setUp(self):
        DepotManager._clear()

        try:
            global S3Storage
            from depot.io.awss3 import S3Storage
        except ImportError:
            self.skipTest('Boto not installed')

        env = os.environ
        access_key_id = env.get('AWS_ACCESS_KEY_ID')
        secret_access_key = env.get('AWS_SECRET_ACCESS_KEY')
        if access_key_id is None or secret_access_key is None:
            self.skipTest('Amazon S3 credentials not available')

        PID = os.getpid()
        NODE = str(uuid.uuid1()).rsplit('-', 1)[-1]  # Travis runs multiple tests concurrently
        default_bucket_name = 'filedepot-%s' % (access_key_id.lower(), )
        cred = (access_key_id, secret_access_key)
        bucket_name = 'filedepot-testfs-%s-%s-%s' % (access_key_id.lower(), NODE, PID)

        DepotManager.configure('awss3', {'depot.backend': 'depot.io.awss3.S3Storage',
                                         'depot.access_key_id': access_key_id,
                                         'depot.secret_access_key': secret_access_key,
                                         'depot.bucket': bucket_name})
        DepotManager.set_default('awss3')

    def tearDown(self):
        store = DepotManager.get('awss3')
        if not store._conn.lookup(store._bucket_driver.bucket.name):
            return
        
        keys = [key.name for key in store._bucket_driver.bucket]
        if keys:
            store._bucket_driver.bucket.delete_keys(keys)

        try:
            store._conn.delete_bucket(store._bucket_driver.bucket.name)
            while store._conn.lookup(store._bucket_driver.bucket.name):
                # Wait for bucket to be deleted, to avoid flaky tests...
                time.sleep(0.5)
        except:
            pass
    
    def test_public_url_gets_redirect(self):
        app = self.make_app()
        new_file = app.post('/create_file').json

        file_path = DepotManager.url_for('%(uploaded_to)s/%(last)s' % new_file)
        uploaded_file = app.get(file_path, status=301)
        location = uploaded_file.headers['Location']
        assert 'https://filedepot-testfs-' in location
        assert 's3.amazonaws.com' in location
