# Copyright (c) 2021 Felix Fontein
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

# Note that this module util is **PRIVATE** to the collection. It can have breaking changes at any time.
# Do not use this from other collections or standalone plugins/modules!

from __future__ import annotations

import typing as t

if t.TYPE_CHECKING:
    import datetime  # pragma: no cover
    from collections.abc import Callable, Mapping, Sequence  # pragma: no cover

    from ansible.module_utils.basic import AnsibleModule  # pragma: no cover

    class OptionProvider(t.Protocol):  # pragma: no cover
        def get_option(self, option: str) -> t.Any: ...  # pragma: no cover

    ArgSpecType = t.Literal[  # pragma: no cover
        "bits",
        "bool",
        "bytes",
        "dict",
        "float",
        "int",
        "json",
        "jsonarg",
        "list",
        "path",
        "raw",
        "sid",
        "str",
    ]
    MutuallyExclusiveT = t.Union[  # pragma: no cover  # noqa: UP007
        Sequence[str], Sequence[Sequence[str]]
    ]
    MutuallyExclusiveMutT = list[Sequence[str]]  # pragma: no cover
    RequiredTogetherT = Sequence[Sequence[str]]  # pragma: no cover
    RequiredTogetherMutT = list[Sequence[str]]  # pragma: no cover
    RequiredOneOfT = Sequence[Sequence[str]]  # pragma: no cover
    RequiredOneOfMutT = list[Sequence[str]]  # pragma: no cover
    RequiredIfT = Sequence[  # pragma: no cover
        t.Union[  # noqa: UP007
            list[object],
            tuple[str, object, Sequence[str]],
            tuple[str, object, Sequence[str], bool],
        ]
    ]
    RequiredIfMutT = list[  # pragma: no cover
        t.Union[  # noqa: UP007
            list[object],
            tuple[str, object, Sequence[str]],
            tuple[str, object, Sequence[str], bool],
        ]
    ]
    RequiredByT = Mapping[str, Sequence[str]]  # pragma: no cover
    RequiredByMutT = dict[str, Sequence[str]]  # pragma: no cover

    class DeprecatedAlias(t.TypedDict):  # pragma: no cover
        name: str
        date: t.NotRequired[datetime.date | str]
        version: t.NotRequired[str]
        collection_name: str

    class OneArgumentSpecT(t.TypedDict):  # pragma: no cover
        type: t.NotRequired[ArgSpecType | Callable[[object], object]]
        elements: t.NotRequired[ArgSpecType]
        default: t.NotRequired[object]
        # For fallback elements, the first element of the sequence has to be a callable, the others sequences or dicts.
        # Unfortunately there is no way to specify this in a generic way...
        fallback: t.NotRequired[
            Sequence[
                Callable[[object], object] | Sequence[object] | Mapping[str, object]
            ]
        ]
        choices: t.NotRequired[Sequence[object]]
        context: t.NotRequired[Mapping[object, object]]
        required: t.NotRequired[bool]
        no_log: t.NotRequired[bool]
        aliases: t.NotRequired[Sequence[str]]
        apply_defaults: t.NotRequired[bool]
        removed_in_version: t.NotRequired[str]
        removed_at_date: t.NotRequired[datetime.date | str]
        removed_from_collection: t.NotRequired[str]
        options: t.NotRequired[Mapping[str, OneArgumentSpecT]]  # recursive!
        deprecated_aliases: t.NotRequired[Sequence[DeprecatedAlias]]

        mutually_exclusive: t.NotRequired[MutuallyExclusiveT]
        required_together: t.NotRequired[RequiredTogetherT]
        required_one_of: t.NotRequired[RequiredOneOfT]
        required_if: t.NotRequired[RequiredIfT]
        required_by: t.NotRequired[RequiredByT]

    ArgumentSpecT = Mapping[str, OneArgumentSpecT]  # pragma: no cover
    ArgumentSpecMutT = dict[str, OneArgumentSpecT]  # pragma: no cover


class ArgumentSpec:
    def __init__(
        self,
        argument_spec: ArgumentSpecT | None = None,
        *,
        required_together: RequiredTogetherT | None = None,
        required_if: RequiredIfT | None = None,
        required_one_of: RequiredOneOfT | None = None,
        mutually_exclusive: MutuallyExclusiveT | None = None,
        required_by: RequiredByT | None = None,
    ) -> None:
        self.argument_spec: ArgumentSpecMutT = {}
        self.required_together: RequiredTogetherMutT = []
        self.required_if: RequiredIfMutT = []
        self.required_one_of: RequiredOneOfMutT = []
        self.mutually_exclusive: MutuallyExclusiveMutT = []
        self.required_by: RequiredByMutT = {}
        if argument_spec:
            self.argument_spec.update(argument_spec)
        if required_together:
            self.required_together.extend(required_together)
        if required_if:
            self.required_if.extend(required_if)
        if required_one_of:
            self.required_one_of.extend(required_one_of)
        if mutually_exclusive:
            if all(isinstance(me, str) for me in mutually_exclusive):
                # mutually_exclusive is a Sequence[str]
                self.mutually_exclusive.append(mutually_exclusive)  # type: ignore
            else:
                self.mutually_exclusive.extend(mutually_exclusive)
        if required_by:
            self.required_by.update(required_by)

    def update(
        self,
        *,
        required_together: RequiredTogetherT | None = None,
        required_if: RequiredIfT | None = None,
        required_one_of: RequiredOneOfT | None = None,
        mutually_exclusive: MutuallyExclusiveT | None = None,
        required_by: RequiredByT | None = None,
    ) -> t.Self:
        if mutually_exclusive:
            self.mutually_exclusive.extend(mutually_exclusive)
        if required_together:
            self.required_together.extend(required_together)
        if required_one_of:
            self.required_one_of.extend(required_one_of)
        if required_if:
            self.required_if.extend(required_if)
        if required_by:
            for k, v in required_by.items():
                if k in self.required_by:
                    v = list(self.required_by[k]) + list(v)
                self.required_by[k] = v
        return self

    def merge(self, other: ArgumentSpec) -> t.Self:
        self.argument_spec.update(other.argument_spec)
        self.update(
            mutually_exclusive=other.mutually_exclusive,
            required_together=other.required_together,
            required_one_of=other.required_one_of,
            required_if=other.required_if,
            required_by=other.required_by,
        )
        return self

    def to_kwargs(self) -> dict[str, t.Any]:
        return {
            "argument_spec": self.argument_spec,
            "required_together": self.required_together,
            "required_if": self.required_if,
            "required_one_of": self.required_one_of,
            "mutually_exclusive": self.mutually_exclusive,
            "required_by": self.required_by,
        }


class ModuleOptionProvider:
    def __init__(self, module: AnsibleModule) -> None:
        self.module = module

    def get_option(self, option: str) -> t.Any:
        return self.module.params[option]
