import argparse
import traceback
from flask import Flask, request, redirect, Response, make_response

from bemani.protocol import EAmuseProtocol
from bemani.backend import Dispatch, UnrecognizedPCBIDException
from bemani.data import Config, Data
from bemani.utils.config import load_config as base_load_config, register_games as base_register_games


app = Flask(__name__)
config = Config()


@app.route('/', defaults={'path': ''}, methods=['GET'])
@app.route('/<path:path>', methods=['GET'])
def receive_healthcheck(path: str) -> Response:
    global config
    redirect_uri = config.server.redirect
    if redirect_uri is None:
        # Return a standard status OKAY message.
        return Response("Services OK.")
    else:
        # Redirect to the configured location.
        return redirect(redirect_uri, code=308)  # type: ignore


@app.route('/', defaults={'path': ''}, methods=['POST'])
@app.route('/<path:path>', methods=['POST'])
def receive_request(path: str) -> Response:
    proto = EAmuseProtocol()
    remote_address = request.headers.get('x-remote-address', None)
    compression = request.headers.get('x-compress', None)
    encryption = request.headers.get('x-eamuse-info', None)
    req = proto.decode(
        compression,
        encryption,
        request.data,
    )

    if req is None:
        # Nothing to do here
        return Response("Unrecognized packet!", 500)
    if req.name in {'soapenv:Envelope', 'soap:Envelope', 'methodCall'}:
        # We get lots of spam from random bots trying to SOAP
        # us up, so ignore this shit.
        return Response("Unrecognized packet!", 500)

    # Create and format config
    global config
    requestconfig = config.clone()
    requestconfig['client'] = {
        'address': remote_address or request.remote_addr,
    }

    dataprovider = Data(requestconfig)
    try:
        dispatch = Dispatch(requestconfig, dataprovider, True)
        resp = dispatch.handle(req)

        if resp is None:
            # Nothing to do here
            dataprovider.local.network.put_event(
                'unhandled_packet',
                {
                    'request': str(req),
                },
            )
            return Response("No response generated", 404)

        data = proto.encode(
            compression,
            encryption,
            resp,
        )

        response = make_response(data)

        # Some old clients are case-sensitive, even though http spec says these
        # shouldn't matter, so capitalize correctly.
        if compression:
            response.headers['X-Compress'] = compression
        else:
            response.headers['X-Compress'] = 'none'
        if encryption:
            response.headers['X-Eamuse-Info'] = encryption

        return response
    except UnrecognizedPCBIDException as e:
        dataprovider.local.network.put_event(
            'unauthorized_pcbid',
            {
                'pcbid': e.pcbid,
                'model': e.model,
                'ip': e.ip,
            },
        )
        return Response("Unauthorized client", 403)
    except Exception:
        stack = traceback.format_exc()
        print(stack)
        dataprovider.local.network.put_event(
            'exception',
            {
                'service': 'xrpc',
                'request': str(req),
                'traceback': stack,
            },
        )
        return Response("Crash when handling packet!", 500)
    finally:
        dataprovider.close()


def register_games() -> None:
    global config
    base_register_games(config)


def load_config(filename: str) -> None:
    global config
    base_load_config(filename, config)


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="A backend services provider for eAmusement games")
    parser.add_argument("-p", "--port", help="Port to listen on. Defaults to 80", type=int, default=80)
    parser.add_argument("-c", "--config", help="Core configuration. Defaults to server.yaml", type=str, default="server.yaml")
    parser.add_argument("-r", "--profile", help="Turn on profiling for services, writing CProfile data to the currenct directory", action="store_true")
    parser.add_argument("-o", "--read-only", action="store_true", help="Force the database into read-only mode.")
    args = parser.parse_args()

    # Set up global configuration, overriding config port for convenience in debugging.
    load_config(args.config)
    config['server']['port'] = args.port
    if args.read_only:
        config['database']['read_only'] = True

    # Register game handlers
    register_games()

    if args.profile:
        from werkzeug.contrib.profiler import ProfilerMiddleware
        app.wsgi_app = ProfilerMiddleware(app.wsgi_app, profile_dir='.')  # type: ignore

    # Run the app
    app.run(host='0.0.0.0', port=args.port, debug=True)
