"""Methods for validating config dicts."""

from sqlfluff.core.config.removed import validate_config_dict_for_removed
from sqlfluff.core.errors import SQLFluffUserError
from sqlfluff.core.types import ConfigMappingType

ALLOWABLE_LAYOUT_CONFIG_KEYS = (
    "spacing_before",
    "spacing_after",
    "spacing_within",
    "line_position",
    "align_within",
    "align_scope",
    "keyword_line_position",
    "keyword_line_position_exclusions",
    "alignment_coordinate_space",
)

ALLOWABLE_IMPLICIT_INDENTS_VALUES = ("forbid", "allow", "require")


def _validate_layout_config(config: ConfigMappingType, logging_reference: str) -> None:
    """Validate the layout config section of the config.

    We check for valid key values and for the depth of the
    structure.

    NOTE: For now we don't check that the "type" is a valid one
    to reference, or that the values are valid. For the values,
    these are likely to be rejected by the layout routines at
    runtime. The last risk area is validating that the type is
    a valid one, but that should be handled by the same as the above.
    """
    layout_section = config.get("layout", {})
    if not layout_section:
        return None

    preamble = f"Config file {logging_reference!r} set an invalid `layout` option. "
    reference = (
        "See https://docs.sqlfluff.com/en/stable/perma/layout.html"
        "#configuring-layout for more details."
    )

    if not isinstance(layout_section, dict):
        raise SQLFluffUserError(
            preamble
            + f"Found value {layout_section!r} instead of a valid layout section. "
            + reference
        )

    # The sections within layout can only be "type" (currently).
    non_type_keys = set(layout_section.keys()) - {"type"}
    type_section = layout_section.get("type", {})
    if non_type_keys or not type_section or not isinstance(type_section, dict):
        raise SQLFluffUserError(
            preamble
            + "Only sections of the form `sqlfluff:layout:type:...` are valid. "
            + reference
        )

    for layout_type, layout_section in type_section.items():
        if not isinstance(layout_section, dict):
            raise SQLFluffUserError(
                preamble
                + f"Layout config for {layout_type!r} is invalid. Expected a section. "
                + reference
            )

        invalid_keys = set(layout_section.keys()) - set(ALLOWABLE_LAYOUT_CONFIG_KEYS)
        if invalid_keys:
            raise SQLFluffUserError(
                preamble
                + f"Layout config for type {layout_type!r} is invalid. "
                + f"Found the following invalid keys: {invalid_keys}. "
                + reference
            )

        for key in ALLOWABLE_LAYOUT_CONFIG_KEYS:
            if key in layout_section:
                if isinstance(layout_section[key], dict):
                    raise SQLFluffUserError(
                        preamble
                        + f"Layout config for type {layout_type!r} is invalid. "
                        + "Found the an unexpected section rather than "
                        + f"value for {key}. "
                        + reference
                    )


def _validate_indentation_config(
    config: ConfigMappingType, logging_reference: str
) -> None:
    """Validate the indentation config section of the config.

    We check for valid values for enum-like config options.
    """
    indentation_section = config.get("indentation", {})
    if not indentation_section:
        return None

    # Validate implicit_indents enum value
    # indentation_section should be a dict, but we need to type check
    assert isinstance(indentation_section, dict)
    implicit_indents = indentation_section.get("implicit_indents")
    if implicit_indents is not None:
        if implicit_indents not in ALLOWABLE_IMPLICIT_INDENTS_VALUES:
            raise SQLFluffUserError(
                f"Config file {logging_reference!r} set an invalid value for "
                f"`implicit_indents`: {implicit_indents!r}. "
                f"Valid options are: {', '.join(ALLOWABLE_IMPLICIT_INDENTS_VALUES)}. "
                "See https://docs.sqlfluff.com/en/stable/perma/configuration.html "
                "for more details."
            )


def _validate_max_parse_depth_config(
    config: ConfigMappingType, logging_reference: str
) -> None:
    """Validate and normalize the max_parse_depth config value.

    The parser convention is that ``0`` disables the limit. We also normalize
    missing/None values to ``0`` so parser code can rely on an integer.
    """
    core_section = config.get("core", {})
    if not core_section:
        return None

    assert isinstance(core_section, dict)
    if "max_parse_depth" not in core_section:
        return None

    max_parse_depth = core_section.get("max_parse_depth")
    if max_parse_depth is None or max_parse_depth == "":
        core_section["max_parse_depth"] = 0
        return None

    if isinstance(max_parse_depth, bool) or not isinstance(max_parse_depth, int):
        raise SQLFluffUserError(
            f"Config file {logging_reference!r} set an invalid value for "
            f"`max_parse_depth`: {max_parse_depth!r}. "
            "This value must be an integer. Use 0 or an empty value to "
            "disable the limit."
        )

    if max_parse_depth < 0:
        raise SQLFluffUserError(
            f"Config file {logging_reference!r} set an invalid value for "
            f"`max_parse_depth`: {max_parse_depth!r}. "
            "This value must be 0 or a positive integer. Use 0 to disable "
            "the limit."
        )


def _validate_max_parse_nodes_config(
    config: ConfigMappingType, logging_reference: str
) -> None:
    """Validate and normalize the max_parse_nodes config value.

    The parser convention is that ``0`` disables the limit. We also normalize
    missing/None values to ``0`` so parser code can rely on an integer.
    """
    core_section = config.get("core", {})
    if not core_section:
        return None

    assert isinstance(core_section, dict)
    if "max_parse_nodes" not in core_section:
        return None

    max_parse_nodes = core_section.get("max_parse_nodes")
    if max_parse_nodes is None or max_parse_nodes == "":
        core_section["max_parse_nodes"] = 0
        return None

    if isinstance(max_parse_nodes, bool) or not isinstance(max_parse_nodes, int):
        raise SQLFluffUserError(
            f"Config file {logging_reference!r} set an invalid value for "
            f"`max_parse_nodes`: {max_parse_nodes!r}. "
            "This value must be an integer. Use 0 or an empty value to "
            "disable the limit."
        )

    if max_parse_nodes < 0:
        raise SQLFluffUserError(
            f"Config file {logging_reference!r} set an invalid value for "
            f"`max_parse_nodes`: {max_parse_nodes!r}. "
            "This value must be 0 or a positive integer. Use 0 to disable "
            "the limit."
        )


def _validate_int_config(
    config: ConfigMappingType, key: str, minimum: int, logging_reference: str
) -> None:
    """Validate that a core integer config value is a valid integer.

    Unlike ``max_parse_depth``/``max_parse_nodes`` there is no ``0`` sentinel
    for these options, so any non-integer or out-of-range value is rejected with
    a clean ``SQLFluffUserError`` rather than being allowed to reach numeric-use
    sites and crash with an opaque ``TypeError``.
    """
    core_section = config.get("core", {})
    if not core_section:
        return None

    assert isinstance(core_section, dict)
    if key not in core_section:
        return None

    value = core_section.get(key)
    if isinstance(value, bool) or not isinstance(value, int):
        raise SQLFluffUserError(
            f"Config file {logging_reference!r} set an invalid value for "
            f"`{key}`: {value!r}. This value must be an integer."
        )

    if value < minimum:
        raise SQLFluffUserError(
            f"Config file {logging_reference!r} set an invalid value for "
            f"`{key}`: {value!r}. This value must be an integer of at least "
            f"{minimum}."
        )


def validate_config_dict(config: ConfigMappingType, logging_reference: str) -> None:
    """Validate a config dict.

    Currently we validate:
    - Removed and deprecated values.
    - Layout configuration structure.
    - Indentation configuration values.

    Using this method ensures that any later validation will also be applied.

    NOTE: Some of these method may mutate the config object where they are
    able to correct issues.
    """
    # Validate the config for any removed values
    validate_config_dict_for_removed(config, logging_reference)
    # Validate layout section
    _validate_layout_config(config, logging_reference)
    # Validate indentation section
    _validate_indentation_config(config, logging_reference)
    # Validate max parse depth config
    _validate_max_parse_depth_config(config, logging_reference)
    # Validate max parse nodes config
    _validate_max_parse_nodes_config(config, logging_reference)
    # Validate other core integer limits which have no disable sentinel.
    _validate_int_config(config, "render_variant_limit", 1, logging_reference)
    _validate_int_config(config, "runaway_limit", 1, logging_reference)
