import logging

from redis.exceptions import RedisError

from .encrypt import EncryptedRedis

logger = logging.getLogger(__name__)


class CredentialManager:
    """
    Manages router credentials and configuration data in Redis.
    Handles migrations, verification, updates, and deletions of router data.
    """

    SENSITIVE_KEYS = {
        "pfsenseV1": ["client_id", "client_token"],
        "pfsenseV2": ["api_key"],
        "opnsense": ["api_key", "api_secret"],
    }

    REQUIRED_KEYS = {
        "pfsenseV1": ["hostname", "client_id", "client_token", "verify_ssl"],
        "pfsenseV2": ["hostname", "api_key", "verify_ssl"],
        "opnsense": ["api_key", "api_secret", "base_url", "verify_ssl"],
    }

    def __init__(self):
        self._redis = EncryptedRedis()

    def list_clients(self) -> set[str]:
        """
        Lists all router clients based on Redis key names.

        Returns:
            Set of client names

        Raises:
            RedisError: If there's an error communicating with Redis
        """
        try:
            keys = self._redis.list_keys("routers:*")
            clients = set()
            for key in keys:
                # Split key into components and extract client name
                key_parts = key.decode("utf-8").split(":")
                if len(key_parts) >= 2:
                    clients.add(key_parts[1])
            return clients
        except RedisError as e:
            logger.error(f"Failed to list clients from Redis: {e}")
            raise

    def migrate_and_verify_router_data(self) -> dict[str, list[str]]:
        """
        Migrates existing pfSense router data to the new format, adds the 'type' key,
        and verifies that all named routers are correctly set up.

        Returns:
            A dictionary with two keys: 'migrated' and 'errors'.
            'migrated' contains a list of successfully migrated router names.
            'errors' contains a list of router names that couldn't be migrated or verified.

        Raises:
            RedisError: If there's an error communicating with Redis
        """
        result = {"migrated": [], "errors": []}

        try:
            clients = self.list_clients()
        except RedisError as e:
            logger.error(f"Failed to list clients during migration: {e}")
            result["errors"].append(f"Failed to list clients: {str(e)}")
            return result

        for client in clients:
            try:
                # Check if 'type' key exists
                router_type = self._redis.get_decrypted(f"routers:{client}:type")

                if router_type is None or router_type == "pfsense":
                    # Assume it's a pfSenseV1 router if 'type' is not set or is set to 'pfsense'
                    self._redis.set_encrypted(f"routers:{client}:type", "pfsenseV1")

                    # Verify pfSense required keys
                    missing_keys = []
                    for key in self.REQUIRED_KEYS["pfsenseV1"]:
                        value = self._redis.get_decrypted(f"routers:{client}:{key}")
                        if value is None:
                            missing_keys.append(key)

                    if missing_keys:
                        result["errors"].append(f"Router '{client}': Missing required keys: {', '.join(missing_keys)}")
                        continue

                    result["migrated"].append(client)
                else:
                    # Verify based on the existing router type
                    if router_type not in self.REQUIRED_KEYS:
                        result["errors"].append(f"Router '{client}': Unknown router type '{router_type}'")
                        continue

                    missing_keys = []
                    for key in self.REQUIRED_KEYS[router_type]:
                        value = self._redis.get_decrypted(f"routers:{client}:{key}")
                        if value is None:
                            missing_keys.append(key)

                    if missing_keys:
                        result["errors"].append(f"Router '{client}': Missing required keys: {', '.join(missing_keys)}")
                        continue

                    result["migrated"].append(client)

            except RedisError as e:
                logger.error(f"Redis error while migrating router {client}: {e}")
                result["errors"].append(f"Router '{client}': Redis error: {str(e)}")
            except Exception as e:
                logger.error(f"Unexpected error while migrating router {client}: {e}")
                result["errors"].append(f"Router '{client}': Unexpected error: {str(e)}")

        return result

    def verify_router_setup(self, router_name: str) -> bool:
        """
        Verifies that a specific router is correctly set up.

        Args:
            router_name: The name of the router to verify.

        Returns:
            True if the router is correctly set up, False otherwise

        Raises:
            RedisError: If there's an error communicating with Redis
        """
        try:
            router_type = self._redis.get_decrypted(f"routers:{router_name}:type")
            if router_type is None or router_type not in self.REQUIRED_KEYS:
                return False

            for key in self.REQUIRED_KEYS[router_type]:
                value = self._redis.get_decrypted(f"routers:{router_name}:{key}")
                if value is None:
                    return False

            return True

        except RedisError as e:
            logger.error(f"Failed to verify router {router_name}: {e}")
            raise

    def get_router_value(self, router_name: str, key: str) -> str | None:
        """
        Gets a specific value for a router.

        Args:
            router_name: The name of the router
            key: The key to retrieve

        Returns:
            The value if it exists, None otherwise

        Raises:
            RedisError: If there's an error communicating with Redis
        """
        try:
            return self._redis.get_decrypted(f"routers:{router_name}:{key}")
        except RedisError as e:
            logger.error(f"Failed to get value {key} for router {router_name}: {e}")
            raise

    def get_router_type(self, router_name: str) -> str | None:
        """
        Gets the type of a specific router.

        Args:
            router_name: The name of the router

        Returns:
            The router type if it exists, None otherwise

        Raises:
            RedisError: If there's an error communicating with Redis
        """
        return self.get_router_value(router_name, "type")

    def get_all_routers_data(self) -> list[dict]:
        """
        Returns a list of dictionaries containing all non-sensitive data for all routers.

        Returns:
            List of dicts containing router data, excluding sensitive credentials

        Raises:
            RedisError: If there's an error communicating with Redis
        """
        result = []
        try:
            clients = self.list_clients()
        except RedisError as e:
            logger.error(f"Failed to list clients while getting router data: {e}")
            raise

        for client in clients:
            router_type = self.get_router_type(client)
            if not router_type:
                logger.warning(f"Router {client} has no type, skipping")
                continue

            if router_type not in self.REQUIRED_KEYS:
                logger.warning(f"Router {client} has an unknown type '{router_type}', skipping")
                continue

            router_data = {"name": client, "type": router_type}
            sensitive_keys = self.SENSITIVE_KEYS.get(router_type, [])
            for key in self.REQUIRED_KEYS[router_type]:
                if key not in sensitive_keys:
                    try:
                        value = self.get_router_value(client, key)
                        if value is not None:
                            router_data[key] = value
                    except RedisError as e:
                        logger.error(f"Failed to get value {key} for router {client}: {e}")
                        raise
            result.append(router_data)
        return result

    def delete_router(self, router_name: str) -> bool:
        """
        Deletes all data associated with a router.

        Args:
            router_name: The name of the router to delete

        Returns:
            True if deletion was successful, False otherwise

        Raises:
            RedisError: If there's an error communicating with Redis
        """
        try:
            router_type = self.get_router_type(router_name)
            if not router_type:
                logger.info(f"No router type found for {router_name}, cannot delete")
                return False

            # Delete all keys for the router
            keys = self._redis.list_keys(f"routers:{router_name}:*")
            if not keys:
                logger.info(f"No keys found for router {router_name}, cannot delete")
                return False

            # Delete each key and collect results
            results = []
            for key in keys:
                decoded_key = key.decode("utf-8")
                success = self._redis.delete(decoded_key)
                logger.info(f"Deleting key {decoded_key}: {'successful' if success else 'failed'}")
                results.append(success)

            # Only return True if all deletions were successful
            return all(results)

        except RedisError as e:
            logger.error(f"Failed to delete router {router_name}: {e}")
            raise

    def update_router_values(self, router_name: str, values: dict) -> dict[str, list[str]]:
        """
        Updates one or more values for a specific router.

        Args:
            router_name: The name of the router to update
            values: Dictionary of key-value pairs to update

        Returns:
            Dictionary with 'updated' and 'errors' lists

        Raises:
            RedisError: If there's an error communicating with Redis
        """
        result = {"updated": [], "errors": []}

        try:
            router_type = self.get_router_type(router_name)
            if not router_type:
                result["errors"].append(f"Router {router_name} not found or has no type")
                return result

            # Validate keys being updated
            for key in values:
                if key == "type":
                    if values[key] not in self.REQUIRED_KEYS:
                        result["errors"].append(f"Invalid router type: {values[key]}")
                        return result
                elif key not in self.REQUIRED_KEYS[router_type]:
                    result["errors"].append(f"Invalid key for {router_type}: {key}")
                    return result

            # Perform updates
            for key, value in values.items():
                try:
                    self._redis.set_encrypted(f"routers:{router_name}:{key}", str(value))
                    result["updated"].append(key)
                except RedisError as e:
                    error_msg = f"Failed to update {key}: {str(e)}"
                    logger.error(error_msg)
                    result["errors"].append(error_msg)

            return result

        except RedisError as e:
            error_msg = f"Failed to update router {router_name}: {str(e)}"
            logger.error(error_msg)
            result["errors"].append(error_msg)
            return result
