import textwrap
from typing import Optional, Type, Any

from debputy.manifest_parser.declarative_parser import (
    ParserGenerator,
    DeclarativeNonMappingInputParser,
)
from debputy.manifest_parser.tagging_types import DebputyParsedContent
from debputy.plugin.api import reference_documentation
from debputy.plugin.api.impl import plugin_metadata_for_debputys_own_plugin
from debputy.plugin.api.impl_types import (
    DispatchingObjectParser,
    DebputyPluginMetadata,
    DeclarativeInputParser,
)
from debputy.plugin.api.spec import ParserDocumentation
from debputy.util import T, _error
from .config_options import ALL_DEBPUTY_CONFIG_OPTIONS


class BoolDebputyParsedContent(DebputyParsedContent):
    content: bool


TT = Type[T]


def _debputy_config_parser() -> DispatchingObjectParser:
    pg = ParserGenerator()
    plugin_metadata = plugin_metadata_for_debputys_own_plugin()
    root_parser = pg.add_object_parser(
        "<ROOT>",
        unknown_keys_diagnostic_severity=None,
        allow_unknown_keys=True,
    )
    diagnostics_object_parser = _add_sub_object_parser(
        pg,
        root_parser,
        "diagnostics",
        plugin_metadata,
        parser_documentation=reference_documentation(
            title="Diagnostics related settings (`diagnostics`)",
            description=textwrap.dedent(
                """\
                Configurations related to diagnostics.
            """
            ),
        ),
    )

    spellchecking_diagnostics = _add_sub_object_parser(
        pg,
        diagnostics_object_parser,
        "spellchecking",
        plugin_metadata,
        parser_documentation=reference_documentation(
            title="Spellchecking related diagnostics settings (`spellchecking`)",
            description=textwrap.dedent(
                """\
                Settings related to spellchecking diagnostics
            """
            ),
        ),
    )
    _config_value(
        pg,
        spellchecking_diagnostics,
        "spellcheck-comments",
        bool,
        plugin_metadata,
        inline_reference_documentation=reference_documentation(
            title="Whether to spellcheck comments (`spellcheck-comments`)",
            description=textwrap.dedent(
                """\
                Define whether `debputy` should spellcheck syntactical comments.

                Consider the following example:
                ```debian/control
                Source: foo

                Package: foo
                # Here is a comment with a typu.
                Description: Also a mistkae here
                ```

                This option affects whether `typu` in the above example will be
                reported provided textual spellchecking is enabled. The other typo
                (`mistkae`) is unaffected by this option, since it is not inside
                a syntactical comment.

                When this option is present and set to `false`, then syntactical comments
                are never spellchecked.
            """
            ),
        ),
    )
    _debputy_self_check_config_parser(root_parser)
    return root_parser


def _debputy_self_check_config_parser(root_parser: DispatchingObjectParser) -> None:
    for config_option in ALL_DEBPUTY_CONFIG_OPTIONS:
        parser: DeclarativeInputParser[Any] = root_parser
        for part in config_option.config_name.split("."):
            if isinstance(parser, DispatchingObjectParser):
                parser = parser.parser_for(part).parser
            else:
                _error(
                    f"Unknown parser for {config_option.config_name} at {part}: {parser}"
                )
        if not isinstance(parser, DeclarativeNonMappingInputParser):
            _error(
                f"Expecting {config_option.config_name} to resolve to a DeclarativeNonMappingInputParser, got: {parser}"
            )
        if parser.alt_form_parser.attribute_type != config_option.value_type:
            _error(
                f"Expecting {config_option.config_name} to be of type {config_option.value_type}, but parser suggest it is {parser.alt_form_parser.attribute_type}"
            )


def _config_value(
    pg: ParserGenerator,
    parent_parser: DispatchingObjectParser,
    path: str,
    value_type: TT,
    plugin_metadata: DebputyPluginMetadata,
    *,
    inline_reference_documentation: Optional[ParserDocumentation] = None,
) -> None:
    class DebputyParsedContentWrapper(DebputyParsedContent):
        content: value_type  # type: ignore

    parser = pg.generate_parser(
        DebputyParsedContentWrapper,
        source_content=value_type,
        inline_reference_documentation=inline_reference_documentation,
    )
    parent_parser.register_parser(
        path,
        parser,
        lambda k, pf, ap, pc: pf["content"],
        plugin_metadata,
    )


def _add_sub_object_parser(
    pg: ParserGenerator,
    parent_parser: DispatchingObjectParser,
    path: str,
    plugin_metadata: DebputyPluginMetadata,
    *,
    parser_documentation: Optional[ParserDocumentation] = None,
) -> DispatchingObjectParser:
    if parent_parser.manifest_attribute_path_template == "<ROOT>":
        full_path = path
    else:
        full_path = f"{parent_parser.manifest_attribute_path_template}.{path}"
    child_parser = pg.add_object_parser(
        full_path,
        unknown_keys_diagnostic_severity=None,
        parser_documentation=parser_documentation,
        allow_unknown_keys=True,
    )
    parent_parser.register_parser(
        path,
        child_parser,
        lambda k, pf, ap, pc: pf,
        plugin_metadata,
    )
    return child_parser


DEBPUTY_CONFIG_PARSER = _debputy_config_parser()

del _debputy_config_parser
del _config_value
del _add_sub_object_parser
del _debputy_self_check_config_parser
