from typing import (
    Optional,
    Any,
    Tuple,
    Union,
    Sequence,
    Container,
)

from debputy.linting.lint_util import LintState, DebputyMetadata
from debputy.lsp.lsp_features import (
    lint_diagnostics,
    lsp_standard_handler,
    lsp_hover,
    lsp_completer,
    SecondaryLanguage,
    LanguageDispatchRule,
)
from debputy.lsp.lsp_generic_yaml import (
    yaml_key_range,
    generic_yaml_hover,
    LSPYAMLHelper,
    generic_yaml_lint,
    generic_yaml_completer,
    _is_empty_range,
)
from debputy.lsprotocol.types import (
    TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL,
    HoverParams,
    Hover,
    TEXT_DOCUMENT_CODE_ACTION,
    CompletionParams,
    CompletionList,
    CompletionItem,
)
from debputy.plugin.api.impl_types import (
    PluginProvidedParser,
)
from debputy.plugin.api.parser_tables import OPARSER_MANIFEST_ROOT
from debputy.plugin.api.spec import DebputyIntegrationMode
from debputy.plugins.debputy.private_api import Capability, load_libcap

try:
    from pygls.server import LanguageServer
    from debputy.lsp.debputy_ls import DebputyLanguageServer
except ImportError:
    pass


_DISPATCH_RULE = LanguageDispatchRule.new_rule(
    "debian/debputy.manifest",
    "debputy.manifest",
    "debian/debputy.manifest",
    [
        SecondaryLanguage("debputy.manifest"),
        # LSP's official language ID for YAML files
        SecondaryLanguage("yaml", secondary_lookup="path-name"),
    ],
)


lsp_standard_handler(_DISPATCH_RULE, TEXT_DOCUMENT_CODE_ACTION)
lsp_standard_handler(_DISPATCH_RULE, TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL)


class DebputyManifestLSPYAMLHelper(LSPYAMLHelper[DebputyMetadata]):

    def _validate_subparser_is_valid_here(
        self,
        subparser: PluginProvidedParser,
        orig_key: str,
        line: int,
        col: int,
    ) -> None:
        _integration_mode_allows_key(
            self.lint_state,
            self.custom_data.debputy_integration_mode,
            subparser.parser.expected_debputy_integration_mode,
            orig_key,
            line,
            col,
        )

    def _type_based_value_check(
        self,
        target_attr_type: type,
        value: Any,
        value_pos: Tuple[int, int],
        *,
        key: Optional[Union[str, int]] = None,
    ) -> bool:
        if issubclass(target_attr_type, Capability):
            has_libcap, _, is_valid_cap = load_libcap()
            if has_libcap and not is_valid_cap(value):
                line_no, cursor_pos = value_pos
                cap_range = self._remaining_line(line_no, cursor_pos)
                if _is_empty_range(cap_range):
                    # FIXME: We cannot report an empty range, but there is still a problem here.
                    return True
                self.lint_state.emit_diagnostic(
                    cap_range,
                    "The value could not be parsed as a capability via cap_from_text on this system",
                    "warning",
                    "debputy",
                )
            return True

        return super()._type_based_value_check(
            target_attr_type,
            value,
            value_pos,
            key=key,
        )


def _initialize_yaml_helper(lint_state: LintState) -> DebputyManifestLSPYAMLHelper:
    return DebputyManifestLSPYAMLHelper(
        lint_state,
        lint_state.plugin_feature_set.manifest_parser_generator,
        lint_state.debputy_metadata,
    )


@lint_diagnostics(_DISPATCH_RULE)
async def _lint_debian_debputy_manifest(lint_state: LintState) -> None:
    pg = lint_state.plugin_feature_set.manifest_parser_generator
    root_parser = pg.dispatchable_object_parsers[OPARSER_MANIFEST_ROOT]
    await generic_yaml_lint(
        lint_state,
        root_parser,
        _initialize_yaml_helper,
    )


def _integration_mode_allows_key(
    lint_state: LintState,
    debputy_integration_mode: Optional[DebputyIntegrationMode],
    expected_debputy_integration_modes: Optional[Container[DebputyIntegrationMode]],
    key: str,
    line: int,
    col: int,
) -> None:
    if debputy_integration_mode is None or expected_debputy_integration_modes is None:
        return
    if debputy_integration_mode in expected_debputy_integration_modes:
        return
    key_range = yaml_key_range(key, line, col)
    lint_state.emit_diagnostic(
        key_range,
        f'Feature "{key}" not supported in integration mode {debputy_integration_mode}',
        "error",
        "debputy",
    )


@lsp_completer(_DISPATCH_RULE)
def debputy_manifest_completer(
    ls: "DebputyLanguageServer",
    params: CompletionParams,
) -> Optional[Union[CompletionList, Sequence[CompletionItem]]]:
    pg = ls.plugin_feature_set.manifest_parser_generator
    root_parser = pg.dispatchable_object_parsers[OPARSER_MANIFEST_ROOT]
    return generic_yaml_completer(
        ls,
        params,
        root_parser,
    )


@lsp_hover(_DISPATCH_RULE)
def debputy_manifest_hover(
    ls: "DebputyLanguageServer",
    params: HoverParams,
) -> Optional[Hover]:
    return generic_yaml_hover(
        ls,
        params,
        lambda pg: pg.dispatchable_object_parsers[OPARSER_MANIFEST_ROOT],
        show_integration_mode=True,
    )
