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, pipeline_manager: PipelineManager):
        # initialize web server
        if exists('webui/index.html'):
            print('production build')

            # TODO update file upload
            # files = FileProvider(app_status)

            # 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')

            # TODO update file upload
            # files = FileProvider(app_status, cors=True)

            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):
            print('connect')
            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'])

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

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

            # TODO move to project manager
            app_status['projects'].append({
                'status': 'create',
                'name': data['name'],
                'description': data['description'],
                'model': data['model']
            })

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

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

            # TODO move to project manager
            for project in app_status['projects']:
                if project['id'] == identifier:
                    # delete
                    if 'delete' in data.keys():
                        project['action'] = 'delete'
                    # update
                    else:
                        set_recursive(data, project)
                        project['action'] = 'update'

            return response()

        @self.__flask.route('/projects/<identifier>/data', methods=['POST'])
        def upload_file(identifier):
            # TODO move to project manager
            file_uuid = str(uuid1())

            # get current project path
            opened_projects = list(filter(lambda x: x['id'] == identifier, app_status['projects']))
            if len(opened_projects) == 0:
                return make_response('no open project available', 500)

            current_project = opened_projects[0]
            upload_path = path.join('projects', current_project['id'], 'data')

            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
            if 'data' not in current_project:
                current_project['data'] = []

            current_project['data'].append({
                '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):
            # get current project
            opened_projects = list(filter(lambda x: x['id'] == project_identifier, app_status['projects']))
            if len(opened_projects) == 0:
                return make_response('no open project available', 500)

            current_project = opened_projects[0]
            file_directory = path.join(getcwd(), 'projects', current_project['id'], 'data')

            print(current_project)

            # get object
            data_objects = list(filter(lambda x: x['id'] == file_identifier, current_project['data']))
            if len(data_objects) == 0:
                return make_response('data object not avilable', 500)

            target_object = data_objects[0]

            # return data
            file_name = target_object['id'] + target_object['extension']
            return send_from_directory(file_directory, file_name)

        @self.__flask.route('/projects/<project_identifier>/data/<file_identifier>', methods=['POST'])
        def run_prediction(project_identifier, file_identifier):
            pipeline_manager.run(project_identifier, file_identifier)
            return response()

        # 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)