from glob import glob
from os import path, mkdir, getcwd
from os.path import exists
from time import time
from uuid import uuid1

import eventlet
import socketio
from flask import Flask, make_response, send_from_directory, request
from werkzeug import formparser

from pycs.ApplicationStatus import ApplicationStatus
from pycs.pipeline.PipelineManager import PipelineManager
from pycs.util.GenericWrapper import GenericWrapper
from pycs.util.ProgressFileWriter import ProgressFileWriter
from pycs.util.RecursiveDictionary import set_recursive


class WebServer:
    def __init__(self, app_status: ApplicationStatus):
        # initialize web server
        if exists('webui/index.html'):
            print('production build')

            # find svg icons and add them as separate static files to
            # set their correct mime type / content_type
            static_files = {'/': 'webui/'}
            for svg_path in glob('webui/img/*.svg'):
                svg_path = svg_path.replace('\\', '/')
                static_files[svg_path[5:]] = {'content_type': 'image/svg+xml', 'filename': svg_path}

            self.__sio = socketio.Server()
            self.__flask = Flask(__name__)
            self.__app = socketio.WSGIApp(self.__sio, self.__flask, static_files=static_files)

            def response():
                rsp = make_response()
                return rsp
        else:
            print('development build')

            self.__sio = socketio.Server(cors_allowed_origins='*')
            self.__flask = Flask(__name__)
            self.__app = socketio.WSGIApp(self.__sio, self.__flask)

            def response():
                rsp = make_response()
                rsp.headers['Access-Control-Allow-Origin'] = '*'
                return rsp

        # save every change in application status and send it to the client
        app_status.subscribe(self.__update_application_status, immediate=True)

        # define events
        @self.__sio.event
        def connect(id, msg):
            self.__sio.emit('app_status', self.__status, to=id)

        @self.__flask.route('/settings', methods=['POST'])
        def edit_settings():
            data = request.get_json(force=True)
            set_recursive(data, app_status['settings'])

            return response()

        @self.__flask.route('/projects', methods=['POST'])
        def create_project():
            data = request.get_json(force=True)
            app_status['projects'].create_project(data['name'], data['description'], data['model'])

            return response()

        @self.__flask.route('/projects/<identifier>', methods=['POST'])
        def edit_project(identifier):
            data = request.get_json(force=True)

            if 'delete' in data.keys():
                app_status['projects'].delete_project(identifier)
            elif 'predict' in data.keys():
                app_status['projects'].predict(identifier, data['predict'])
            else:
                app_status['projects'].update_project(identifier, data)

            return response()

        @self.__flask.route('/projects/<identifier>/data', methods=['POST'])
        def upload_file(identifier):
            # abort if project id is not valid
            if identifier not in app_status['projects'].keys():
                # TODO return 404
                return make_response('project does not exist', 500)

            # get project and upload path
            project = app_status['projects'][identifier]
            upload_path, file_uuid = project.new_media_file_path()

            # prepare wrapper objects
            job = GenericWrapper()
            file_name = GenericWrapper()
            file_extension = GenericWrapper()
            file_size = GenericWrapper(0)

            # save upload to file
            def custom_stream_factory(total_content_length, filename, content_type, content_length=None):
                file_name.value, file_extension.value = path.splitext(filename)
                file_path = path.join(upload_path, f'{file_uuid}{file_extension.value}')

                # add job to app status
                job.value = app_status['jobs'].append({
                    'id': file_uuid,
                    'type': 'upload',
                    'progress': 0,
                    'filename': filename,
                    'created': int(time()),
                    'finished': None
                })

                # create upload path if not exists
                if not path.exists(upload_path):
                    mkdir(upload_path)

                # define progress callback
                length = content_length if content_length is not None and content_length != 0 else total_content_length

                def callback(progress):
                    file_size.value += progress
                    relative = progress / length

                    if relative - job.value['progress'] > 0.02:
                        job.value['progress'] = relative

                # open file handler
                return ProgressFileWriter(file_path, 'wb', callback)

            stream, form, files = formparser.parse_form_data(request.environ, stream_factory=custom_stream_factory)

            if 'file' not in files.keys():
                return make_response('no file uploaded', 500)

            # set progress to 1 after upload is done
            job = job.value

            job['progress'] = 1
            job['finished'] = int(time())

            # add to project files
            project.add_media_file({
                'id': file_uuid,
                'name': file_name.value,
                'extension': file_extension.value,
                'size': file_size.value,
                'created': job['created']
            })

            # return default success response
            return response()

        @self.__flask.route('/projects/<project_identifier>/data/<file_identifier>', methods=['GET'])
        def get_file(project_identifier, file_identifier):
            # abort if project id is not valid
            if project_identifier not in app_status['projects'].keys():
                return make_response('project does not exist', 500)

            project = app_status['projects'][project_identifier]

            # abort if file id is not valid
            if file_identifier not in project['data'].keys():
                return make_response('file does not exist', 500)

            target_object = project['data'][file_identifier]

            # construct directory and filename
            file_directory = path.join(getcwd(), 'projects', project['id'], 'data')
            file_name = target_object['id'] + target_object['extension']

            # return data
            return send_from_directory(file_directory, file_name)

        # finally start web server
        host = app_status['settings']['frontend']['host']
        port = app_status['settings']['frontend']['port']

        eventlet.wsgi.server(eventlet.listen((host, port)), self.__app)

    def __update_application_status(self, status):
        self.__status = status
        self.__sio.emit('app_status', status)