"""
Test runner for the JSON Schema official test suite

Tests comprehensive correctness of each draft's validator.

See https://github.com/json-schema-org/JSON-Schema-Test-Suite for details.
"""

import sys

from asdf._jsonschema.tests._helpers import bug
from asdf._jsonschema.tests._suite import Suite
import asdf._jsonschema

SUITE = Suite()
DRAFT3 = SUITE.version(name="draft3")
DRAFT4 = SUITE.version(name="draft4")
DRAFT6 = SUITE.version(name="draft6")
DRAFT7 = SUITE.version(name="draft7")
DRAFT201909 = SUITE.version(name="draft2019-09")
DRAFT202012 = SUITE.version(name="draft2020-12")


def skip(message, **kwargs):
    def skipper(test):
        if all(value == getattr(test, attr) for attr, value in kwargs.items()):
            return message
    return skipper


def missing_format(Validator):
    def missing_format(test):  # pragma: no cover
        schema = test.schema
        if (
            schema is True
            or schema is False
            or "format" not in schema
            or schema["format"] in Validator.FORMAT_CHECKER.checkers
            or test.valid
        ):
            return

        return "Format checker {0!r} not found.".format(schema["format"])
    return missing_format


def complex_email_validation(test):
    if test.subject != "email":
        return

    message = "Complex email validation is (intentionally) unsupported."
    return skip(
        message=message,
        description="an invalid domain",
    )(test) or skip(
        message=message,
        description="an invalid IPv4-address-literal",
    )(test) or skip(
        message=message,
        description="dot after local part is not valid",
    )(test) or skip(
        message=message,
        description="dot before local part is not valid",
    )(test) or skip(
        message=message,
        description="two subsequent dots inside local part are not valid",
    )(test)


is_narrow_build = sys.maxunicode == 2 ** 16 - 1
if is_narrow_build:  # pragma: no cover
    message = "Not running surrogate Unicode case, this Python is narrow."

    def narrow_unicode_build(test):  # pragma: no cover
        return skip(
            message=message,
            description=(
                "one supplementary Unicode code point is not long enough"
            ),
        )(test) or skip(
            message=message,
            description="two supplementary Unicode code points is long enough",
        )(test)
else:
    def narrow_unicode_build(test):  # pragma: no cover
        return


if sys.version_info < (3, 9):  # pragma: no cover
    message = "Rejecting leading zeros is 3.9+"
    allowed_leading_zeros = skip(
        message=message,
        subject="ipv4",
        description="invalid leading zeroes, as they are treated as octals",
    )
else:
    def allowed_leading_zeros(test):  # pragma: no cover
        return


def leap_second(test):
    message = "Leap seconds are unsupported."
    return skip(
        message=message,
        subject="time",
        description="a valid time string with leap second",
    )(test) or skip(
        message=message,
        subject="time",
        description="a valid time string with leap second, Zulu",
    )(test) or skip(
        message=message,
        subject="time",
        description="a valid time string with leap second with offset",
    )(test) or skip(
        message=message,
        subject="time",
        description="valid leap second, positive time-offset",
    )(test) or skip(
        message=message,
        subject="time",
        description="valid leap second, negative time-offset",
    )(test) or skip(
        message=message,
        subject="time",
        description="valid leap second, large positive time-offset",
    )(test) or skip(
        message=message,
        subject="time",
        description="valid leap second, large negative time-offset",
    )(test) or skip(
        message=message,
        subject="time",
        description="valid leap second, zero time-offset",
    )(test) or skip(
        message=message,
        subject="date-time",
        description="a valid date-time with a leap second, UTC",
    )(test) or skip(
        message=message,
        subject="date-time",
        description="a valid date-time with a leap second, with minus offset",
    )(test)


TestDraft3 = DRAFT3.to_unittest_testcase(
    DRAFT3.tests(),
    DRAFT3.format_tests(),
    DRAFT3.optional_tests_of(name="bignum"),
    DRAFT3.optional_tests_of(name="non-bmp-regex"),
    DRAFT3.optional_tests_of(name="zeroTerminatedFloats"),
    Validator=asdf._jsonschema.Draft3Validator,
    format_checker=asdf._jsonschema.Draft3Validator.FORMAT_CHECKER,
    skip=lambda test: (
        narrow_unicode_build(test)
        or missing_format(asdf._jsonschema.Draft3Validator)(test)
        or complex_email_validation(test)
        or skip(
            message=bug(),
            subject="ref",
            valid=False,
            case_description=(
                "$ref prevents a sibling id from changing the base uri"
            ),
        )(test)
    ),
)


TestDraft4 = DRAFT4.to_unittest_testcase(
    DRAFT4.tests(),
    DRAFT4.format_tests(),
    DRAFT4.optional_tests_of(name="bignum"),
    DRAFT4.optional_tests_of(name="float-overflow"),
    DRAFT4.optional_tests_of(name="non-bmp-regex"),
    DRAFT4.optional_tests_of(name="zeroTerminatedFloats"),
    Validator=asdf._jsonschema.Draft4Validator,
    format_checker=asdf._jsonschema.Draft4Validator.FORMAT_CHECKER,
    skip=lambda test: (
        narrow_unicode_build(test)
        or allowed_leading_zeros(test)
        or leap_second(test)
        or missing_format(asdf._jsonschema.Draft4Validator)(test)
        or complex_email_validation(test)
        or skip(
            message=bug(),
            subject="ref",
            case_description="Recursive references between schemas",
        )(test)
        or skip(
            message=bug(),
            subject="ref",
            case_description=(
                "Location-independent identifier with "
                "base URI change in subschema"
            ),
        )(test)
        or skip(
            message=bug(),
            subject="ref",
            case_description=(
                "$ref prevents a sibling id from changing the base uri"
            ),
        )(test)
        or skip(
            message=bug(),
            subject="id",
            description="match $ref to id",
        )(test)
        or skip(
            message=bug(),
            subject="id",
            description="no match on enum or $ref to id",
        )(test)
        or skip(
            message=bug(),
            subject="refRemote",
            case_description="base URI change - change folder in subschema",
        )(test)
        or skip(
            message=bug(),
            subject="ref",
            case_description=(
                "id must be resolved against nearest parent, "
                "not just immediate parent"
            ),
        )(test)
    ),
)


TestDraft6 = DRAFT6.to_unittest_testcase(
    DRAFT6.tests(),
    DRAFT6.format_tests(),
    DRAFT6.optional_tests_of(name="bignum"),
    DRAFT6.optional_tests_of(name="float-overflow"),
    DRAFT6.optional_tests_of(name="non-bmp-regex"),
    Validator=asdf._jsonschema.Draft6Validator,
    format_checker=asdf._jsonschema.Draft6Validator.FORMAT_CHECKER,
    skip=lambda test: (
        narrow_unicode_build(test)
        or allowed_leading_zeros(test)
        or leap_second(test)
        or missing_format(asdf._jsonschema.Draft6Validator)(test)
        or complex_email_validation(test)
        or skip(
            message=bug(),
            subject="refRemote",
            case_description="base URI change - change folder in subschema",
        )(test)
    ),
)


TestDraft7 = DRAFT7.to_unittest_testcase(
    DRAFT7.tests(),
    DRAFT7.format_tests(),
    DRAFT7.optional_tests_of(name="bignum"),
    DRAFT7.optional_tests_of(name="cross-draft"),
    DRAFT7.optional_tests_of(name="float-overflow"),
    DRAFT7.optional_tests_of(name="non-bmp-regex"),
    Validator=asdf._jsonschema.Draft7Validator,
    format_checker=asdf._jsonschema.Draft7Validator.FORMAT_CHECKER,
    skip=lambda test: (
        narrow_unicode_build(test)
        or allowed_leading_zeros(test)
        or leap_second(test)
        or missing_format(asdf._jsonschema.Draft7Validator)(test)
        or complex_email_validation(test)
        or skip(
            message=bug(),
            subject="refRemote",
            case_description="base URI change - change folder in subschema",
        )(test)
        or skip(
            message=bug(),
            subject="ref",
            case_description=(
                "$id must be resolved against nearest parent, "
                "not just immediate parent"
            ),
        )(test)
    ),
)


TestDraft201909 = DRAFT201909.to_unittest_testcase(
    DRAFT201909.tests(),
    DRAFT201909.optional_tests_of(name="bignum"),
    DRAFT201909.optional_tests_of(name="cross-draft"),
    DRAFT201909.optional_tests_of(name="float-overflow"),
    DRAFT201909.optional_tests_of(name="non-bmp-regex"),
    DRAFT201909.optional_tests_of(name="refOfUnknownKeyword"),
    Validator=asdf._jsonschema.Draft201909Validator,
    skip=lambda test: (
        skip(
            message="recursiveRef support isn't working yet.",
            subject="recursiveRef",
            case_description=(
                "$recursiveRef with no $recursiveAnchor in "
                "the initial target schema resource"
            ),
            description=(
                "leaf node does not match: recursion uses the inner schema"
            ),
        )(test)
        or skip(
            message="recursiveRef support isn't working yet.",
            subject="recursiveRef",
            description="leaf node matches: recursion uses the inner schema",
        )(test)
        or skip(
            message="recursiveRef support isn't working yet.",
            subject="recursiveRef",
            case_description=(
                "dynamic $recursiveRef destination (not predictable "
                "at schema compile time)"
            ),
            description="integer node",
        )(test)
        or skip(
            message="recursiveRef support isn't working yet.",
            subject="recursiveRef",
            case_description=(
                "multiple dynamic paths to the $recursiveRef keyword"
            ),
            description="recurse to integerNode - floats are not allowed",
        )(test)
        or skip(
            message="recursiveRef support isn't working yet.",
            subject="recursiveRef",
            description="integer does not match as a property value",
        )(test)
        or skip(
            message="recursiveRef support isn't working yet.",
            subject="recursiveRef",
            description=(
                "leaf node does not match: "
                "recursion only uses inner schema"
            ),
        )(test)
        or skip(
            message="recursiveRef support isn't working yet.",
            subject="recursiveRef",
            description=(
                "leaf node matches: "
                "recursion only uses inner schema"
            ),
        )(test)
        or skip(
            message="recursiveRef support isn't working yet.",
            subject="recursiveRef",
            description=(
                "two levels, integer does not match as a property value"
            ),
        )(test)
        or skip(
            message="recursiveRef support isn't working yet.",
            subject="recursiveRef",
            description="recursive mismatch",
        )(test)
        or skip(
            message="recursiveRef support isn't working yet.",
            subject="recursiveRef",
            description="two levels, no match",
        )(test)
        or skip(
            message="recursiveRef support isn't working yet.",
            subject="id",
            case_description=(
                "Invalid use of fragments in location-independent $id"
            ),
        )(test)
        or skip(
            message="dynamicRef support isn't fully working yet.",
            subject="defs",
            description="invalid definition schema",
        )(test)
        or skip(
            message="dynamicRef support isn't fully working yet.",
            subject="anchor",
            case_description="same $anchor with different base uri",
        )(test)
        or skip(
            message="Vocabulary support is still in-progress.",
            subject="vocabulary",
            description=(
                "no validation: invalid number, but it still validates"
            ),
        )(test)
        or skip(
            message=bug(),
            subject="ref",
            case_description=(
                "$id must be resolved against nearest parent, "
                "not just immediate parent"
            ),
        )(test)
        or skip(
            message=bug(),
            subject="refRemote",
            case_description="remote HTTP ref with nested absolute ref",
        )(test)
    ),
)


TestDraft201909Format = DRAFT201909.to_unittest_testcase(
    DRAFT201909.format_tests(),
    name="TestDraft201909Format",
    Validator=asdf._jsonschema.Draft201909Validator,
    format_checker=asdf._jsonschema.Draft201909Validator.FORMAT_CHECKER,
    skip=lambda test: (
        complex_email_validation(test)
        or allowed_leading_zeros(test)
        or leap_second(test)
        or missing_format(asdf._jsonschema.Draft201909Validator)(test)
        or complex_email_validation(test)
    ),
)


TestDraft202012 = DRAFT202012.to_unittest_testcase(
    DRAFT202012.tests(),
    DRAFT202012.optional_tests_of(name="bignum"),
    DRAFT202012.optional_tests_of(name="cross-draft"),
    DRAFT202012.optional_tests_of(name="float-overflow"),
    DRAFT202012.optional_tests_of(name="non-bmp-regex"),
    DRAFT202012.optional_tests_of(name="refOfUnknownKeyword"),
    Validator=asdf._jsonschema.Draft202012Validator,
    skip=lambda test: (
        narrow_unicode_build(test)
        or skip(
            message="dynamicRef support isn't fully working yet.",
            subject="dynamicRef",
            description="The recursive part is not valid against the root",
        )(test)
        or skip(
            message="dynamicRef support isn't fully working yet.",
            subject="dynamicRef",
            description="incorrect extended schema",
            case_description=(
                "$ref and $dynamicAnchor are independent of order - "
                "$defs first"
            ),
        )(test)
        or skip(
            message="dynamicRef support isn't fully working yet.",
            subject="dynamicRef",
            description="correct extended schema",
            case_description=(
                "$ref and $dynamicAnchor are independent of order - "
                "$defs first"
            ),
        )(test)
        or skip(
            message="dynamicRef support isn't fully working yet.",
            subject="dynamicRef",
            description="correct extended schema",
            case_description=(
                "$ref and $dynamicAnchor are independent of order - $ref first"
            ),
        )(test)
        or skip(
            message="dynamicRef support isn't fully working yet.",
            subject="dynamicRef",
            description="incorrect extended schema",
            case_description=(
                "$ref and $dynamicAnchor are independent of order - $ref first"
            ),
        )(test)
        or skip(
            message="dynamicRef support isn't fully working yet.",
            subject="dynamicRef",
            description=(
                "/then/$defs/thingy is the final stop for the $dynamicRef"
            ),
        )(test)
        or skip(
            message="dynamicRef support isn't fully working yet.",
            subject="dynamicRef",
            description=(
                "string matches /$defs/thingy, but the $dynamicRef "
                "does not stop here"
            ),
        )(test)
        or skip(
            message="dynamicRef support isn't fully working yet.",
            subject="dynamicRef",
            description=(
                "string matches /$defs/thingy, but the $dynamicRef "
                "does not stop here"
            ),
        )(test)
        or skip(
            message="dynamicRef support isn't fully working yet.",
            subject="dynamicRef",
            description="recurse to integerNode - floats are not allowed",
        )(test)
        or skip(
            message="dynamicRef support isn't fully working yet.",
            subject="defs",
            description="invalid definition schema",
        )(test)
        or skip(
            message="dynamicRef support isn't fully working yet.",
            subject="anchor",
            case_description="same $anchor with different base uri",
        )(test)
        or skip(
            message="Vocabulary support is still in-progress.",
            subject="vocabulary",
            description=(
                "no validation: invalid number, but it still validates"
            ),
        )(test)
        or skip(
            message=bug(),
            subject="ref",
            case_description=(
                "$id must be resolved against nearest parent, "
                "not just immediate parent"
            ),
        )(test)
        or skip(
            message=bug(),
            subject="refRemote",
            case_description="remote HTTP ref with nested absolute ref",
        )(test)
    ),
)


TestDraft202012Format = DRAFT202012.to_unittest_testcase(
    DRAFT202012.format_tests(),
    name="TestDraft202012Format",
    Validator=asdf._jsonschema.Draft202012Validator,
    format_checker=asdf._jsonschema.Draft202012Validator.FORMAT_CHECKER,
    skip=lambda test: (
        complex_email_validation(test)
        or allowed_leading_zeros(test)
        or leap_second(test)
        or missing_format(asdf._jsonschema.Draft202012Validator)(test)
        or complex_email_validation(test)
    ),
)
