from __future__ import annotations

# DTFIX-MERGE: this module should probably move to module_utils/_internal

import sys
import dataclasses

# deprecated: description='typing.Self exists in Python 3.11+' python_version='3.10'
from ..compat import typing as t

from ansible.module_utils._internal._datatag import AnsibleSerializableDataclass

if sys.version_info >= (3, 10):
    # Using slots for reduced memory usage and improved performance.
    _dataclass_kwargs = dict(frozen=True, kw_only=True, slots=True)
else:
    # deprecated: description='always use dataclass slots and keyword-only args' python_version='3.9'
    _dataclass_kwargs = dict(frozen=True)


@dataclasses.dataclass(**_dataclass_kwargs)
class PluginInfo(AnsibleSerializableDataclass):
    """Information about a loaded plugin."""

    requested_name: str
    """The plugin name as requested, before resolving, which may be partially or fully qualified."""
    resolved_name: str
    """The resolved canonical plugin name; always fully-qualified for collection plugins."""
    type: str
    """The plugin type."""

    @property
    def description(self) -> str:
        """A brief description of the plugin, including name(s) and type."""
        name = repr(self.resolved_name)
        clarification = f' (requested as {self.requested_name!r})' if self.requested_name != self.resolved_name else ''

        if self.type in ("module", "modules"):
            # DTFIX-RELEASE: pluginloader or AnsiblePlugin needs a "type desc" property that doesn't suffer from legacy "inconsistencies" like this
            plugin_type = "module"
        elif self.type == "collection":
            # not a real plugin type, but used for tombstone errors generated by plugin loader
            plugin_type = self.type
        else:
            plugin_type = f'{self.type} plugin'

        return f'{name} {plugin_type}{clarification}'


@dataclasses.dataclass(**_dataclass_kwargs)
class Detail(AnsibleSerializableDataclass):
    """Message detail with optional source context and help text."""

    msg: str
    formatted_source_context: t.Optional[str] = None
    help_text: t.Optional[str] = None


@dataclasses.dataclass(**_dataclass_kwargs)
class SummaryBase(AnsibleSerializableDataclass):
    """Base class for an error/warning/deprecation summary with details (possibly derived from an exception __cause__ chain) and an optional traceback."""

    details: t.Tuple[Detail, ...]
    formatted_traceback: t.Optional[str] = None

    def format(self) -> str:
        """Returns a string representation of the warning details."""
        # DTFIX-RELEASE: should this borrow some of the message squashing features we use in get_chained_message?
        return ': '.join(detail.msg for detail in self.details)

    def _post_validate(self) -> None:
        if not self.details:
            raise ValueError(f'{type(self).__name__}.details cannot be empty')

    @classmethod
    def _from_details(cls, *details: Detail, formatted_traceback: t.Optional[str] = None, **kwargs) -> t.Self:
        """Utility factory method to avoid inline tuples."""
        return cls(details=details, formatted_traceback=formatted_traceback, **kwargs)


@dataclasses.dataclass(**_dataclass_kwargs)
class ErrorSummary(SummaryBase):
    """Error summary with details (possibly derived from an exception __cause__ chain) and an optional traceback."""


@dataclasses.dataclass(**_dataclass_kwargs)
class WarningSummary(SummaryBase):
    """Warning summary with details (possibly derived from an exception __cause__ chain) and an optional traceback."""


@dataclasses.dataclass(**_dataclass_kwargs)
class DeprecationSummary(WarningSummary):
    """Deprecation summary with details (possibly derived from an exception __cause__ chain) and an optional traceback."""

    version: t.Optional[str] = None
    date: t.Optional[str] = None
    plugin: t.Optional[PluginInfo] = None

    @property
    def collection_name(self) -> t.Optional[str]:
        if not self.plugin:
            return None

        parts = self.plugin.resolved_name.split('.')

        if len(parts) < 2:
            return None

        collection_name = '.'.join(parts[:2])

        # deprecated: description='enable the deprecation message for collection_name' core_version='2.23'
        # from ansible.module_utils.datatag import deprecate_value
        # collection_name = deprecate_value(collection_name, 'The `collection_name` property is deprecated.', removal_version='2.25')

        return collection_name

    def _as_simple_dict(self) -> t.Dict[str, t.Any]:
        """Returns a dictionary representation of the deprecation object in the format exposed to playbooks."""
        result = self._as_dict()
        result.pop('details')
        result.update(
            msg=self.format(),
            collection_name=self.collection_name,
        )

        return result
