"""
Utility module for Open API Specification 3.0.x.

This should get merged into gemma-zds-client, but some heavy refactoring is
needed for that.
"""
from typing import Union

import requests
import yaml
from drf_yasg import openapi

TYPE_MAP = {
    openapi.TYPE_OBJECT: dict,
    openapi.TYPE_STRING: str,
    openapi.TYPE_NUMBER: (float, int),
    openapi.TYPE_INTEGER: int,
    openapi.TYPE_BOOLEAN: bool,
    openapi.TYPE_ARRAY: list,
}


class SchemaFetcher:

    def __init__(self):
        self.cache = {}

    def fetch(self, url: str):
        """
        Fetch a YAML-based OAS 3.0.x schema.
        """
        if url in self.cache:
            return self.cache[url]

        response = requests.get(url)
        response.raise_for_status()

        spec = yaml.safe_load(response.content)
        spec_version = response.headers.get('X-OAS-Version', spec.get('openapi', spec.get('swagger', '')))
        if not spec_version.startswith('3.0'):
            raise ValueError("Unsupported spec version: {}".format(spec_version))

        self.cache[url] = spec
        return spec


def obj_has_shape(obj: Union[list, dict], schema: dict, resource: str) -> bool:
    """
    Compare an instance of an object with the expected shape from an OAS 3 schema.

    ..todo:: doesn't handle references and nested schema's yet.

    :param obj: the value retrieved from the endpoint, json-decoded to a dict or list
    :param schema: the OAS 3.0.x schema to test against, yaml-decoded to dict
    :param resource: the name of the resource to test the schape against
    """
    obj_schema = schema['components']['schemas'][resource]

    required = obj_schema.get('required', [])
    for prop, prop_schema in obj_schema['properties'].items():
        if prop in required and prop not in obj:
            # missing required prop -> can't match the schema
            return False

        # can't compare something that isn't there...
        if prop not in obj:
            continue

        value = obj[prop]
        expected_type = TYPE_MAP[prop_schema['type']]

        # type mismatch -> not what we're looking for
        if not isinstance(value, expected_type):
            return False

    return True


fetcher = SchemaFetcher()
