__author__ = 'Michael Messmore'
__email__ = 'mike@messmore.org'
__version__ = '0.2.0'

try:
	import urlparse
except:
	from urllib import parse as urlparse

import json
import yaml
from flask import jsonify, request, Blueprint, redirect
from flask_restless import APIManager
from flask_restless.helpers import *

sqlalchemy_swagger_type = {
    'INTEGER': 'integer',
    'SMALLINT': 'int32',
    'NUMERIC': 'number',
    'DECIMAL': 'number',
    'VARCHAR': 'string',
    'TEXT': 'string',
    'DATE': 'date',
    'BOOLEAN': 'bool',
    'BLOB': 'binary',
    'BYTEA': 'binary',
    'BINARY': 'binary',
    'VARBINARY': 'binary',
    'FLOAT': 'float',
    'REAL': 'float',
    'DATETIME': 'date-time',
    'TIMESTAMP': 'date-time',
    'BIGINT': 'int64',
    'ENUM': 'string',
    'INTERVAL': 'date-time',
}


class SwagAPIManager(object):
    swagger = {
        'swagger': '2.0',
        'info': {},
        'schemes': ['http', 'https'],
        'basePath': '/',
        'consumes': ['application/json'],
        'produces': ['application/json'],
        'paths': {},
        'definitions': {}
    }

    def __init__(self, app=None, **kwargs):
        self.app = None
        self.manager = None

        if app is not None:
            self.init_app(app, **kwargs)

    def to_json(self, **kwargs):
        return json.dumps(self.swagger, **kwargs)

    def to_yaml(self, **kwargs):
        return yaml.dump(self.swagger, **kwargs)

    def __str__(self):
        return self.to_json(indent=4)

    @property
    def version(self):
        if 'version' in self.swagger['info']:
            return self.swagger['info']['version']
        return None

    @version.setter
    def version(self, value):
        self.swagger['info']['version'] = value

    @property
    def title(self):
        if 'title' in self.swagger['info']:
            return self.swagger['info']['title']
        return None

    @title.setter
    def title(self, value):
        self.swagger['info']['title'] = value

    @property
    def description(self):
        if 'description' in self.swagger['info']:
            return self.swagger['info']['description']
        return None

    @description.setter
    def description(self, value):
        self.swagger['info']['description'] = value

    def add_path(self, model, **kwargs):
        name = kwargs.get("collection_name", model.__tablename__)
        schema = model.__name__
        path = kwargs.get('url_prefix', "") + '/' + name
        id_path = "{0}/{{{1}Id}}".format(path, schema.lower())
        self.swagger['paths'][path] = {}

        for method in [m.lower() for m in kwargs.get('methods', ['GET'])]:
            if method == 'get':
                self.swagger['paths'][path][method] = {
                    'parameters': [{
                        'name': 'q',
                        'in': 'query',
                        'description': 'searchjson',
                        'type': 'string'
                    }],
                    'responses': {
                        200: {
                            'description': 'List ' + name,
                            'schema': {
                                'title': name,
                                'type': 'array',
                                'items': {'$ref': '#/definitions/' + name}
                            }
                        }

                    }
                }

                if model.__doc__:
                    self.swagger['paths'][path]['description'] = model.__doc__
                if id_path not in self.swagger['paths']:
                    self.swagger['paths'][id_path] = {}
                self.swagger['paths'][id_path][method] = {
                    'parameters': [{
                        'name': schema.lower() + 'Id',
                        'in': 'path',
                        'description': 'ID of ' + schema,
                        'required': True,
                        'type': 'integer'
                    }],
                    'responses': {
                        200: {
                            'description': 'Success ' + name,
                            'schema': {
                                'title': name,
                                '$ref': '#/definitions/' + name
                            }
                        }

                    }
                }
                if model.__doc__:
                    self.swagger['paths'][id_path]['description'] = model.__doc__
            elif method == 'delete':
                if id_path not in self.swagger['paths']:
                    self.swagger['paths'][id_path] = {}
                self.swagger['paths']["{0}/{{{1}Id}}".format(path, schema.lower())][method] = {
                    'parameters': [{
                        'name': schema.lower() + 'Id',
                        'in': 'path',
                        'description': 'ID of ' + schema,
                        'required': True,
                        'type': 'integer'
                    }],
                    'responses': {
                        200: {
                            'description': 'Success'
                        }

                    }
                }
                if model.__doc__:
                    self.swagger['paths'][id_path]['description'] = model.__doc__
            else:
                self.swagger['paths'][path][method] = {
                    'parameters': [{
                        'name': name,
                        'in': 'body',
                        'description': schema,
                        'type': "#/definitions/" + schema
                    }],
                    'responses': {
                        200: {
                            'description': 'Success'
                        }

                    }
                }
                if model.__doc__:
                    self.swagger['paths'][path]['description'] = model.__doc__

    def add_defn(self, model, **kwargs):
        name = model.__name__
        self.swagger['definitions'][name] = {
            'type': 'object',
            'properties': {}
        }
        columns = get_columns(model).keys()
        for column_name, column in get_columns(model).items():
            if column_name in kwargs.get('exclude_columns', []):
                continue
            try:
                column_type = str(column.type)
                if '(' in column_type:
                    column_type = column_type.split('(')[0]
                column_defn = sqlalchemy_swagger_type[column_type]
            except AttributeError:
                schema = get_related_model(model, column_name)
                if column_name + '_id' in columns:
                    column_defn = {'schema': {
                        '$ref': schema.__name__
                    }}
                else:
                    column_defn = {'schema': {
                        'type': 'array',
                        'items': {
                            '$ref': schema.__name__
                        }
                    }}

            if column.__doc__:
                column_defn['description'] = column.__doc__
            self.swagger['definitions'][name]['properties'][column_name] = column_defn

    def init_app(self, app, **kwargs):
        self.app = app
        self.manager = APIManager(self.app, **kwargs)

        swagger = Blueprint('swagger', __name__, static_folder='static',
                            static_url_path=self.app.static_url_path + '/swagger', )

        @swagger.route('/swagger')
        def swagger_ui():
            return redirect('/static/swagger/swagger-ui/index.html')

        @swagger.route('/swagger.json')
        def swagger_json():
            # I can only get this from a request context
            self.swagger['host'] = urlparse.urlparse(request.url_root).netloc
            return jsonify(self.swagger)

        app.register_blueprint(swagger)

    def create_api(self, model, **kwargs):
        self.manager.create_api(model, **kwargs)
        self.add_defn(model, **kwargs)
        self.add_path(model, **kwargs)

    def swagger_blueprint(self):

        return swagger