# Copyright 2020 ACSONE SA/NV
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

import marshmallow
from apispec.ext.marshmallow.openapi import OpenAPIConverter
from marshmallow.exceptions import ValidationError

from odoo import _
from odoo.exceptions import UserError

from odoo.addons.base_rest import restapi


class Datamodel(restapi.RestMethodParam):
    def __init__(self, name, is_list=False, partial=None):
        """

        :param name: The datamodel name
        :param is_list: Should be set to True if params is a collection so that
                        the object will be de/serialized from/to a list
        :param partial: Whether to ignore missing fields and not require
            any fields declared. Propagates down to ``Nested`` fields as well. If
            its value is an iterable, only missing fields listed in that iterable
            will be ignored. Use dot delimiters to specify nested fields.
        """
        self._name = name
        self._is_list = is_list
        self._partial = partial

    def from_params(self, service, params):
        ModelClass = service.env.datamodels[self._name]
        try:
            return ModelClass.load(
                params,
                many=self._is_list,
                unknown=marshmallow.EXCLUDE,
                partial=self._partial,
            )
        except ValidationError as ve:
            raise UserError(_("BadRequest %s") % ve.messages) from ve

    def to_response(self, service, result):
        ModelClass = service.env.datamodels[self._name]
        if self._is_list:
            json = [i.dump() for i in result]
        else:
            json = result.dump()
        errors = ModelClass.validate(
            json, many=self._is_list, unknown=marshmallow.EXCLUDE
        )
        if errors:
            raise SystemError(_("Invalid Response %s") % errors)
        return json

    def to_openapi_query_parameters(self, service, spec):
        converter = self._get_converter()
        schema = self._get_schema(service)
        return converter.schema2parameters(schema, location="query")

    # TODO, we should probably get the spec as parameters. That should
    # allows to add the definition of a schema only once into the specs
    # and use a reference to the schema into the parameters
    def to_openapi_requestbody(self, service, spec):
        return {
            "content": {
                "application/json": {
                    "schema": self.to_json_schema(service, spec, "input")
                }
            }
        }

    def to_openapi_responses(self, service, spec):
        return {
            "200": {
                "content": {
                    "application/json": {
                        "schema": self.to_json_schema(service, spec, "output")
                    }
                }
            }
        }

    def to_json_schema(self, service, spec, direction):
        converter = self._get_converter()
        schema = self._get_schema(service)
        return converter.resolve_nested_schema(schema)

    def _get_schema(self, service):
        return service.env.datamodels[self._name].get_schema(many=self._is_list)

    def _get_converter(self):
        return OpenAPIConverter("3.0", self._schema_name_resolver, None)

    def _schema_name_resolver(self, schema):
        # name resolver used by the OpenapiConverter. always return None
        # to force nested schema definition
        return None


restapi.Datamodel = Datamodel
