import re
import pytest
from gersemi.builtin_commands import builtin_commands
from gersemi.extension_type import ModuleExtension
from gersemi.extensions import VerificationFailure, verify

defs = "gersemi_foo:command_definitions"
ab = f"{defs}['ab']"
ab_sections = f"{ab}['sections']"
ab_signatures = f"{ab}['signatures']"


@pytest.mark.parametrize(
    "definition",
    [
        {},
        {"ab": {}},
        {"ab": {"options": set()}},
        {"ab": {"options": ()}},
        {"ab": {"options": []}},
        {"ab": {"options": ("ONE", "TWO", "THREE")}},
        {"ab": {"unsupported_entry": {}, "foobar": {}}},
        {"ab": {"unsupported_entry": {}, "options": [], "foobar": {}}},
        {"ab": {"signatures": {"OK": {}}}},
        {"ab": {"signatures": {"OK": {"multi_value_keywords": "VALUES"}}}},
        {"ab": {"options": ("ONE", "22", "THREE")}},
        {"ab": {"one_value_keywords": ("ONE", "234", "THREE")}},
    ],
)
def test_extension_passes_verification(definition):
    try:
        verify(ModuleExtension("foo"), definition)
    except VerificationFailure as e:
        pytest.fail(f"{repr(definition)} should pass verification but doesn't: {e}")


@pytest.mark.parametrize(
    ("definition", "outcome"),
    [
        ((), f"{defs}: is not a mapping"),
        ({12: 34}, f"{defs}: command name (12) has to be a string"),
        (
            {"": 34},
            f"{defs}: command name ('') has to start with a letter or underscore",
        ),
        (
            {"12": 34},
            f"{defs}: command name ('12') has to start with a letter or underscore",
        ),
        ({"ab": 34}, f"{defs}['ab']: is not a mapping"),
        (
            {"ab": {"options": {1: 2}}},
            f"{ab}['options']: keyword (1) has to be a string",
        ),
        (
            {"ab": {"options": ("ONE", 2, "THREE")}},
            f"{ab}['options']: keyword (2) has to be a string",
        ),
        (
            {"ab": {"front_positional_arguments": 34}},
            f"{ab}['front_positional_arguments']: is not a collection of strings (34)",
        ),
        (
            {"ab": {"front_positional_arguments": [34]}},
            f"{ab}['front_positional_arguments'][0]: argument (34) has to be a string",
        ),
        (
            {"ab": {"front_positional_arguments": ["abc", "", 34]}},
            f"{ab}['front_positional_arguments'][2]: argument (34) has to be a string",
        ),
        (
            {"ab": {"sections": ()}},
            f"{ab_sections}: is not a mapping",
        ),
        (
            {"ab": {"sections": {12: 34}}},
            f"{ab_sections}: keyword (12) has to be a string",
        ),
        (
            {"ab": {"sections": {"12": 34}}},
            f"{ab_sections}['12']: is not a mapping",
        ),
        (
            {"ab": {"sections": {"ABC": 34}}},
            f"{ab_sections}['ABC']: is not a mapping",
        ),
        (
            {"ab": {"sections": {"ABC": {"sections": ()}}}},
            f"{ab_sections}['ABC']['sections']: is not a mapping",
        ),
        ({"ab": {"signatures": ()}}, f"{ab_signatures}: is not a mapping"),
        (
            {"ab": {"signatures": {12: 34}}},
            f"{ab_signatures}: signature (12) has to be a string",
        ),
        (
            {"ab": {"signatures": {"12": 34}}},
            f"{ab_signatures}['12']: is not a mapping",
        ),
        (
            {"ab": {"signatures": {"CD": 34}}},
            f"{ab_signatures}['CD']: is not a mapping",
        ),
    ],
)
def test_extension_fails_verification(definition, outcome):
    with pytest.raises(VerificationFailure, match=re.escape(outcome)):
        verify(ModuleExtension("foo"), definition)


@pytest.mark.parametrize(
    ("command", "definition"),
    list(builtin_commands.items()),
    ids=list(builtin_commands),
)
def test_extension_based_on_builtins_works(command, definition):
    try:
        verify(ModuleExtension("foo"), {command: definition})
    except VerificationFailure as e:
        pytest.fail(f"{repr(definition)} should pass verification but doesn't: {e}")
