#    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.
"""Inventory handlers for Placement API."""

import copy

from oslo_db import exception as db_exc
from oslo_serialization import jsonutils
from oslo_utils import encodeutils
import webob

from nova.api.openstack.placement import microversion
from nova.api.openstack.placement import util
from nova.api.openstack.placement import wsgi_wrapper
from nova import db
from nova import exception
from nova.i18n import _
from nova import objects

RESOURCE_CLASS_IDENTIFIER = "^[A-Z0-9_]+$"
BASE_INVENTORY_SCHEMA = {
    "type": "object",
    "properties": {
        "resource_provider_generation": {
            "type": "integer"
        },
        "total": {
            "type": "integer",
            "maximum": db.MAX_INT,
            "minimum": 1,
        },
        "reserved": {
            "type": "integer",
            "maximum": db.MAX_INT,
            "minimum": 0,
        },
        "min_unit": {
            "type": "integer",
            "maximum": db.MAX_INT,
            "minimum": 1
        },
        "max_unit": {
            "type": "integer",
            "maximum": db.MAX_INT,
            "minimum": 1
        },
        "step_size": {
            "type": "integer",
            "maximum": db.MAX_INT,
            "minimum": 1
        },
        "allocation_ratio": {
            "type": "number",
            "maximum": db.SQL_SP_FLOAT_MAX
        },
    },
    "required": [
        "total",
        "resource_provider_generation"
    ],
    "additionalProperties": False
}
POST_INVENTORY_SCHEMA = copy.deepcopy(BASE_INVENTORY_SCHEMA)
POST_INVENTORY_SCHEMA['properties']['resource_class'] = {
    "type": "string",
    "pattern": RESOURCE_CLASS_IDENTIFIER,
}
POST_INVENTORY_SCHEMA['required'].append('resource_class')
POST_INVENTORY_SCHEMA['required'].remove('resource_provider_generation')
PUT_INVENTORY_RECORD_SCHEMA = copy.deepcopy(BASE_INVENTORY_SCHEMA)
PUT_INVENTORY_RECORD_SCHEMA['required'].remove('resource_provider_generation')
PUT_INVENTORY_SCHEMA = {
    "type": "object",
    "properties": {
        "resource_provider_generation": {
            "type": "integer"
        },
        "inventories": {
            "type": "object",
            "patternProperties": {
                RESOURCE_CLASS_IDENTIFIER: PUT_INVENTORY_RECORD_SCHEMA,
            }
        }
    },
    "required": [
        "resource_provider_generation",
        "inventories"
    ],
    "additionalProperties": False
}

# NOTE(cdent): We keep our own representation of inventory defaults
# and output fields, separate from the versioned object to avoid
# inadvertent API changes when the object defaults are changed.
OUTPUT_INVENTORY_FIELDS = [
    'total',
    'reserved',
    'min_unit',
    'max_unit',
    'step_size',
    'allocation_ratio',
]
INVENTORY_DEFAULTS = {
    'reserved': 0,
    'min_unit': 1,
    'max_unit': db.MAX_INT,
    'step_size': 1,
    'allocation_ratio': 1.0
}


def _extract_inventory(body, schema):
    """Extract and validate inventory from JSON body."""
    data = util.extract_json(body, schema)

    inventory_data = copy.copy(INVENTORY_DEFAULTS)
    inventory_data.update(data)

    return inventory_data


def _extract_inventories(body, schema):
    """Extract and validate multiple inventories from JSON body."""
    data = util.extract_json(body, schema)

    inventories = {}
    for res_class, raw_inventory in data['inventories'].items():
        inventory_data = copy.copy(INVENTORY_DEFAULTS)
        inventory_data.update(raw_inventory)
        inventories[res_class] = inventory_data

    data['inventories'] = inventories
    return data


def _make_inventory_object(resource_provider, resource_class, **data):
    """Single place to catch malformed Inventories."""
    # TODO(cdent): Some of the validation checks that are done here
    # could be done via JSONschema (using, for example, "minimum":
    # 0) for non-negative integers. It's not clear if that is
    # duplication or decoupling so leaving it as this for now.
    try:
        inventory = objects.Inventory(
            resource_provider=resource_provider,
            resource_class=resource_class, **data)
    except (ValueError, TypeError) as exc:
        raise webob.exc.HTTPBadRequest(
            _('Bad inventory %(class)s for resource provider '
              '%(rp_uuid)s: %(error)s') % {'class': resource_class,
                                           'rp_uuid': resource_provider.uuid,