import logging
from typing import Type, TypeVar, Optional
from sqlalchemy.ext.asyncio import AsyncSession
from app.repositories.configuration import ConfigurationRepository
from app.services.cache_service import cache_service
from app.database import Base

logger = logging.getLogger(__name__)
T = TypeVar("T", bound=Base)

class ConfigurationService:
    @staticmethod
    def _get_cache_key(product_id: int, model_name: str) -> str:
        return f"lms_config:{product_id}:{model_name.lower()}"

    @classmethod
    async def get_config(
        cls,
        db: AsyncSession,
        model_class: Type[T],
        product_id: int
    ) -> Optional[T]:
        """
        Retrieves active configuration using Redis cache first, falling back to database.
        """
        model_name = model_class.__name__
        cache_key = cls._get_cache_key(product_id, model_name)
        
        # 1. Try Cache
        cached_data = cache_service.get(cache_key)
        if cached_data:
            # Map dictionary back to model instance
            # In a production setup, we can instantiate SQLAlchemy objects or return direct DTOs
            instance = model_class()
            for key, val in cached_data.items():
                if hasattr(instance, key):
                    setattr(instance, key, val)
            return instance

        # 2. Cache Miss: Query Database
        repo = ConfigurationRepository(db)
        config_instance = await repo.get_active_config(model_class, product_id)
        
        if config_instance:
            # Save to Cache (Convert columns to dictionary first)
            config_dict = {}
            for col in config_instance.__table__.columns:
                val = getattr(config_instance, col.name)
                # Serialize dates/decimals to strings/floats for JSON compatibility
                if hasattr(val, "isoformat"):
                    config_dict[col.name] = val.isoformat()
                elif hasattr(val, "to_eng_string") or hasattr(val, "as_tuple"):
                    config_dict[col.name] = float(val)
                else:
                    config_dict[col.name] = val
                    
            cache_service.set(cache_key, config_dict)
            
        return config_instance

    @classmethod
    async def get_all_configs(
        cls,
        db: AsyncSession,
        model_class: Type[T],
        product_id: int
    ) -> list[T]:
        """
        Retrieves all active configurations for a given product and model class.
        Bypasses caching for now as lists are slightly more complex to serialize/deserialize
        robustly and this is primarily used for multi-record models like ChargeConfiguration.
        """
        repo = ConfigurationRepository(db)
        return await repo.get_all_active_configs(model_class, product_id)


    @classmethod
    async def save_config(
        cls,
        db: AsyncSession,
        config_obj: Base
    ) -> Base:
        """
        Saves a new configuration version and invalidates the corresponding Redis cache key.
        """
        repo = ConfigurationRepository(db)
        saved = await repo.save_config(config_obj)
        
        # Invalidate Cache
        model_name = config_obj.__class__.__name__
        cache_key = cls._get_cache_key(config_obj.product_id, model_name)
        cache_service.invalidate(cache_key)
        
        return saved
