# -*- coding: utf-8 -*-
import json

from flask import request
from flask_restful import abort, Resource, fields
from werkzeug.exceptions import NotFound

from distribution import app
from distribution.utils import validate_uri_format
from distribution.validator import Validator
from distribution.models import ValidationError


class LengthField(fields.Raw):
    def format(self, value):
        return len(value) if value else 0


class BaseResource(Resource):
    """Common base class for all schema validating resources """
    def __init__(self, *args, **kwargs):
        super(BaseResource, self).__init__(*args, **kwargs)
        self.validator = Validator(allow_unknown=True)

    def validate_schema(self, data, schema, abort_on_failure=True):
        self.validator.schema = schema
        is_valid = self.validator.validate(data)

        if not is_valid and abort_on_failure:
            abort(422, message=self.validator.errors)

        return is_valid, self.validator.errors

    def parse_request_data(self, valid_types=(dict, list)):
        try:
            request_data = json.loads(request.data)
            if not isinstance(request_data, valid_types):
                raise ValueError()
            return request_data
        except ValueError:
            abort(422, message="The incoming data is not a JSON object")


class ModelResource(BaseResource):
    model = None

    def save_object(self, obj, is_update=False):
        obj.save()

    def get_search_results(self):
        """
        By default return all objects using the search method.
        """
        return self.model.manager.search()

    def get_object(self, pk):
        try:
            obj = self.model.manager.get(pk)
            if obj.is_deleted():
                message = '{} with key {} has been deleted'
                abort(410, message=message.format(self.model.__name__, obj.key))
            return obj
        except NotFound as e:
            app.logger.exception(e)
            abort(404, message='{} with key {} not found'.format(self.model.__name__, pk))

    def get_object_by_field(self, field, value):
        try:
            obj = self.model.manager.get_by_field(field, value)
            if obj.is_deleted():
                message = '{} with key {} has been deleted'
                abort(410, message=message.format(self.model.__name__, obj.key))
            if field == 'uri' and not validate_uri_format(obj.data['uri'], value):
                message = '{} with field {} equals {} not found'
                abort(404, message=message.format(self.model.__name__, field, value))
            return obj
        except NotFound as e:
            app.logger.exception(e)
            message = '{} with field {} equals {} not found'
            abort(404, message=message.format(self.model.__name__, field, value))

    def delete_object(self, pk):
        self.model.manager.delete([pk])


class GenericDetailResource(ModelResource):
    """
    This resource represents the basic details, update and delete operations
    """
    updatable_fields = []

    def get(self, pk):
        return self.get_object(pk).clear_data

    def put(self, pk):
        request_data = self.parse_request_data(dict)
        invalid_keys = [key for key in request_data.keys() if key not in self.updatable_fields]
        if invalid_keys:
            abort(422, message='Invalid keys: {}'.format(invalid_keys))

        obj = self.get_object(pk)

        try:
            obj.update_data(request_data)
        except ValidationError as e:
            return e.message, 422

        self.save_object(obj, is_update=True)
        return obj.clear_data

    def delete(self, pk):
        obj = self.get_object(pk)
        self.delete_object(obj)
        return '', 204


class GenericListResource(ModelResource):

    enable_multipost = False
    post_blacklist_fields = []

    def get(self):
        return [obj.clear_data for obj in self.get_search_results()]

    def sanitize_request_data(self, request_data):
        request_data = dict(request_data)
        for field in self.post_blacklist_fields:
            request_data.pop(field, None)
        return request_data

    def post(self):
        request_data = self.parse_request_data((dict, list) if self.enable_multipost else dict)
        if isinstance(request_data, list):
            return self.multipost(request_data)

        request_data = self.sanitize_request_data(request_data)

        try:
            obj = self.model(request_data)
        except ValidationError as e:
            return e.message, 422

        try:
            self.model.manager.get(obj.key)
        except NotFound:
            self.save_object(obj, False)
            return obj.clear_data, 201
        else:
            msg = '{} with key {} already exists, use the PUT method to update the item'.format(
                self.model.__name__, obj.key)
            abort(409, message=msg)

    def multipost(self, object_list):
        response = {
            'success': 0,
            'failed': 0,
            'errors': []
        }
        objects = []

        for object_data in object_list:
            object_data = self.sanitize_request_data(object_data)
            try:
                objects.append(self.model(object_data))
                response['success'] += 1
            except ValidationError as e:
                response['failed'] += 1
                response['errors'].append({
                    'data': object_data,
                    'error': e.message
                })

        self.model.manager.bulk_save(objects)
        return response
