import textwrap
from functools import lru_cache
from typing import (
    Optional,
    Any,
    Union,
    Sequence,
    TYPE_CHECKING,
    Type,
)

from debputy.linting.lint_util import LintState
from debputy.lsp.lsp_features import (
    lint_diagnostics,
    lsp_standard_handler,
    lsp_hover,
    lsp_completer,
    LanguageDispatchRule,
    SecondaryLanguage,
)
from debputy.lsp.lsp_generic_yaml import (
    generic_yaml_hover,
    LSPYAMLHelper,
    generic_yaml_lint,
    generic_yaml_completer,
)
from debputy.manifest_parser.base_types import (
    DebputyParsedContent,
)
from debputy.manifest_parser.declarative_parser import (
    ParserGenerator,
)
from debputy.manifest_parser.parser_data import ParserContextData
from debputy.manifest_parser.util import AttributePath
from debputy.plugin.api.impl import plugin_metadata_for_debputys_own_plugin
from debputy.plugin.api.impl_types import (
    DispatchingParserBase,
    DebputyPluginMetadata,
    DispatchingObjectParser,
)
from debputy.plugin.api.spec import ParserDocumentation, reference_documentation
from debputy.util import T

try:
    from debputy.lsp.debputy_ls import DebputyLanguageServer
    from debputy.lsp.vendoring._deb822_repro.locatable import (
        Position as TEPosition,
        Range as TERange,
    )
except ImportError:
    pass

if TYPE_CHECKING:
    import lsprotocol.types as types
else:
    import debputy.lsprotocol.types as types


_DISPATCH_RULE = LanguageDispatchRule.new_rule(
    "debian/upstream/metadata",
    None,
    "debian/upstream/metadata",
    [SecondaryLanguage("yaml", secondary_lookup="path-name")],
)


lsp_standard_handler(_DISPATCH_RULE, types.TEXT_DOCUMENT_CODE_ACTION)
lsp_standard_handler(_DISPATCH_RULE, types.TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL)


TT = Type[T]


def _parser_handler(
    _key: str,
    value: Any,
    _attr_path: AttributePath,
    _context: Optional["ParserContextData"],
) -> Any:
    return value


def add_keyword(
    pg: ParserGenerator,
    root_parser: DispatchingParserBase[Any],
    plugin_metadata: DebputyPluginMetadata,
    keyword: str,
    value_type: TT,
    *,
    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,
    )
    root_parser.register_parser(
        keyword,
        parser,
        _parser_handler,
        plugin_metadata,
    )


@lru_cache
def root_object_parser() -> DispatchingObjectParser:
    plugin_metadata = plugin_metadata_for_debputys_own_plugin()
    pg = ParserGenerator()
    root_parser = pg.add_object_parser(
        "<ROOT>",
        unknown_keys_diagnostic_severity="warning",
    )
    add_keyword(
        pg,
        root_parser,
        plugin_metadata,
        "Archive",
        str,
        inline_reference_documentation=reference_documentation(
            title="Archive (`Archive`)",
            description=textwrap.dedent(
                """\
                The name of the large archive that the upstream work is part of, like CPAN.
            """
            ),
        ),
    )
    add_keyword(
        pg,
        root_parser,
        plugin_metadata,
        "ASCL-Id",
        str,
        inline_reference_documentation=reference_documentation(
            title="ASCL Identifier (`ASCL-Id`)",
            description=textwrap.dedent(
                """\
                Identification code in the http://ascl.net
            """
            ),
        ),
    )
    add_keyword(
        pg,
        root_parser,
        plugin_metadata,
        "Bug-Database",
        str,
        inline_reference_documentation=reference_documentation(
            title="Bug database or tracker for the project (`Bug-Database`)",
            description=textwrap.dedent(
                """\
                A URL to the list of known bugs for the project.
            """
            ),
        ),
    )
    add_keyword(
        pg,
        root_parser,
        plugin_metadata,
        "Bug-Submit",
        str,
        inline_reference_documentation=reference_documentation(
            title="Bug submission URL for the project (`Bug-Submit`)",
            description=textwrap.dedent(
                """\
                A URL that is the place where new bug reports should be sent.
            """
            ),
        ),
    )
    add_keyword(
        pg,
        root_parser,
        plugin_metadata,
        "Cite-As",
        str,
        inline_reference_documentation=reference_documentation(
            title="Cite-As (`Cite-As`)",
            description=textwrap.dedent(
                """\
                The way the authors want their software be cited in publications.

                The value is a string which might contain a link in valid HTML syntax.
            """
            ),
        ),
    )
    add_keyword(
        pg,
        root_parser,
        plugin_metadata,
        "Changelog",
        str,
        inline_reference_documentation=reference_documentation(
            title="Changelog (`Changelog`)",
            description=textwrap.dedent(
                """\
                URL to the upstream changelog.
            """
            ),
        ),
    )
    add_keyword(
        pg,
        root_parser,
        plugin_metadata,
        "CPE",
        str,
        inline_reference_documentation=reference_documentation(
            title="CPE (`CPE`)",
            description=textwrap.dedent(
                """\
                One or more space separated http://cpe.mitre.org/ values useful to look up relevant CVEs
                in the https://nvd.nist.gov/home.cfm and other CVE sources.

                See `CPEtagPackagesDep` for information on how this information can be used.
                **Example**: `cpe:/a:ethereal_group:ethereal`
            """
            ),
        ),
    )
    add_keyword(
        pg,
        root_parser,
        plugin_metadata,
        "Documentation",
        str,
        inline_reference_documentation=reference_documentation(
            title="Documentation (`Documentation`)",
            description=textwrap.dedent(
                """\
                A URL to online documentation.
            """
            ),
        ),
    )
    add_keyword(
        pg,
        root_parser,
        plugin_metadata,
        "Donation",
        str,
        inline_reference_documentation=reference_documentation(
            title="Donation (`Donation`)",
            description=textwrap.dedent(
                """\
                A URL to a donation form (or instructions).
            """
            ),
        ),
    )
    add_keyword(
        pg,
        root_parser,
        plugin_metadata,
        "FAQ",
        str,
        inline_reference_documentation=reference_documentation(
            title="FAQ (`FAQ`)",
            description=textwrap.dedent(
                """\
                A URL to the online FAQ.
            """
            ),
        ),
    )
    add_keyword(
        pg,
        root_parser,
        plugin_metadata,
        "Funding",
        # Unsure of the format
        Any,
        inline_reference_documentation=reference_documentation(
            title="Funding (`Funding`)",
            description=textwrap.dedent(
                """\
                One or more sources of funding which have supported this project (e.g. NSF OCI-12345).
            """
            ),
        ),
    )
    add_keyword(
        pg,
        root_parser,
        plugin_metadata,
        "Gallery",
        str,
        inline_reference_documentation=reference_documentation(
            title="Gallery (`Gallery`)",
            description=textwrap.dedent(
                """\
                A URL to a gallery of pictures made with the program (not screenshots).
            """
            ),
        ),
    )
    add_keyword(
        pg,
        root_parser,
        plugin_metadata,
        "Other-References",
        str,
        inline_reference_documentation=reference_documentation(
            title="Other-References (`Other-References`)",
            description=textwrap.dedent(
                """\
                A URL to a upstream page containing more references.
            """
            ),
        ),
    )
    add_keyword(
        pg,
        root_parser,
        plugin_metadata,
        "Reference",
        # Complex type
        Any,
        inline_reference_documentation=reference_documentation(
            title="Reference (`Reference`)",
            # FIXME: Add the fields below as a nested subobject or list of such objects
            description=textwrap.dedent(
                """\
                One or more bibliographic references, represented as a mapping or sequence of mappings containing
                the one or more of the following keys.
 
                The values for the keys are always scalars, and the keys that correspond to standard BibTeX
                entries must provide the same content.
            """
            ),
        ),
    )

    # Reference:: One or more bibliographic references, represented as a mapping or sequence of mappings containing the one or more of the following keys. The values for the keys are always scalars, and the keys that correspond to standard BibTeX entries must provide the same content.
    #
    # Author:: Author list in BibTeX friendly syntax (separating multiple authors by the keyword "and" and using as few as possible abbreviations in the names, as proposed in http://nwalsh.com/tex/texhelp/bibtx-23.html).
    #
    # Booktitle:: Title of the book the article is published in
    #
    # DOI:: This is the digital object identifier of the academic publication describing the packaged work.
    #
    # Editor:: Editor of the book the article is published in
    #
    # Eprint:: Hyperlink to the PDF file of the article.
    #
    # ISBN:: International Standard Book Number of the book if the article is part of the book or the reference is a book
    #
    # ISSN:: International Standard Serial Number of the periodical publication if the article is part of a series
    #
    # Journal:: Abbreviated journal name [To be discussed: which standard to recommend ?].
    #
    # Number:: Issue number.
    #
    # Pages:: Article page number(s). [To be discussed] Page number separator must be a single ASCII hyphen. What do we do with condensed notations like 401-10 ?
    #
    # PMID:: ID number in the https://www.ncbi.nlm.nih.gov/pubmed/ database.
    #
    # Publisher:: Publisher of the book containing the article
    #
    # Title:: Article title.
    #
    # Type:: A http://www.bibtex.org/Format indicating what is cited. Typical values are {{{article}}}, {{{book}}}, or {{{inproceedings}}}. [To be discussed]. In case this field is not present, {{{article}}} is assumed.
    #
    # URL:: Hyperlink to the abstract of the article. This should not point to the full version because this is specified by Eprint. Please also do not drop links to pubmed here because this would be redundant to PMID.
    #
    # Volume:: Journal volume.
    #
    # Year:: Year of publication
    #

    add_keyword(
        pg,
        root_parser,
        plugin_metadata,
        "Registration",
        str,
        inline_reference_documentation=reference_documentation(
            title="Registration (`Registration`)",
            description=textwrap.dedent(
                """\
                A URL to a registration form (or instructions). This could be registration of bug reporting
                accounts, registration for counting/contacting users etc.
            """
            ),
        ),
    )
    add_keyword(
        pg,
        root_parser,
        plugin_metadata,
        "Registry",
        # FIXME: Add List of `Name`, `Entry` objects
        Any,
        inline_reference_documentation=reference_documentation(
            title="Registry (`Registry`)",
            description=textwrap.dedent(
                """\
                    This field shall point to external catalogs/registries of software.
 
                    The field features an array of "Name (of registry) - Entry (ID of software in that catalog)" pairs.
                    The names and entries shall only be names, not complete URIs, to avoid any bias on mirrors etc.
                    Example:
                    ```yaml
                       Registry:
                         - Name: bio.tools
                           Entry: clustalw
                         - Name: OMICtools
                           Entry: OMICS_02562
                         - Name: SciCrunch
                           Entry: SCR_002909
                    ```
            """
            ),
        ),
    )

    add_keyword(
        pg,
        root_parser,
        plugin_metadata,
        "Repository",
        str,
        inline_reference_documentation=reference_documentation(
            title="Repository (`Repository`)",
            description=textwrap.dedent(
                """\
                    URL to a repository containing the upstream sources.
            """
            ),
        ),
    )

    add_keyword(
        pg,
        root_parser,
        plugin_metadata,
        "Repository-Browse",
        str,
        inline_reference_documentation=reference_documentation(
            title="Repository-Browse (`Repository-Browse`)",
            description=textwrap.dedent(
                """\
                     A URL to browse the repository containing the upstream sources.
            """
            ),
        ),
    )
    add_keyword(
        pg,
        root_parser,
        plugin_metadata,
        "Screenshots",
        Any,
        inline_reference_documentation=reference_documentation(
            title="Screenshots (`Screenshots`)",
            description=textwrap.dedent(
                """\
                     One or more URLs to upstream pages containing screenshots (not <https://screenshots.debian.net>),
                     represented by a scalar or a sequence of scalars.
            """
            ),
        ),
    )
    add_keyword(
        pg,
        root_parser,
        plugin_metadata,
        "Security-Contact",
        str,
        inline_reference_documentation=reference_documentation(
            title="Security-Contact (`Security-Contact`)",
            description=textwrap.dedent(
                """\
                      Which person, mailing list, forum, etc. to send security-related messages in the first place.
            """
            ),
        ),
    )
    add_keyword(
        pg,
        root_parser,
        plugin_metadata,
        "Webservice",
        str,
        inline_reference_documentation=reference_documentation(
            title="Webservice (`Webservice`)",
            description=textwrap.dedent(
                """\
                      URL to a web page where the packaged program can also be used.
            """
            ),
        ),
    )
    return root_parser


def _initialize_yaml_helper(lint_state: LintState) -> LSPYAMLHelper[None]:
    return LSPYAMLHelper(
        lint_state,
        lint_state.plugin_feature_set.manifest_parser_generator,
        None,
    )


@lint_diagnostics(_DISPATCH_RULE)
async def _lint_debian_upstream_metadata(lint_state: LintState) -> None:
    await generic_yaml_lint(
        lint_state,
        root_object_parser(),
        _initialize_yaml_helper,
    )


@lsp_completer(_DISPATCH_RULE)
def debian_upstream_metadata_completer(
    ls: "DebputyLanguageServer",
    params: types.CompletionParams,
) -> Optional[Union[types.CompletionList, Sequence[types.CompletionItem]]]:
    return generic_yaml_completer(
        ls,
        params,
        root_object_parser(),
    )


@lsp_hover(_DISPATCH_RULE)
def debputy_manifest_hover(
    ls: "DebputyLanguageServer",
    params: types.HoverParams,
) -> Optional[types.Hover]:
    return generic_yaml_hover(ls, params, lambda _: root_object_parser())
