import json
import sys
from contextlib import suppress
from functools import partial

import fastjsonschema
import jsonschema
import pytest

if sys.implementation.name != "pypy":
    import jsonschema_rs
else:
    jsonschema_rs = None


def load_json(filename):
    with open(filename) as fd:
        return json.load(fd)


def load_json_str(filename):
    with open(filename) as fd:
        return fd.read()


def load_from_benches(filename, loader=load_json):
    return loader(f"../../jsonschema/benches/data/{filename}")


OPENAPI = load_from_benches("openapi.json")
ZUORA = load_from_benches("zuora.json")
SWAGGER = load_from_benches("swagger.json")
KUBERNETES = load_from_benches("kubernetes.json")
GEOJSON = load_from_benches("geojson.json")
CANADA = load_from_benches("canada.json")
CITM_CATALOG_SCHEMA = load_from_benches("citm_catalog_schema.json")
CITM_CATALOG = load_from_benches("citm_catalog.json")
FAST_SCHEMA = load_from_benches("fast_schema.json")
FAST_INSTANCE_VALID = [9, "hello", [1, "a", True], {"a": "a", "b": "b", "d": "d"}, 42, 3]
FAST_INSTANCE_INVALID = [10, "world", [1, "a", True], {"a": "a", "b": "b", "c": "xy"}, "str", 5]


@pytest.fixture(params=[True, False], ids=("compiled", "raw"))
def is_compiled(request):
    return request.param


if jsonschema_rs is not None:
    variants = ["jsonschema-rs-is-valid", "jsonschema-rs-validate", "jsonschema", "fastjsonschema"]
else:
    variants = ["jsonschema", "fastjsonschema"]


DEFAULT_BENCHMARK_CONFIG = {"iterations": 10, "rounds": 10, "warmup_rounds": 10}


@pytest.fixture(params=variants)
def variant(request):
    return request.param


@pytest.fixture
def args(request, variant, is_compiled):
    schema, instance = request.node.get_closest_marker("data").args
    if variant == "jsonschema-rs-is-valid":
        if is_compiled:
            return jsonschema_rs.JSONSchema(schema, with_meta_schemas=True).is_valid, instance
        else:
            return partial(jsonschema_rs.is_valid, with_meta_schemas=True), schema, instance
    if variant == "jsonschema-rs-validate":
        if is_compiled:
            return jsonschema_rs.JSONSchema(schema, with_meta_schemas=True).validate, instance
        else:
            return partial(jsonschema_rs.validate, with_meta_schemas=True), schema, instance
    if variant == "jsonschema":
        if is_compiled:
            return jsonschema.validators.validator_for(schema)(schema).is_valid, instance
        else:
            return jsonschema.validate, instance, schema
    if variant == "fastjsonschema":
        if is_compiled:
            return fastjsonschema.compile(schema, use_default=False), instance
        else:
            return partial(fastjsonschema.validate, use_default=False), schema, instance


@pytest.mark.parametrize(
    "name", ("openapi.json", "swagger.json", "geojson.json", "citm_catalog_schema.json", "fast_schema.json")
)
@pytest.mark.parametrize(
    "func",
    (
        lambda x: jsonschema_rs.JSONSchema(json.loads(x)),
        jsonschema_rs.JSONSchema.from_str,
    ),
    ids=["py-parse", "rs-parse"],
)
@pytest.mark.benchmark(group="create schema")
def test_create_schema(benchmark, func, name):
    benchmark.group = f"{name}: {benchmark.group}"
    schema = load_from_benches(name, loader=load_json_str)
    benchmark(func, schema)


# Small schemas


@pytest.mark.data(True, True)
@pytest.mark.benchmark(group="boolean")
def test_boolean(benchmark, args):
    benchmark(*args)


@pytest.mark.data({"minimum": 10}, 10)
@pytest.mark.benchmark(group="minimum")
def test_minimum(benchmark, args):
    benchmark(*args)


@pytest.mark.data(FAST_SCHEMA, FAST_INSTANCE_VALID)
@pytest.mark.benchmark(group="fast-valid")
def test_fast_valid(benchmark, args):
    benchmark(*args)


@pytest.mark.data(FAST_SCHEMA, FAST_INSTANCE_VALID)
@pytest.mark.benchmark(group="fast-invalid")
def test_fast_invalid(benchmark, args):
    def func():
        with suppress(Exception):
            args[0](*args[1:])

    benchmark(func)


# Large schemas


@pytest.mark.data(OPENAPI, ZUORA)
@pytest.mark.benchmark(group="openapi")
def test_openapi(benchmark, args):
    benchmark(*args)


@pytest.mark.data(SWAGGER, KUBERNETES)
@pytest.mark.benchmark(group="swagger")
def test_swagger(benchmark, args):
    benchmark(*args)


@pytest.mark.data(GEOJSON, CANADA)
@pytest.mark.benchmark(group="canada")
def test_canada(benchmark, args):
    benchmark(*args)


@pytest.mark.data(CITM_CATALOG_SCHEMA, CITM_CATALOG)
@pytest.mark.benchmark(group="citm_catalog")
def test_citm_catalog(benchmark, args):
    benchmark(*args)
