from datetime import datetime
from typing import Type, TypeVar, Optional
from sqlalchemy import select, and_, or_
from sqlalchemy.ext.asyncio import AsyncSession
from app.repositories.base import BaseRepository
from app.models.base import Base

T = TypeVar("T", bound=Base)

class ConfigurationRepository:
    def __init__(self, db: AsyncSession):
        self.db = db

    async def get_active_config(self, model_class: Type[T], product_id: int, target_time: Optional[datetime] = None) -> Optional[T]:
        """
        Dynamically query active versioned configurations for a product at a specific point in time.
        """
        if target_time is None:
            target_time = datetime.utcnow()

        stmt = select(model_class).where(
            and_(
                model_class.product_id == product_id,
                model_class.is_active == True,
                model_class.effective_from <= target_time,
                or_(
                    model_class.effective_to.is_(None),
                    model_class.effective_to >= target_time
                )
            )
        ).order_by(model_class.version.desc())  # Resolve the highest version in case of overlap

        result = await self.db.execute(stmt)
        return result.scalars().first()
        
    async def get_all_active_configs(self, model_class: Type[T], product_id: int, target_time: Optional[datetime] = None) -> list[T]:
        """
        Dynamically query ALL active versioned configurations for a product at a specific point in time.
        Useful for models like ChargeConfiguration where multiple different configs are active simultaneously.
        """
        if target_time is None:
            target_time = datetime.utcnow()

        stmt = select(model_class).where(
            and_(
                model_class.product_id == product_id,
                model_class.is_active == True,
                model_class.effective_from <= target_time,
                or_(
                    model_class.effective_to.is_(None),
                    model_class.effective_to >= target_time
                )
            )
        ).order_by(model_class.version.desc())

        result = await self.db.execute(stmt)
        # Filter to keep only the highest version for each uniqueness constraint if needed, 
        # but since we sort by version desc, we can deduplicate by the unique business key (e.g., charge_master_id)
        configs = result.scalars().all()
        deduped = {}
        for c in configs:
            # If it's a ChargeConfiguration, deduplicate by charge_master_id
            if hasattr(c, 'charge_master_id'):
                if c.charge_master_id not in deduped:
                    deduped[c.charge_master_id] = c
            # If it's an AllocationConfiguration, deduplicate by sequence_type
            elif hasattr(c, 'sequence_type'):
                if c.sequence_type not in deduped:
                    deduped[c.sequence_type] = c
            # If it's a SpecificCollectionConfiguration, deduplicate by collection_type
            elif hasattr(c, 'collection_type'):
                if c.collection_type not in deduped:
                    deduped[c.collection_type] = c
            else:
                deduped[c.id] = c # fallback
        return list(deduped.values())
        
    async def save_config(self, config_obj: Base) -> Base:
        self.db.add(config_obj)
        await self.db.flush()
        return config_obj
