import logging
import time

from pyfsense_client.v2.client import PfSenseV2Client
from pyfsense_client.v2.exceptions import ValidationError
from pyfsense_client.v2.models import (
    APIResponse,
    FirewallAlias,
    FirewallAliasCreate,
    FirewallAliasUpdate,
)

from ..alias.types import Alias

logger = logging.getLogger(__name__)


class PfSenseV2MetaClient:
    """
    A meta client for PfSense V2 API that adds caching and name-based operations.
    This wrapper maintains a cache of alias names to their full data to reduce
    the number of API calls needed for operations that require alias IDs.
    """

    # Default cache TTL of 300 seconds (5 minutes)
    DEFAULT_CACHE_TTL = 300

    def __init__(
        self,
        client: PfSenseV2Client,
        auto_init_cache: bool = True,
        cache_ttl: int = 300,
    ):
        """
        Initialize the meta client.

        Args:
            client: The base PfSenseV2Client instance
            auto_init_cache: Whether to initialize the cache immediately (default: True)
            cache_ttl: Time in seconds before cache is considered stale (default: 300)
        """
        self._client = client
        self._cache: dict[str, FirewallAlias] = {}
        self._cache_initialized = False
        self._last_refresh: float = 0
        self._cache_ttl = cache_ttl if cache_ttl is not None else self.DEFAULT_CACHE_TTL

        if auto_init_cache:
            self.refresh_cache()

    def _is_cache_stale(self) -> bool:
        """Check if the cache is stale based on TTL."""
        return time.time() - self._last_refresh > self._cache_ttl

    def _ensure_cache_initialized(self) -> None:
        """Initialize the cache if not already done or if stale"""
        if not self._cache_initialized or self._is_cache_stale():
            self.refresh_cache()

    def refresh_cache(self) -> list[FirewallAlias]:
        """Refresh the entire alias cache"""
        aliases = self._client.get_firewall_aliases()
        # Pre-allocate the dictionary size for better performance
        self._cache = {alias.name: alias for alias in aliases}
        self._cache_initialized = True
        self._last_refresh = time.time()
        logger.debug(f"Refreshed alias cache with {len(self._cache)} entries")
        return aliases

    def list_aliases(self) -> list[FirewallAlias]:
        """
        Returns a list of FirewallAlias object from the API.
        This method will refresh the cache each time it is called.
        """
        return self.refresh_cache()

    def get_alias_by_name(self, name: str) -> FirewallAlias | None:
        """
        Get an alias by name from the cache, refreshing if necessary.
        Returns None if the alias doesn't exist.
        """
        self._ensure_cache_initialized()
        return self._cache.get(name)

    def get_alias_id_by_name(self, name: str) -> int | None:
        """
        Get an alias ID by name from the cache, refreshing if necessary.
        Returns None if the alias doesn't exist.
        """
        self._ensure_cache_initialized()
        alias = self._cache.get(name)
        return alias.id if alias else None

    def create_firewall_alias(self, alias: FirewallAliasCreate) -> FirewallAlias:
        """
        Create a new firewall alias and update the cache.
        """
        response = self._client.create_firewall_alias(alias)
        # Update cache with the new alias
        self._cache[response.name] = response
        # Update last refresh time since we modified the cache
        self._last_refresh = time.time()
        return response

    def update_firewall_alias_by_name(self, alias: Alias, original_name: str | None = None) -> FirewallAlias:
        """
        Update a firewall alias using an Alias instance.

        Args:
            alias (Alias): An instance containing the new alias values.
                Its `name` represents the new name, if renaming is desired.
            original_name (str | None): The current (old) name of the alias.
                If not provided, `alias.name` is used to locate the existing alias.

        Returns:
            FirewallAlias: The updated firewall alias as returned by the API.

        Raises:
            ValidationError: If the alias to update is not found, or if renaming
                would cause a conflict with an existing alias.
        """
        self._ensure_cache_initialized()
        lookup_name = original_name if original_name is not None else alias.name
        cached_alias = self._cache.get(lookup_name)
        if not cached_alias:
            raise ValidationError(f"Alias '{lookup_name}' not found", None)

        # If renaming, ensure the new name doesn't already exist in the cache.
        if alias.name != lookup_name and alias.name in self._cache:
            raise ValidationError(f"Alias name '{alias.name}' already exists", None)

        update_model = FirewallAliasUpdate(
            id=cached_alias.id,
            name=alias.name,
            type=alias.alias_type,
            descr=alias.description,
            address=alias.content,
            detail=alias.detail,
        )

        response = self._client.update_firewall_alias(update_model)

        # Update the cache: remove the old key if the alias was renamed, then add/update the entry.
        if lookup_name != response.name:
            self._cache.pop(lookup_name, None)
        self._cache[response.name] = response
        # Update last refresh time since we modified the cache
        self._last_refresh = time.time()

        return response

    def delete_firewall_alias_by_name(self, name: str) -> APIResponse:
        """
        Delete a firewall alias by name instead of ID.

        Raises:
            ValidationError: If the alias doesn't exist
        """
        self._ensure_cache_initialized()
        alias = self._cache.get(name)
        if not alias:
            raise ValidationError(f"Alias '{name}' not found", None)

        response = self._client.delete_firewall_alias(alias.id)
        # Remove from cache
        self._cache.pop(name, None)
        # Update last refresh time since we modified the cache
        self._last_refresh = time.time()
        return response

    def batch_update_aliases(self, updates: list[tuple[str, FirewallAliasUpdate]]) -> list[FirewallAlias]:
        """
        Efficiently update multiple aliases in batch.
        This method minimizes API calls by using the cache.

        Args:
            updates: List of tuples containing (alias_name, update_data)

        Returns:
            List of updated FirewallAlias objects

        Raises:
            ValidationError: If any alias doesn't exist
        """
        self._ensure_cache_initialized()
        results = []

        for name, update_data in updates:
            alias = self._cache.get(name)
            if not alias:
                raise ValidationError(f"Alias '{name}' not found", None)

            # Ensure the update data has the correct ID
            update_data.id = alias.id
            response = self._client.update_firewall_alias(update_data)

            # Update cache
            self._cache[response.name] = response
            results.append(response)

        # Update last refresh time after batch operation
        self._last_refresh = time.time()
        return results

    def apply_firewall_changes(self) -> APIResponse:
        """Apply pending firewall changes."""
        return self._client.apply_firewall_changes()

    # Delegate any other methods to the underlying client
    def __getattr__(self, name):
        return getattr(self._client, name)
