from __future__ import annotations

import json
import re
from pathlib import Path

import pytest

from schematools import validation
from schematools.permissions import PUBLIC_SCOPE
from schematools.types import DatasetSchema
from schematools.validation import (
    PROPERTIES_INTRODUCING_BREAKING_CHANGES,
    _check_display,
    _check_maingeometry,
    _identifier_properties,
    validate_dataset,
    validate_dataset_versions_version,
    validate_schema_identifier,
    validate_table,
    validate_table_version,
)


def test_camelcase() -> None:
    for ident in ("camelCase", "camelCase100"):
        assert validation._camelcase_ident(ident) is None

    error = validation._camelcase_ident("")
    assert error is not None
    assert "empty identifier" in error

    for ident, suggest in (
        ("snake_case", "snakeCase"),
        ("camelCase_snake", "camelCaseSnake"),
        ("camel100camel", "camel100Camel"),
    ):
        error = validation._camelcase_ident(ident)
        assert error is not None
        assert error.endswith(f"suggestion: {suggest}")


def test_enum_types(schema_loader) -> None:
    dataset = schema_loader.get_dataset_from_file("enum_types.json")

    errors = validation.run(dataset)

    error = next(errors)
    assert error.validator_name == "enum type error"
    assert error.message == "value 'foo' in field enumInts is not an integer"

    error = next(errors)
    assert error.validator_name == "enum type error"
    assert error.message == "value 2 in field enumStrs is not a string"

    error = next(errors)
    assert error.validator_name == "enum type error"
    assert error.message == "enumFloats: enum of type number not possible"

    assert list(errors) == []


def test_id_auth(schema_loader) -> None:
    dataset = schema_loader.get_dataset_from_file("id_auth.json")

    errors = validation.run(dataset)

    error = next(errors)
    assert error
    assert error.validator_name == "Auth on identifier field"
    assert """auth on field 'id'""" in error.message

    assert list(errors) == []


def test_id_type(schema_loader) -> None:
    dataset = schema_loader.get_dataset_from_file("id_type.json")

    errors = validation.run(dataset)

    error = next(errors)
    assert error
    assert error.validator_name == "Identifier field with the wrong type"
    assert """field 'uniqid'""" in error.message
    assert """'number'""" in error.message

    assert list(errors) == []


def test_id_matches_path(here: Path, schema_loader) -> None:
    dataset = schema_loader.get_dataset_from_file("stadsdelen.json")

    # No errors when id equals parent path name
    errors = validation.run(dataset, str(here / "files/datasets/stadsdelen/dataset.json"))
    assert list(errors) == []

    # Error when not equal
    errors = validation.run(dataset, str(here / "files/datasets/regios/dataset.json"))
    error = next(errors)
    assert error
    assert error.validator_name == "ID does not match file path"
    assert re.match(
        r"^Id of the dataset stadsdelen does not match the parent directory"
        r" .*/files/datasets/regios\.$",
        error.message,
    )
    assert list(errors) == []

    # Test datasets in sub directory
    dataset.__setitem__("id", "beheerkaartCbsGrid")
    errors = validation.run(dataset, str(here / "files/datasets/bierkaart/cbs_grid/dataset.json"))
    error = next(errors)
    assert error
    assert error.validator_name == "ID does not match file path"
    assert re.match(
        r"^Id of the dataset beheerkaartCbsGrid does not match the parent directory"
        r" .*/files/datasets/bierkaart/cbs_grid\.$",
        error.message,
    )
    assert list(errors) == []

    errors = validation.run(
        dataset, str(here / "files/datasets/beheerkaart/cbs_grid/dataset.json")
    )
    assert list(errors) == []

    # Test identifiers ending with a number
    dataset.__setitem__("id", "covid19")
    errors = validation.run(dataset, str(here / "files/datasets/covid19/dataset.json"))
    assert list(errors) == []


def test_crs(schema_loader) -> None:
    dataset = schema_loader.get_dataset_from_file("crs_validation.json")

    errors = validation.run(dataset)

    error = next(errors)
    assert error
    assert error.validator_name == "crs"
    assert """No coordinate reference system defined for field""" in error.message

    assert list(errors) == []


def test_postgres_identifier_length(schema_loader) -> None:
    dataset = schema_loader.get_dataset_from_file("long_ids.json")

    error = next(validation.run(dataset))
    assert error
    assert error.validator_name == "PostgreSQL identifier length"
    assert "absurdly_long" in error.message

    dataset = schema_loader.get_dataset_from_file("stadsdelen.json")
    assert list(validation.run(dataset)) == []  # no validation errors


def test_postgres_duplicate_shortnames(schema_loader) -> None:
    dataset = schema_loader.get_dataset_from_file("duplicate_shortnames.json")

    error = next(validation.run(dataset))
    assert error
    assert error.validator_name == "PostgreSQL duplicate shortnames"
    assert error.message == "Duplicate shortname 'sameName' found for field: 'veld1,veld2'"


def test_postgres_duplicate_abbreviated_fieldnames(schema_loader) -> None:
    dataset = schema_loader.get_dataset_from_file("abbreviated_fieldnames.json")

    error = next(validation.run(dataset))
    assert error
    assert error.validator_name == "PostgreSQL duplicate abbreviated fieldnames"
    assert (
        error.message
        == "Fields 'eenVestigingIsGebouwOfEenComplexGebouwenDuurzameUitoefeningActiviteitenOndernemingRechtspersoon',"
        " 'eenVestigingIsGebouwOfEenComplexGebouwenDuurzameUitoefeningActiviteitenOndernemingRechtspersoon2' share "
        "the same first 63 characters. Add a shortname."
    )


def test_postgres_duplicate_abbreviated_fieldnames_with_shortname(schema_loader) -> None:
    dataset = schema_loader.get_dataset_from_file("abbreviated_fieldnames_with_shortname.json")
    assert list(validation.run(dataset)) == []  # no validation errors


def test_identifier_properties(schema_loader) -> None:
    dataset = schema_loader.get_dataset_from_file("identifier_ref.json")
    error = next(validation.run(dataset))
    assert error
    assert "foobar" in error.message

    dataset = schema_loader.get_dataset_from_file("stadsdelen.json")
    assert list(_identifier_properties(dataset)) == []  # no validation errors


def test_main_geometry(schema_loader, gebieden_schema) -> None:
    dataset = schema_loader.get_dataset_from_file("meetbouten.json")
    assert list(_check_maingeometry(dataset)) == []

    dataset.get_table_by_id("meetbouten")["schema"]["mainGeometry"] = None
    error = next(validation.run(dataset))
    assert "'mainGeometry' is required but not defined in table" in error.message

    dataset.get_table_by_id("meetbouten")["schema"]["mainGeometry"] = "not_a_geometry"
    error = next(validation.run(dataset))
    assert "mainGeometry = 'not_a_geometry'" in error.message
    assert "Field 'not_a_geometry' does not exist" in error.message

    dataset.get_table_by_id("meetbouten")["schema"]["mainGeometry"] = "merkOmschrijving"
    error = next(validation.run(dataset))
    assert error.message == (
        "mainGeometry = 'merkOmschrijving' is not a geometry field, type = 'string'"
    )


def test_display(here: Path, schema_loader) -> None:
    schema_loader.get_dataset_from_file("gebieden.json")  # fill cache
    dataset = schema_loader.get_dataset_from_file("meetbouten.json")
    assert list(_check_display(dataset)) == []

    table = dataset.get_table_by_id("meetbouten")
    table["schema"]["display"] = "not_a_field"
    error = next(validation.run(dataset))
    assert "display = 'not_a_field'" in error.message
    assert "Field 'not_a_field' does not exist" in error.message

    table["schema"]["display"] = "merkCode"
    table["schema"]["properties"]["merkCode"]["auth"] = "some_scope"
    table.__dict__.pop("fields", None)  # clear cached property
    error = next(validation.run(dataset))
    assert "'auth' property on the display field: 'merkCode' is not allowed." in error.message


def test_rel_auth_dataset(schema_loader) -> None:
    dataset = schema_loader.get_dataset_from_file("rel_auth.json")
    dataset["auth"] = ["HAMMERTIME"]
    dataset["reasonsNonPublic"] = ["U can't touch this"]

    errors = list(validation.run(dataset))
    assert errors == []


def test_rel_auth_dataset_public(schema_loader) -> None:
    dataset = schema_loader.get_dataset_from_file("rel_auth.json")
    dataset["auth"] = [PUBLIC_SCOPE]

    errors = list(validation.run(dataset))
    assert len(errors) == 0, errors


def test_rel_auth_table(here: Path) -> None:
    with (here / "files/datasets/rel_auth.json").open() as f:
        dataset_json = json.load(f)
    table = next(t for t in dataset_json["versions"]["v1"]["tables"] if t["id"] == "base")
    table["auth"] = ["HAMMERTIME"]
    table["reasonsNonPublic"] = ["U can't touch this"]
    dataset = DatasetSchema.from_dict(dataset_json)

    errors = list(validation.run(dataset))
    assert len(errors) == 1, errors
    assert "requires scopes ['HAMMERTIME']" in str(errors[0])


def test_rel_auth_field(here: Path) -> None:
    with (here / "files/datasets/rel_auth.json").open() as f:
        dataset_json = json.load(f)
    table = next(t for t in dataset_json["versions"]["v1"]["tables"] if t["id"] == "base")
    field = table["schema"]["properties"]["stop"]
    field["auth"] = ["HAMMERTIME"]

    dataset = DatasetSchema.from_dict(dataset_json)
    errors = list(validation.run(dataset))

    assert len(errors) >= 1, errors
    assert any("requires scopes ['HAMMERTIME']" in str(e) for e in errors)


@pytest.mark.skip(reason="See comment in validator function")
def test_repetitive_naming(here: Path, schema_loader) -> None:
    dataset = schema_loader.get_dataset("repetitive")
    errors = {str(e) for e in validation.run(dataset)}

    assert errors == {
        "[repetitive identifiers] " + e
        for e in [
            "table name 'repetitiveTable' should not start with 'repetitive'",
            "field name 'repetitiveTableField' should not start with 'repetitive'",
            "field name 'repetitiveTableField' should not start with 'repetitiveTable'",
        ]
    }


def test_reasons_non_public_exists(here: Path, schema_loader) -> None:
    dataset = schema_loader.get_dataset_from_file("hr_auth.json")
    errors = list(validation.run(dataset))

    # Test an error is given for the highest non-public scope
    # and only for the highest non-public scope.
    assert len(errors) == 1
    assert errors[0].message == "Non-public dataset hr should have a 'reasonsNonPublic' property."

    dataset["auth"] = [PUBLIC_SCOPE]
    errors = list(validation.run(dataset))
    assert len(errors) == 1
    assert (
        errors[0].message
        == "Non-public table sbiactiviteiten should have a 'reasonsNonPublic' property."
    )

    # Test no error is given when a reason is present
    dataset_json = json.load((here / "files/datasets/hr_auth.json").open())
    dataset_json["versions"]["v1"]["tables"][0]["reasonsNonPublic"] = [
        "5.1 1c: Bevat persoonsgegevens"
    ]
    dataset = DatasetSchema.from_dict(dataset_json)
    dataset["auth"] = [PUBLIC_SCOPE]
    errors = list(validation.run(dataset))
    assert len(errors) == 0


def test_reasons_non_public_value(schema_loader) -> None:
    dataset = schema_loader.get_dataset_from_file("hr_auth.json")
    dataset["reasonsNonPublic"] = ["5.1 1c: Bevat persoonsgegevens", "nader te bepalen"]
    errors = list(validation.run(dataset))

    # Test an error is given for the placeholder value in a dataset with status = beschikbaar.
    assert len(errors) == 1
    assert "not allowed in ReasonsNonPublic property of dataset hr." in errors[0].message

    # Test no error is given for the placeholder value in a dataset with status != beschikbaar.
    dataset.versions["v1"]["enableAPI"] = False
    errors = list(validation.run(dataset))
    assert len(errors) == 0

    # Test no error is given for other values of reasonsNonPublic
    dataset.versions["v1"]["enableAPI"] = True
    dataset["reasonsNonPublic"] = ["5.1 1c: Bevat persoonsgegevens"]
    errors = list(validation.run(dataset))
    assert len(errors) == 0


def test_schema_ref(schema_loader) -> None:
    dataset = schema_loader.get_dataset_from_file("schema_ref_validation.json")

    errors = validation.run(dataset)

    error = next(errors)
    assert error
    assert error.validator_name == "schema ref"
    assert """Incorrect `$ref` for""" in error.message

    assert list(errors) == []


def test_check_default_version(schema_loader) -> None:
    dataset = schema_loader.get_dataset_from_file("schema_default_version.json")

    errors = list(validation.run(dataset))
    assert len(errors) == 0

    # Prove that disabled default version gives an error (when there are multiple versions)
    dataset["defaultVersion"] = "v2"
    dataset["versions"]["v2"]["status"] = "niet_beschikbaar"
    errors = list(validation.run(dataset))
    assert len(errors) == 1
    assert "Default version v2 is not enabled." in errors[0].message


def test_check_default_version_is_experimental(schema_loader) -> None:
    """Ensure that if there is only one version and it is experimental, it does not matter that
    the default version is unavailable."""
    dataset = schema_loader.get_dataset_from_file("schema_default_version_experimental.json")

    errors = list(validation.run(dataset))
    assert len(errors) == 0


def test_production_version_tables(schema_loader) -> None:
    dataset = schema_loader.get_dataset("production_version")

    errors = list(validation.run(dataset))
    assert len(errors) == 1
    assert (
        "Dataset version (v1) cannot contain non-production table [tables/v0]" in errors[0].message
    )


def test_row_level_auth(schema_loader) -> None:
    dataset = schema_loader.get_dataset_from_file("brp_row_level_auth.json")
    errors = list(validation.run(dataset))
    assert len(errors) == 0


def test_row_level_auth_fail(schema_loader) -> None:
    dataset = schema_loader.get_dataset_from_file("brp_row_level_auth_fail.json")
    errors = [v.message for v in validation.run(dataset)]
    assert len(errors) == 6
    assert errors == [
        "Source verblijfplaats.afgeschermd_adres is not available in table Ingeschrevenpersonen.",
        "Target verblijfplaats.telefoon does not exist in table Ingeschrevenpersonen",
        "Source verblijfplaats.afgeschermd in table Ingeschrevenpersonen2 is not a boolean.",
        "Source verblijfplaats.afgeschermd is also a target!",
        "Target verblijfplaats.huisnummer does not define FEATURE/RLA auth.",
        "Target bestaat.niet does not exist in table Ingeschrevenpersonen2",
    ]


def test_subresource(schema_loader) -> None:
    schema_loader.get_dataset_from_file("woningbouwplannen.json")
    dataset = schema_loader.get_dataset_from_file("subresources_invalid.json")
    errors = [v.message for v in validation.run(dataset)]

    # There are two subresources defined in the schema, but only one refers to a table in the
    # same dataset. The other should give an error.
    assert len(errors) == 3
    assert errors == [
        "Subresource woningbouwplannen:woningbouwplan is not part of the same dataset as "
        "gebieden:wijken. Subresources must always be part of the same dataset.",
        "Table pleinen does not exist in dataset gebieden. Cannot use as subresource.",
        "Field ligtInDezeWijk does not exist on table gebieden:straten. Cannot use as subresource.",
    ]


def test_check_status(schema_loader) -> None:
    dataset = schema_loader.get_dataset("status")

    errors = list(validation.run(dataset))
    assert len(errors) == 1
    assert (
        "Dataset version (v0) cannot have a status of 'stable' while being a non-production version."
        in errors[0].message
    )


def test_check_enable_api(schema_loader) -> None:
    dataset = schema_loader.get_dataset("metaschemav4/enableapi")

    errors = list(validation.run(dataset))
    assert len(errors) == 1
    assert "Default version v1 is not enabled." in errors[0].message


def test_check_superseded_version(schema_loader) -> None:
    dataset = schema_loader.get_dataset("metaschemav4/supersededversion")

    errors = list(validation.run(dataset))
    assert len(errors) == 1
    assert (
        "Dataset version (v1) cannot have a status of 'superseded' without an endSupportDate."
        in errors[0].message
    )


@pytest.mark.parametrize(
    "prev,curr,errors",
    [
        # No changes
        ([{"id": "table", "$ref": "table/v1"}], [{"id": "table", "$ref": "table/v1"}], []),
        # Added table
        (
            [{"id": "table", "$ref": "table/v1"}],
            [{"id": "table", "$ref": "table/v1"}, {"id": "table2", "$ref": "table2/v1"}],
            [],
        ),
        # Removed table
        (
            [{"id": "table", "$ref": "table/v1"}, {"id": "table2", "$ref": "table2/v1"}],
            [{"id": "table", "$ref": "table/v1"}],
            ["Table table2 has been removed."],
        ),
        # Changed table version
        (
            [{"id": "table", "$ref": "table/v1"}],
            [{"id": "table", "$ref": "table/v2"}],
            [
                "Table table has changed version. Previous version: table/v1, current version: table/v2."
            ],
        ),
        # Multiple errors
        (
            [{"id": "table", "$ref": "table/v1"}, {"id": "table2", "$ref": "table2/v1"}],
            [{"id": "table", "$ref": "table/v2"}],
            [
                "Table table has changed version. Previous version: table/v1, current version: table/v2.",
                "Table table2 has been removed.",
            ],
        ),
    ],
)
def test_validate_dataset(prev, curr, errors):
    table_errors = validate_dataset(prev, curr)
    assert table_errors == errors


@pytest.mark.parametrize(
    "prev,curr,errors",
    [
        (
            "value",
            "changed_value",
            [
                "Identifier field would be changed from value to changed_value. "
                "Changing the table schema identifier is a breaking change."
            ],
        ),
        (
            ["value"],
            ["changed_value"],
            [
                "Identifier field would be changed from ['value'] to ['changed_value']. "
                "Changing the table schema identifier is a breaking change."
            ],
        ),
        (
            None,
            ["changed_value"],
            [
                "Identifier field would be changed from None to ['changed_value']. "
                "Changing the table schema identifier is a breaking change."
            ],
        ),
    ],
)
def test_validate_table_schema_identifier(prev, curr, errors):
    table_errors = validate_schema_identifier(prev, curr)
    assert table_errors == errors


@pytest.mark.parametrize(
    "prev,curr,errors",
    [
        # No changes
        ({"name": {"type": "string"}}, {"name": {"type": "string"}}, []),
        # Deleted field
        ({"name": {"type": "string"}}, {}, ["Column name would be deleted."]),
        # Changed schema ref
        (
            {
                "schema": {
                    "$ref": "https://schemas.data.amsterdam.nl/schema@v1.1.1#/definitions/schema"
                }
            },
            {
                "schema": {
                    "$ref": "https://schemas.data.amsterdam.nl/schema@v3.1.0#/definitions/schema"
                }
            },
            [],
        ),
        # Changed array item type
        (
            {"list": {"type": "array", "items": {"type": "string"}}},
            {"list": {"type": "array", "items": {"type": "integer"}}},
            ["Column list would change items.type."],
        ),
        # Changed object property.
        (
            {"object": {"type": "object", "properties": {"element": {"type": "string"}}}},
            {"object": {"type": "object", "properties": {"element": {"type": "integer"}}}},
            ["Column object would change element.type."],
        ),
        # Object with format JSON works does not raise errors.
        (
            {"object": {"type": "object", "format": "json"}},
            {"object": {"type": "object", "format": "json"}},
            [],
        ),
        # Changed object property type within an array
        (
            {
                "list": {
                    "type": "array",
                    "items": {"type": "object", "properties": {"element": {"type": "string"}}},
                }
            },
            {
                "list": {
                    "type": "array",
                    "items": {"type": "object", "properties": {"element": {"type": "integer"}}},
                }
            },
            ["Column list would change items.element.type."],
        ),
        # Changed subproperty of a property of an object
        (
            {
                "object": {
                    "type": "object",
                    "properties": {
                        "element": {
                            "type": "object",
                            "properties": {"subelement": {"type": "string"}},
                        }
                    },
                }
            },
            {
                "object": {
                    "type": "object",
                    "properties": {
                        "element": {
                            "type": "object",
                            "properties": {"subelement": {"type": "integer"}},
                        }
                    },
                }
            },
            ["Column object would change element.subelement.type."],
        ),
        # Multiple breaking changes
        (
            {
                "object": {
                    "type": "object",
                    "properties": {
                        "element": {
                            "type": "object",
                            "properties": {
                                "subelement": {"type": "string"},
                                "subelement2": {"type": "string"},
                            },
                        }
                    },
                    "description": "Original description",
                    "relation": "table1:object_1",
                },
                "id": {"type": "string"},
                "deprecated_field": {"type": "string"},
            },
            {
                "object": {
                    "type": "object",
                    "properties": {
                        "element": {
                            "type": "object",
                            "properties": {"subelement": {"type": "integer"}},
                        }
                    },
                    "description": "Description 2.0",
                    "relation": "table2:object_2",
                },
                "id": {"type": "integer"},
            },
            [
                "Column object would change relation.",
                "Column object would change element.subelement.type.",
                "Property element.subelement2 would be deleted from column object.",
                "Column id would change type.",
                "Column deprecated_field would be deleted.",
            ],
        ),
    ]
    + [
        # All properties that should stay the same.
        (
            {"property": {prop: "string"}},
            {"property": {prop: "integer"}},
            [f"Column property would change {prop}."],
        )
        for prop in PROPERTIES_INTRODUCING_BREAKING_CHANGES
    ],
)
def test_validate_table(prev, curr, errors):
    table_errors = validate_table(prev, curr)
    assert table_errors == errors


@pytest.mark.parametrize(
    "prev,curr,errors",
    [
        # No updates
        (
            {"id": "test", "version": "1.0.0", "schema": {"properties": {"field": {}}}},
            {"id": "test", "version": "1.0.0", "schema": {"properties": {"field": {}}}},
            [],
        ),
        # Add field success
        (
            {"id": "test", "version": "1.0.0", "schema": {"properties": {"field": {}}}},
            {
                "id": "test",
                "version": "1.1.0",
                "schema": {"properties": {"field": {}, "field2": {}}},
            },
            [],
        ),
        # Add field fail no new version
        (
            {"id": "test", "version": "1.0.0", "schema": {"properties": {"field": {}}}},
            {
                "id": "test",
                "version": "1.0.0",
                "schema": {"properties": {"field": {}, "field2": {}}},
            },
            ["Table 'test' added fields, expecting new version to be 1.1.0."],
        ),
        # Add field fail only patch bump
        (
            {"id": "test", "version": "1.0.0", "schema": {"properties": {"field": {}}}},
            {
                "id": "test",
                "version": "1.0.1",
                "schema": {"properties": {"field": {}, "field2": {}}},
            },
            ["Table 'test' added fields, expecting new version to be 1.1.0."],
        ),
        # Change metadata fail no new version
        (
            {
                "id": "test",
                "title": "Title 1",
                "description": "Description 1",
                "shortname": "test",
                "version": "1.0.0",
                "schema": {"properties": {"field": {}}},
            },
            {
                "id": "test",
                "title": "Title 2",
                "description": "Description 2",
                "shortname": "test2",
                "version": "1.0.0",
                "schema": {"properties": {"field": {}}},
            },
            [
                "Property 'title' on table 'test' has changed, expecting new version to be 1.0.1.",
                "Property 'description' on table 'test' has changed, expecting new version to be 1.0.1.",
                "Property 'shortname' on table 'test' has changed, expecting new version to be 1.0.1.",
            ],
        ),
        # Change metadata fail version too high
        (
            {
                "id": "test",
                "title": "Title 1",
                "description": "Description 1",
                "shortname": "test",
                "version": "1.0.0",
                "schema": {"properties": {"field": {}}},
            },
            {
                "id": "test",
                "title": "Title 2",
                "description": "Description 2",
                "shortname": "test2",
                "version": "1.1.0",
                "schema": {"properties": {"field": {}}},
            },
            [
                "Property 'title' on table 'test' has changed, expecting new version to be 1.0.1.",
                "Property 'description' on table 'test' has changed, expecting new version to be 1.0.1.",
                "Property 'shortname' on table 'test' has changed, expecting new version to be 1.0.1.",
            ],
        ),
        # Change metadata success
        (
            {
                "id": "test",
                "title": "Title 1",
                "description": "Description 1",
                "shortname": "test",
                "version": "1.0.0",
                "schema": {"properties": {"field": {}}},
            },
            {
                "id": "test",
                "title": "Title 2",
                "description": "Description 2",
                "shortname": "test2",
                "version": "1.0.1",
                "schema": {"properties": {"field": {}}},
            },
            [],
        ),
        # Change metadata on schema fail no new version
        (
            {
                "id": "test",
                "version": "1.0.0",
                "schema": {"display": "field", "properties": {"field": {}}},
            },
            {
                "id": "test",
                "version": "1.0.0",
                "schema": {"display": "field2", "properties": {"field": {}}},
            },
            [
                "Property 'schema.display' on table 'test' has changed, expecting new version to be 1.0.1."
            ],
        ),
        # Change metadata on schema success
        (
            {
                "id": "test",
                "version": "1.0.0",
                "schema": {"display": "field", "properties": {"field": {}}},
            },
            {
                "id": "test",
                "version": "1.0.1",
                "schema": {"display": "field2", "properties": {"field": {}}},
            },
            [],
        ),
        # Change metadata on field fail no new version
        (
            {
                "id": "test",
                "version": "1.0.0",
                "schema": {
                    "properties": {
                        "field": {
                            "title": "field",
                            "unit": "m2",
                            "shortname": "field",
                            "description": "Field1",
                        }
                    }
                },
            },
            {
                "id": "test",
                "version": "1.0.0",
                "schema": {
                    "properties": {
                        "field": {
                            "title": "field2",
                            "unit": "cm2",
                            "shortname": "field2",
                            "description": "Field2",
                        }
                    }
                },
            },
            [
                "Property 'title' on field 'field' in table 'test' has changed, expecting new version to be 1.0.1.",
                "Property 'description' on field 'field' in table 'test' has changed, expecting new version to be 1.0.1.",
                "Property 'shortname' on field 'field' in table 'test' has changed, expecting new version to be 1.0.1.",
                "Property 'unit' on field 'field' in table 'test' has changed, expecting new version to be 1.0.1.",
            ],
        ),
        # Change metadata on field success
        (
            {
                "id": "test",
                "version": "1.0.0",
                "schema": {
                    "properties": {
                        "field": {
                            "title": "field",
                            "unit": "m2",
                            "shortname": "field",
                            "description": "Field1",
                        }
                    }
                },
            },
            {
                "id": "test",
                "version": "1.0.1",
                "schema": {
                    "properties": {
                        "field": {
                            "title": "field2",
                            "unit": "cm2",
                            "shortname": "field2",
                            "description": "Field2",
                        }
                    }
                },
            },
            [],
        ),
        # Change major version number on table fail
        (
            {
                "id": "test",
                "version": "1.0.0",
                "schema": {
                    "properties": {
                        "field": {
                            "title": "field",
                            "unit": "m2",
                            "shortname": "field",
                            "description": "Field1",
                        }
                    }
                },
            },
            {
                "id": "test",
                "version": "2.0.0",
                "schema": {
                    "properties": {
                        "field": {
                            "title": "field2",
                            "unit": "cm2",
                            "shortname": "field2",
                            "description": "Field2",
                        }
                    }
                },
            },
            ["Table 'test' changed major version, a new file v2.json should be created."],
        ),
    ],
)
def test_validate_table_version(prev, curr, errors):
    table_errors = validate_table_version(prev, curr)
    assert table_errors == errors


@pytest.mark.parametrize(
    "prev,curr,errors",
    [
        # No changes, no fail
        (
            {"version": "1.0.0", "status": "stable", "tables": [{"id": "table1"}]},
            {"version": "1.0.0", "status": "stable", "tables": [{"id": "table1"}]},
            [],
        ),
        # Changes with version bump, no fail
        (
            {"version": "1.0.0", "status": "stable", "tables": [{"id": "table1"}]},
            {
                "version": "1.1.0",
                "status": "stable",
                "tables": [{"id": "table1"}, {"id": "table2"}],
            },
            [],
        ),
        # Changes for experimental table, no fail
        (
            {"version": "1.0.0", "status": "under_development", "tables": [{"id": "table1"}]},
            {
                "version": "1.1.0",
                "status": "under_development",
                "tables": [{"id": "table1"}, {"id": "table2"}],
            },
            [],
        ),
        # Changes without version bump, fail
        (
            {"version": "1.0.0", "status": "stable", "tables": [{"id": "table1"}]},
            {
                "version": "1.0.0",
                "status": "stable",
                "tables": [{"id": "table1"}, {"id": "table2"}],
            },
            ["Dataset 'dataset' v1 has an added table, expecting new version to be 1.1.0."],
        ),
        # Changes with too big of a minor version bump, fail
        (
            {"version": "1.0.0", "status": "stable", "tables": [{"id": "table1"}]},
            {
                "version": "1.2.0",
                "status": "stable",
                "tables": [{"id": "table1"}, {"id": "table2"}],
            },
            ["Dataset 'dataset' v1 has an added table, expecting new version to be 1.1.0."],
        ),
        # Changes with a patch version expect new minor version without patch, no fail
        (
            {"version": "1.1.1", "status": "stable", "tables": [{"id": "table1"}]},
            {
                "version": "1.2.0",
                "status": "stable",
                "tables": [{"id": "table1"}, {"id": "table2"}],
            },
            [],
        ),
        # Changes with a major version bump, fail
        (
            {"version": "1.0.0", "status": "stable", "tables": [{"id": "table1"}]},
            {
                "version": "2.0.0",
                "status": "stable",
                "tables": [{"id": "table1"}, {"id": "table2"}],
            },
            ["Dataset 'dataset' v1 has an added table, expecting new version to be 1.1.0."],
        ),
        # Changes with too little of a version bump, fail
        (
            {"version": "1.0.0", "status": "stable", "tables": [{"id": "table1"}]},
            {
                "version": "1.0.1",
                "status": "stable",
                "tables": [{"id": "table1"}, {"id": "table2"}],
            },
            ["Dataset 'dataset' v1 has an added table, expecting new version to be 1.1.0."],
        ),
    ],
)
def test_validate_dataset_versions_version(prev, curr, errors):
    dataset_version_errors = validate_dataset_versions_version("dataset", prev, curr)
    assert dataset_version_errors == errors
