#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

import argparse

import bottle
from http import HTTPStatus  # Only from Python 3.5
from oslo_serialization import jsonutils

import dragonflow.common.exceptions as df_exceptions
from dragonflow.common import utils
from dragonflow.db import api_nb
from dragonflow.db import model_framework as mf
from dragonflow.db.models import all  # noqa


schema_file = None


def nbapi_decorator(f):
    # f(nbapi, ...) -> f(...)
    def wrapper(*args, **kwargs):
        # REVISIT(oanson) We might get away with using functools, but we
        # need nbapi to be instantiated after argument parsing.
        nbapi = api_nb.NbApi.get_instance()
        return f(nbapi, *args, **kwargs)
    return wrapper


def model_decorator(f):
    def wrapper(*args, **kwargs):
        name = kwargs['name']
        try:
            model = mf.get_model(name)
        except KeyError:
            bottle.abort(HTTPStatus.NOT_FOUND.value,
                         "Model '%s' not found" % (name,))
        return f(model, *args, **kwargs)
    return wrapper


@bottle.get('/<name>')
@model_decorator
@nbapi_decorator
def get_all(nbapi, model, name):
    instances = nbapi.get_all(model)
    result = [i.to_struct() for i in instances]
    bottle.response.content_type = 'application/json'
    return jsonutils.dumps(result)


@bottle.post('/<name>')
@model_decorator
@nbapi_decorator
def create(nbapi, model, name):
    """POST is create! Create a new instance"""
    json_data_dict = bottle.request.json
    if not json_data_dict:
        bottle.abort(HTTPStatus.PRECONDITION_FAILED.value,
                     "JSON content required")
    instance = model(**json_data_dict)
    nbapi.create(instance)
    bottle.response.status = HTTPStatus.CREATED.value


@bottle.put('/<name>')
@model_decorator
@nbapi_decorator
def update(nbapi, model, name):
    """PUT is update! Update an existing instance"""
    json_data_dict = bottle.request.json
    if not json_data_dict:
        bottle.abort(HTTPStatus.PRECONDITION_FAILED.value,
                     "JSON content required")
    instance = model(**json_data_dict)
    try:
        nbapi.update(instance)
    except df_exceptions.DBKeyNotFound:
        bottle.abort(HTTPStatus.NOT_FOUND.value,
                     "Model instance '%s/%s' not found" % (name, instance.id))
    bottle.response.status = HTTPStatus.NO_CONTENT.value


@bottle.get('/<name>/<id_>')
@model_decorator
@nbapi_decorator
def get(nbapi, model, name, id_):
    instance = nbapi.get(model(id=id_))
    if not instance:
        bottle.abort(HTTPStatus.NOT_FOUND.value,
                     "Model instance '%s/%s' not found" % (name, id_))
    bottle.response.content_type = 'application/json'
    return instance.to_json()


@bottle.delete('/<name>/<id_>')
@model_decorator
@nbapi_decorator
def delete(nbapi, model, name, id_):
    instance = nbapi.get(model(id=id_))
    if not instance:
        bottle.abort(HTTPStatus.NOT_FOUND.value,
                     "Model instance '%s/%s' not found" % (name, id_))
    nbapi.delete(instance)
    bottle.response.status = HTTPStatus.NO_CONTENT.value


@bottle.get('/schema.json')
def schema():
    if not schema_file:
        bottle.abort(HTTPStatus.NOT_FOUND.value)
    return bottle.static_file(schema_file, '/')


def main():
    parser = argparse.ArgumentParser(description='Dragonflow REST server')
    parser.add_argument('--host', type=str, default='127.0.0.1',
                        help='Host to listen on (127.0.0.1)')
    parser.add_argument('--port', type=int, default=8080,
                        help='Port to listen on (8080)')
    parser.add_argument('--config', type=str,
                        default='/etc/dragonflow/dragonflow.ini',
                        help=('Dragonflow config file '
                              '(/etc/dragonflow/dragonflow.ini)'))
    parser.add_argument('--json', type=str,
                        default=None,
                        help=('JSON schema file (None)'))
    args = parser.parse_args()
    global schema_file
    schema_file = args.json
    utils.config_init(None, [args.config])
    bottle.run(host=args.host, port=args.port)