"""Generic backend classes."""

from __future__ import annotations

from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Optional

from waldur_api_client.client import AuthenticatedClient
from waldur_api_client.models.offering_user import OfferingUser
from waldur_api_client.models.resource import Resource as WaldurResource

from waldur_site_agent.backend import logger, structures, utils

if TYPE_CHECKING:
    from waldur_site_agent.common.structures import Offering
from waldur_site_agent.backend.clients import BaseClient, UnknownClient
from waldur_site_agent.backend.exceptions import BackendError, DuplicateResourceError

UNKNOWN_BACKEND_TYPE = "unknown"


class BaseBackend(ABC):
    """Backend class with implemented generic methods and other abstract methods."""

    # Capability flag: Set to True for backends where usage can decrease
    # (e.g., storage backends reporting current state, not accumulated usage)
    supports_decreasing_usage: bool = False

    # Capability flag: Set to True for backends that use async order tracking
    # via order.backend_id (e.g., Waldur-to-Waldur federation).
    # When enabled, the processor checks order.backend_id on EXECUTING orders
    # and calls check_pending_order() to track remote order completion.
    supports_async_orders: bool = False

    def __init__(self, backend_settings: dict, backend_components: dict[str, dict]) -> None:
        """Init backend info."""
        self.backend_type = "abstract"
        self.backend_settings = backend_settings
        self.backend_components = backend_components
        self.client: BaseClient = UnknownClient()
        self.service_provider_uuid: str | None = None
        self.timezone: str = ""

    @abstractmethod
    def ping(self, raise_exception: bool = False) -> bool:
        """Check if the backend system is reachable and operational.

        Called by diagnostics and health checks before any agent mode starts processing.
        Implementations should attempt a lightweight connection to the backend system
        (e.g., listing resources or running a version command).

        Args:
            raise_exception: If True, raise BackendError on failure instead of returning False.

        Returns:
            True if the backend is reachable, False otherwise.

        No-op guidance:
            Return False if your backend has no health check mechanism.
        """

    @abstractmethod
    def diagnostics(self) -> bool:
        """Log diagnostic information about the backend and return status.

        Called by the ``waldur_site_diagnostics`` CLI tool. Implementations should
        log useful debugging information (version, connectivity, configuration)
        and return whether the backend is healthy.

        Returns:
            True if diagnostics pass, False if issues are detected.

        No-op guidance:
            Log a message indicating diagnostics are not supported and return True.
        """

    @abstractmethod
    def list_components(self) -> list[str]:
        """Return a list of computing component types available on the backend.

        Called during diagnostics to verify that the backend exposes the same
        components that are configured in ``backend_components``.

        Returns:
            List of component type identifiers (e.g., ``["cpu", "mem", "gres/gpu"]``
            for SLURM, or ``["deposit"]`` for MOAB).

        No-op guidance:
            Return an empty list if component discovery is not supported.
        """

    @abstractmethod
    def _get_usage_report(self, resource_backend_ids: list[str]) -> dict:
        """Collect usage report for the specified resources.

        Called by the ``report`` agent mode (via ``pull_resource``) and
        ``membership_sync`` mode. Must return a nested dict keyed by resource
        backend ID, containing per-user usage and a ``TOTAL_ACCOUNT_USAGE`` entry.

        Args:
            resource_backend_ids: List of backend resource identifiers to report on.

        Returns:
            Nested dict with the following structure::

                {
                    "<resource_backend_id>": {
                        "TOTAL_ACCOUNT_USAGE": {"cpu": 1000, "mem": 2048},
                        "user1": {"cpu": 500, "mem": 1024},
                        "user2": {"cpu": 500, "mem": 1024},
                    }
                }

            Component keys must match those defined in ``backend_components``.
            Values are in **Waldur units** (after applying ``unit_factor`` conversion).
            The ``TOTAL_ACCOUNT_USAGE`` key is required and should be the sum of all
            per-user usages.

        No-op guidance:
            Return an empty dict ``{}`` if usage reporting is not supported.
        """

    def get_usage_report_for_period(
        self,
        resource_backend_ids: list[str],  # noqa: ARG002
        year: int,  # noqa: ARG002
        month: int,  # noqa: ARG002
    ) -> dict:
        """Collect usage report for a specific historical billing period.

        Override this method to enable multi-period usage reporting. By default,
        returns an empty dict, meaning the backend does not support historical
        usage queries. The processor will skip past periods for such backends.

        Args:
            resource_backend_ids: List of backend resource identifiers to report on.
            year: Year of the billing period (e.g., 2024).
            month: Month of the billing period (1-12).

        Returns:
            Same structure as ``_get_usage_report``: nested dict keyed by
            resource backend ID with per-user and TOTAL_ACCOUNT_USAGE entries.
            Return ``{}`` if historical reporting is not supported.
        """
        return {}

    @abstractmethod
    def downscale_resource(self, resource_backend_id: str) -> bool:
        """Downscale the resource, restricting its capabilities.

        Called by ``membership_sync`` mode when a Waldur resource enters
        a downscaled state (e.g., allocation nearing exhaustion). The exact
        behavior is backend-specific (e.g., SLURM sets a restrictive QoS).

        Args:
            resource_backend_id: Backend identifier for the resource.

        Returns:
            True if downscaling succeeded, False otherwise.

        No-op guidance:
            Return True if your backend has no concept of resource downscaling.
        """

    @abstractmethod
    def pause_resource(self, resource_backend_id: str) -> bool:
        """Pause the resource, preventing all usage.

        Called by ``membership_sync`` mode when a Waldur resource is paused
        (e.g., allocation fully exhausted or administratively suspended).

        Args:
            resource_backend_id: Backend identifier for the resource.

        Returns:
            True if pausing succeeded, False otherwise.

        No-op guidance:
            Return True if your backend has no concept of resource pausing.
        """

    @abstractmethod
    def restore_resource(self, resource_backend_id: str) -> bool:
        """Restore the resource to normal operation after downscaling or pausing.

        Called by ``membership_sync`` mode when a Waldur resource returns to
        normal state (e.g., allocation topped up or administrative hold lifted).

        Args:
            resource_backend_id: Backend identifier for the resource.

        Returns:
            True if restoration succeeded, False otherwise.

        No-op guidance:
            Return True if your backend has no concept of resource pausing/downscaling.
        """

    @abstractmethod
    def get_resource_metadata(self, resource_backend_id: str) -> dict:
        """Get backend-specific resource metadata for reporting to Waldur.

        Called by ``membership_sync`` mode to fetch backend-specific state
        (e.g., current QoS for SLURM). The returned dict is stored as
        resource metadata in Waldur Mastermind.

        Args:
            resource_backend_id: Backend identifier for the resource.

        Returns:
            Dict of metadata key-value pairs (e.g., ``{"qos": "normal"}``
            for SLURM). Keys should be meaningful to the Waldur UI.

        No-op guidance:
            Return an empty dict ``{}`` if no metadata is available.
        """

    def setup_target_event_subscriptions(
        self,
        source_offering: Offering,
        user_agent: str = "",
        global_proxy: str = "",
    ) -> list:
        """Set up event subscriptions on target systems for async order tracking.

        Override in backends that support async order creation and want instant
        notifications from the target system when orders complete.

        Args:
            source_offering: The source Waldur offering configuration.
            user_agent: User agent string for API calls.
            global_proxy: Optional proxy configuration.

        Returns:
            List of StompConsumer tuples for lifecycle management.
        """
        del source_offering, user_agent, global_proxy
        return []

    def check_pending_order(self, order_backend_id: str) -> bool:
        """Check if an async order on a remote system has completed.

        Called when a source order has a non-empty backend_id indicating
        an async creation was started on a remote backend.

        Args:
            order_backend_id: The source order's backend_id (= target order UUID)

        Returns:
            True if target order completed successfully, False if still pending.

        Raises:
            BackendError: If the target order failed/was cancelled.
        """
        del order_backend_id
        return True  # Default: no async orders, always "complete"

    def list_resources(self) -> list[structures.BackendResourceInfo]:
        """List resources in the the backend."""
        resources = self.client.list_resources()
        return [
            structures.BackendResourceInfo(
                backend_id=resource.name, parent_id=resource.organization
            )
            for resource in resources
        ]

    def create_user_homedirs(self, usernames: set[str], umask: str = "0700") -> None:
        """Create homedirs for users."""
        logger.info("Creating homedirs for users")
        for username in usernames:
            try:
                self.client.create_linux_user_homedir(username, umask)
                logger.info("Homedir for user %s has been created", username)
            except BackendError as err:
                logger.exception(
                    "Unable to create user homedir for %s, reason: %s",
                    username,
                    err,
                )

    @abstractmethod
    def _collect_resource_limits(
        self, waldur_resource: WaldurResource
    ) -> tuple[dict[str, int], dict[str, int]]:
        """Collect and convert resource limits for both backend and Waldur.

        Called during resource creation (``order_process`` mode) to translate
        Waldur resource limits into backend-native limits. Each component's
        ``unit_factor`` (from ``backend_components``) is used for conversion:
        ``backend_value = waldur_value * unit_factor``.

        Args:
            waldur_resource: The Waldur resource containing requested limits.

        Returns:
            A tuple of two dicts:
            - **backend_limits**: Limits in backend-native units for setting on the
              backend (e.g., ``{"cpu": 60000, "mem": 61440}`` in SLURM minutes).
            - **waldur_limits**: Limits in Waldur units to store back in Waldur
              (e.g., ``{"cpu": 1, "mem": 1}`` in k-Hours/GB-Hours).

        No-op guidance:
            Return ``({}, {})`` if your backend does not support resource limits.
        """

    def pull_resources(
        self, waldur_resources: list[WaldurResource]
    ) -> dict[str, tuple[WaldurResource, structures.BackendResourceInfo]]:
        """Pull data of resources available in the backend."""
        report = {}
        for waldur_resource in waldur_resources:
            backend_id = waldur_resource.backend_id
            try:
                backend_resource_info = self.pull_resource(waldur_resource)
                if backend_resource_info is not None:
                    report[backend_id] = (waldur_resource, backend_resource_info)
            except Exception as e:
                logger.exception("Error while pulling resource [%s]: %s", backend_id, e)
        return report

    def pull_resource(
        self, waldur_resource: WaldurResource
    ) -> Optional[structures.BackendResourceInfo]:
        """Pull resource from backend."""
        try:
            backend_id = waldur_resource.backend_id
            if not backend_id:
                logger.warning("Backend ID is missing for resource %s", waldur_resource.name)
                return None
            backend_resource_info = self._pull_backend_resource(backend_id)
            if backend_resource_info is None:
                return None
        except Exception as e:
            logger.exception("Error while pulling resource [%s]: %s", backend_id, e)
            return None
        else:
            return backend_resource_info

    def _pull_backend_resource(
        self, resource_backend_id: str
    ) -> Optional[structures.BackendResourceInfo]:
        """Pull resource data from the backend."""
        logger.info("Pulling resource %s", resource_backend_id)
        resource_backend_info = self.client.get_resource(resource_backend_id)

        if resource_backend_info is None:
            logger.warning("There is no resource with ID %s in the backend", resource_backend_id)
            return None

        users = self.client.list_resource_users(resource_backend_id)

        report = self._get_usage_report([resource_backend_id])
        usage = report.get(resource_backend_id)

        if usage is None:
            empty_usage = dict.fromkeys(self.backend_components, 0)
            usage = {"TOTAL_ACCOUNT_USAGE": empty_usage}

        return structures.BackendResourceInfo(
            users=users,
            usage=usage,
        )

    def _pre_delete_resource(self, waldur_resource: WaldurResource) -> None:
        """Perform actions before deleting the resource in the backend.

        Called by ``delete_resource`` before the backend resource is removed.
        Override to perform cleanup (e.g., SLURM cancels active jobs and
        removes all user associations).

        Args:
            waldur_resource: Resource data from Waldur marketplace.
        """
        del waldur_resource

    def delete_resource(
        self,
        waldur_resource: WaldurResource,
        **kwargs: str,
    ) -> None:
        """Delete resource from the backend."""
        resource_backend_id = waldur_resource.backend_id
        if not resource_backend_id.strip():
            logger.warning("Empty backend_id for resource, skipping deletion")
            return

        if self.client.get_resource(resource_backend_id) is None:
            logger.warning(
                "No resource with ID %s is in %s", resource_backend_id, self.backend_type
            )
            return

        self._pre_delete_resource(waldur_resource)
        self._delete_resource_safely(resource_backend_id)

        if "project_slug" in kwargs:
            project_backend_id = self._get_project_backend_id(kwargs["project_slug"])
            if (
                len(
                    [
                        resource
                        for resource in self.client.list_resources()
                        if resource.organization == project_backend_id
                        and resource.name != project_backend_id
                    ]
                )
                == 0
            ):
                self._delete_resource_safely(project_backend_id)

        self.post_delete_resource(waldur_resource)

    def post_delete_resource(
        self,
        waldur_resource: WaldurResource,
    ) -> None:
        """Perform actions after resource deletion on the backend.

        Called by ``delete_resource`` after the backend resource is removed.
        Override to perform post-deletion tasks (e.g., deleting child offerings
        linked to the deleted resource).

        Args:
            waldur_resource: Resource data from Waldur marketplace.
        """
        del waldur_resource

    def _delete_resource_safely(self, resource_backend_id: str) -> None:
        if self.client.get_resource(resource_backend_id):
            logger.info(
                "Deleting the resource with ID %s from %s", resource_backend_id, self.backend_type
            )
            self.client.delete_resource(resource_backend_id)
        else:
            logger.warning(
                "No resource with ID %s is in %s", resource_backend_id, self.backend_type
            )

    def _create_backend_resource(
        self,
        resource_backend_id: str,
        resource_name: str,
        resource_organization: str,
        resource_parent_name: Optional[str] = None,
    ) -> bool:
        logger.info(
            "Creating resource %s in %s backend (backend ID = %s)",
            resource_name,
            self.backend_type,
            resource_backend_id,
        )
        if self.client.get_resource(resource_backend_id) is None:
            self.client.create_resource(
                name=resource_backend_id,
                description=resource_name,
                organization=resource_organization,
                parent_name=resource_parent_name,
            )
            return True
        logger.info("The resource with ID %s already exists in the cluster", resource_backend_id)
        return False  # Return False if resource isn't created

    def create_resource(
        self,
        waldur_resource: WaldurResource,
        user_context: Optional[dict] = None,
    ) -> structures.BackendResourceInfo:
        """Create resource on the backend.

        Creates necessary objects and resources and sets up resource limits.

        Args:
            waldur_resource: Resource data from Waldur marketplace
            user_context: Optional user context including team members and offering users
        """
        logger.info("Creating resource in the backend")

        # Generate backend ID
        resource_backend_id = self._get_resource_backend_id(waldur_resource.slug)

        # Create resource with generated ID
        return self.create_resource_with_id(
            waldur_resource, resource_backend_id, user_context
        )

    @abstractmethod
    def _pre_create_resource(
        self,
        waldur_resource: WaldurResource,
        user_context: Optional[dict] = None,
    ) -> None:
        """Perform actions prior to resource creation on the backend.

        Called by ``create_resource_with_id`` before the backend resource is created.
        Use this to set up prerequisite structures (e.g., SLURM creates parent
        customer and project accounts in its hierarchy).

        Args:
            waldur_resource: Resource data from Waldur marketplace.
            user_context: Optional dict with team members, offering users,
                and pre-resolved data (plan_quotas, ssh_keys).

        Raises:
            BackendError: If prerequisite setup fails (aborts resource creation).

        No-op guidance:
            Use ``pass`` if no pre-creation setup is needed.
        """

    def create_resource_with_id(
        self,
        waldur_resource: WaldurResource,
        resource_backend_id: str,
        user_context: Optional[dict] = None,
    ) -> structures.BackendResourceInfo:
        """Create resource with a specific backend ID.

        This method creates a resource with a predetermined backend ID,
        without any retry logic or uniqueness checking. The calling code
        (typically the processor) is responsible for ensuring the ID is unique.

        Args:
            waldur_resource: Resource data from Waldur marketplace
            resource_backend_id: The specific backend ID to use for the resource
            user_context: Optional user context including team members and offering users

        Returns:
            Created backend resource information

        Raises:
            BackendError: If resource creation fails
        """
        logger.info("Creating resource in the backend with ID: %s", resource_backend_id)

        # Actions prior to resource creation
        self._pre_create_resource(waldur_resource, user_context)

        # Create resource with specific ID
        project_backend_id = self._get_project_backend_id(waldur_resource.project_slug)
        if not self._create_backend_resource(
            resource_backend_id, waldur_resource.name, project_backend_id, project_backend_id
        ):
            raise DuplicateResourceError(resource_backend_id)

        # Setup limits
        waldur_limits = self._setup_resource_limits(resource_backend_id, waldur_resource)
        backend_resource_info = structures.BackendResourceInfo(
            backend_id=resource_backend_id,
            limits=waldur_limits,
        )

        # Actions after resource creation
        self.post_create_resource(
            backend_resource_info, waldur_resource, user_context
        )
        return backend_resource_info

    def _setup_resource_limits(
        self, resource_backend_id: str, waldur_resource: WaldurResource
    ) -> dict[str, int]:
        """Setup resource limits for the resource in backend."""
        resource_backend_limits, waldur_limits = self._collect_resource_limits(waldur_resource)

        if not resource_backend_limits:
            logger.info("Skipping setting of limits")
            return {}

        # Convert limits for logging
        converted_limits = {
            key: value // self.backend_components[key].get("unit_factor", 1)
            for key, value in resource_backend_limits.items()
        }

        limits_str = utils.prettify_limits(converted_limits, self.backend_components)
        logger.info("Setting resource backend limits to: \n%s", limits_str)
        self.client.set_resource_limits(resource_backend_id, resource_backend_limits)
        return waldur_limits

    def post_create_resource(
        self,
        resource: structures.BackendResourceInfo,
        waldur_resource: WaldurResource,
        user_context: Optional[dict] = None,
    ) -> None:
        """Perform actions after resource creation on the backend.

        Called by ``create_resource_with_id`` after the resource is created and
        limits are set. Override to perform post-creation tasks (e.g., SLURM
        creates home directories for users proactively).

        To push metadata back to Waldur, set ``resource.backend_metadata``
        to a dict. The processor will push it via the Waldur API.

        Args:
            resource: The newly created backend resource info (includes backend_id, limits).
            waldur_resource: Original resource data from Waldur marketplace.
            user_context: Optional dict with team members and offering users.
        """
        # Not used in base implementation
        del resource, waldur_resource, user_context

    def add_users_to_resource(
        self, waldur_resource: WaldurResource, user_ids: set[str], **kwargs: dict
    ) -> set[str]:
        """Add specified users to the resource on the backend."""
        del kwargs
        resource_backend_id = waldur_resource.backend_id
        if len(user_ids) < 1:
            logger.info("No new users to add")
            return set()

        logger.info(
            "Adding %s users to resource %s on backend: %s",
            len(user_ids),
            resource_backend_id,
            " ,".join(user_ids),
        )
        added_users = set()
        for username in user_ids:
            try:
                succeeded = self.add_user(waldur_resource, username)
                if succeeded:
                    added_users.add(username)
            except BackendError as e:
                logger.exception(
                    "Unable to add user %s to resource %s, details: %s",
                    username,
                    resource_backend_id,
                    e,
                )

        return added_users

    def add_user(self, waldur_resource: WaldurResource, username: str) -> bool:
        """Add association between user and backend resource if it doesn't exists."""
        resource_backend_id = waldur_resource.backend_id
        if not resource_backend_id.strip():
            message = "Empty backend ID for resource"
            raise BackendError(message)

        logger.info("Adding user %s to resource %s", username, resource_backend_id)
        if not username:
            logger.warning("Username is blank, skipping creation of association")
            return False

        if not self.client.get_association(username, resource_backend_id):
            logger.info("Creating association between %s and %s", username, resource_backend_id)
            try:
                self.client.create_association(
                    username,
                    resource_backend_id,
                    self.backend_settings.get("default_account", "root"),
                )
                logger.info("Created association between %s and %s", username, resource_backend_id)
            except BackendError as err:
                logger.exception("Unable to create association on backend: %s", err)
                return False
        else:
            logger.info("Association already exists, skipping creation")
        return True

    def remove_users_from_resource(
        self, waldur_resource: WaldurResource, usernames: set[str]
    ) -> list[str]:
        """Remove specified users from the resource on the backend."""
        resource_backend_id = waldur_resource.backend_id
        if len(usernames) < 1:
            logger.info("No users to remove")
            return []

        logger.info(
            "Removing %s users from resource %s on backend: %s",
            len(usernames),
            resource_backend_id,
            " ,".join(usernames),
        )
        removed_users = []
        for username in usernames:
            try:
                succeeded = self.remove_user(waldur_resource, username)
                if succeeded:
                    removed_users.append(username)
            except BackendError as e:
                logger.exception(
                    "Unable to remove user %s from resource %s, details: %s",
                    username,
                    resource_backend_id,
                    e,
                )
        return removed_users

    def _pre_delete_user_actions(self, resource_backend_id: str, username: str) -> None:
        """Perform actions before removing the user from the resource.

        Called by ``remove_user`` before deleting the user-resource association.
        Override to perform per-user cleanup (e.g., cancelling active jobs).

        Args:
            resource_backend_id: Backend identifier for the resource.
            username: Username to be removed.
        """
        del resource_backend_id, username

    def remove_user(self, waldur_resource: WaldurResource, username: str) -> bool:
        """Delete association between user and backend resource if it exists."""
        resource_backend_id = waldur_resource.backend_id
        if not resource_backend_id.strip():
            message = "Empty resource backend ID"
            raise BackendError(message)

        logger.info("Removing user %s from resource %s", username, resource_backend_id)

        if self.client.get_association(username, resource_backend_id):
            logger.info("Deleting association between %s and %s", username, resource_backend_id)
            try:
                self._pre_delete_user_actions(resource_backend_id, username)
                self.client.delete_association(username, resource_backend_id)
            except BackendError as err:
                logger.exception("Unable to delete association in the backend: %s", err)
                return False
        return True

    def update_user_attributes(self, username: str, attributes: dict) -> None:
        """Forward user profile attribute updates to the backend.

        Called when Waldur publishes an OFFERING_USER attribute_update event.
        Override in backends that need to sync user attributes.

        Args:
            username: The username of the user whose attributes changed.
            attributes: Dict of user profile attributes to forward.
        """
        del username, attributes

    def sync_offering_user_usernames(
        self,
        waldur_a_offering_uuid: str,
        waldur_rest_client: AuthenticatedClient,
    ) -> bool:
        """Pull offering user usernames from backend and update on Waldur A.

        Override in backends where the backend assigns its own usernames
        to offering users (e.g., Waldur federation). Default: no-op.

        Args:
            waldur_a_offering_uuid: UUID of the offering on Waldur A.
            waldur_rest_client: Authenticated client for Waldur A API.

        Returns:
            True if any usernames were updated, False otherwise.
        """
        del waldur_a_offering_uuid, waldur_rest_client
        return False

    def process_existing_users(self, existing_users: set[str]) -> None:
        """Process existing users on the backend.

        This method can be overridden by subclasses to implement
        specific logic for processing existing users.
        """
        del existing_users

    def set_resource_limits(self, resource_backend_id: str, limits: dict[str, int]) -> None:
        """Set limits for the resource on the backend."""
        self.client.set_resource_limits(resource_backend_id, limits)

    def get_resource_limits(self, resource_backend_id: str) -> dict[str, int]:
        """Get limits for the resource on the backend."""
        return self.client.get_resource_limits(resource_backend_id)

    def get_resource_user_limits(self, resource_backend_id: str) -> dict[str, dict[str, int]]:
        """Get limits for the resource users on the backend."""
        return self.client.get_resource_user_limits(resource_backend_id)

    def set_resource_user_limits(
        self, resource_backend_id: str, username: str, limits: dict[str, int]
    ) -> None:
        """Set limits for a specific user in a resource on the backend."""
        logger.info(
            "Setting user %s limits to %s for resource %s", username, limits, resource_backend_id
        )
        self.client.set_resource_user_limits(resource_backend_id, username, limits)

    def _get_resource_backend_id(self, resource_slug: str, prefix: str = "") -> str:
        prefix = self.backend_settings.get("allocation_prefix", "")
        return f"{prefix}{resource_slug}".lower()

    def _get_project_backend_id(self, slug: str) -> str:
        return f"{self.backend_settings.get('project_prefix', '')}{slug}"

    def _get_customer_backend_id(self, slug: str) -> str:
        return f"{self.backend_settings.get('customer_prefix', '')}{slug}"


class UnknownBackend(BaseBackend):
    """Common class for unknown backends."""

    def __init__(self) -> None:
        """Placeholder."""
        super().__init__({}, {})
        self.backend_type = UNKNOWN_BACKEND_TYPE

    def ping(self, _: bool = False) -> bool:
        """Placeholder."""
        return False

    def diagnostics(self) -> bool:
        """Placeholder."""
        return True

    def list_components(self) -> list[str]:
        """Placeholder."""
        return []

    def _pre_create_resource(
        self,
        waldur_resource: WaldurResource,
        user_context: Optional[dict] = None,
    ) -> None:
        """Placeholder."""

    def pull_resources(
        self, _: list[WaldurResource]
    ) -> dict[str, tuple[WaldurResource, structures.BackendResourceInfo]]:
        """Placeholder."""
        return {}

    def delete_resource(
        self,
        waldur_resource: WaldurResource,
        **kwargs: str,
    ) -> None:
        """Placeholder."""
        del kwargs, waldur_resource

    def create_resource(
        self,
        _: dict,
        user_context: Optional[dict] = None,
    ) -> structures.BackendResourceInfo:
        """Placeholder."""
        del user_context
        return structures.BackendResourceInfo()

    def downscale_resource(self, resource_backend_id: str) -> bool:
        """Placeholder."""
        del resource_backend_id
        return False

    def pause_resource(self, resource_backend_id: str) -> bool:
        """Placeholder."""
        del resource_backend_id
        return False

    def restore_resource(self, resource_backend_id: str) -> bool:
        """Placeholder."""
        del resource_backend_id
        return False

    def get_resource_metadata(self, _: str) -> dict:
        """Placeholder."""
        return {}

    def add_users_to_resource(
        self, waldur_resource: WaldurResource, user_ids: set[str], **kwargs: dict
    ) -> set[str]:
        """Placeholder."""
        del kwargs, waldur_resource
        return user_ids

    def set_resource_limits(self, _: str, limits: dict[str, int]) -> None:
        """Placeholder."""
        del limits

    def _collect_resource_limits(self, _: WaldurResource) -> tuple[dict[str, int], dict[str, int]]:
        return {"": 0}, {"": 0}

    def _pull_backend_resource(self, _: str) -> Optional[structures.BackendResourceInfo]:
        return None

    def _get_usage_report(self, _: list[str]) -> dict:
        return {}



class AbstractUsernameManagementBackend(ABC):
    """Base class for username management backends.

    Username management backends are responsible for creating and managing
    usernames for offering users. They support the new OfferingUser state model
    with enhanced error handling that can include comments and URLs for user guidance.

    When username generation encounters issues that require user action,
    backends can raise exceptions with detailed comments and optional URLs:
    - OfferingUserAccountLinkingRequiredError: When user needs to link existing account
    - OfferingUserAdditionalValidationRequiredError: When additional validation needed

    Both exceptions support an optional comment_url parameter to provide
    users with links to forms, documentation, or other resources.
    """

    def __init__(
        self,
        backend_settings: dict | None = None,
        offering: Optional[Offering] = None,
    ) -> None:
        """Initialize the username management backend.

        Args:
            backend_settings: Backend-specific configuration dict.
            offering: The offering configuration this backend is associated with.
        """
        self.backend_settings = backend_settings or {}
        self.offering = offering

    @abstractmethod
    def generate_username(self, offering_user: OfferingUser) -> str:
        """Generate username based on offering user details."""

    @abstractmethod
    def get_username(self, offering_user: OfferingUser) -> Optional[str]:
        """Get username in local IDP if exists."""

    def get_or_create_username(self, offering_user: OfferingUser) -> str:
        """Get username from local IDP if exists, otherwise request generation."""
        logger.info(
            "Retrieving username for offering user %s (email %s) from the backend",
            offering_user.uuid,
            offering_user.user_email,
        )
        username = self.get_username(offering_user)
        if username:
            return username
        logger.info(
            "Generating username for offering user %s (email %s) in the backend",
            offering_user.uuid,
            offering_user.user_email,
        )
        return self.generate_username(offering_user)

    def sync_user_profiles(self, offering_users: list[OfferingUser]) -> None:
        """Push user profiles to external system before membership sync.

        Override in backends that need to ensure users exist on a remote
        system before membership synchronization runs. Default: no-op.
        """
        del offering_users

    def deactivate_users(self, usernames: set[str]) -> None:
        """Deactivate users no longer in the offering.

        Override in backends that need to remove departed users from
        external systems. Default: no-op.
        """
        del usernames


class UnknownUsernameManagementBackend(AbstractUsernameManagementBackend):
    """Class for an unknown username management backend."""

    def __init__(self, **kwargs: object) -> None:  # noqa: ARG002
        """Initialize the unknown username management backend."""
        super().__init__()

    def generate_username(self, offering_user: OfferingUser) -> str:
        """Generate username based on offering user details."""
        del offering_user
        return ""

    def get_username(self, offering_user: OfferingUser) -> Optional[str]:
        """Get username in local IDP if exists."""
        del offering_user
        return None
