# -*- coding: utf-8 -*-
'''
This module give access to OpenAPI specifications schemas
and allows to validate specs against them.

.. versionadded:: 0.12.1
'''
from __future__ import unicode_literals

import io
import json
import pkg_resources

from collections import Mapping

from jsonschema import Draft4Validator

from flask_restplus import errors


class SchemaValidationError(errors.ValidationError):
    '''
    Raised when specification is not valid

    .. versionadded:: 0.12.1
    '''
    def __init__(self, msg, errors=None):
        super(SchemaValidationError, self).__init__(msg)
        self.errors = errors

    def __str__(self):
        msg = [self.msg]
        for error in sorted(self.errors, key=lambda e: e.path):
            path = '.'.join(error.path)
            msg.append('- {}: {}'.format(path, error.message))
            for suberror in sorted(error.context, key=lambda e: e.schema_path):
                path = '.'.join(suberror.schema_path)
                msg.append('  - {}: {}'.format(path, suberror.message))
        return '\n'.join(msg)

    __unicode__ = __str__


class LazySchema(Mapping):
    '''
    A thin wrapper around schema file lazy loading the data on first access

    :param filename str: The package relative json schema filename
    :param validator: The jsonschema validator class version

    .. versionadded:: 0.12.1
    '''
    def __init__(self, filename, validator=Draft4Validator):
        super(LazySchema, self).__init__()
        self.filename = filename
        self._schema = None
        self._validator = validator

    def _load(self):
        if not self._schema:
            filename = pkg_resources.resource_filename(__name__, self.filename)
            with io.open(filename) as infile:
                self._schema = json.load(infile)

    def __getitem__(self, key):
        self._load()
        return self._schema.__getitem__(key)

    def __iter__(self):
        self._load()
        return self._schema.__iter__()

    def __len__(self):
        self._load()
        return self._schema.__len__()

    @property
    def validator(self):
        '''The jsonschema validator to validate against'''
        return self._validator(self)


#: OpenAPI 2.0 specification schema
OAS_20 = LazySchema('oas-2.0.json')

#: Map supported OpenAPI versions to their JSON schema
VERSIONS = {
    '2.0': OAS_20,
}


def validate(data):
    '''
    Validate an OpenAPI specification.

    Supported OpenAPI versions: 2.0

    :param data dict: The specification to validate
    :returns boolean: True if the specification is valid
    :raises SchemaValidationError: when the specification is invalid
    :raises flask_restplus.errors.SpecsError: when it's not possible to determinate
                                              the schema to validate against

    .. versionadded:: 0.12.1
    '''
    if 'swagger' not in data:
        raise errors.SpecsError('Unable to determinate OpenAPI schema version')

    version = data['swagger']
    if version not in VERSIONS:
        raise errors.SpecsError('Unknown OpenAPI schema version "{}"'.format(version))

    validator = VERSIONS[version].validator

    validation_errors = list(validator.iter_errors(data))
    if validation_errors:
        raise SchemaValidationError('OpenAPI {} validation failed'.format(version),
                                    errors=validation_errors)
    return True
