"""
Prediction Cache Repository
Repository for prediction cache operations
"""

from typing import Optional, List, Dict, Any
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import text
from datetime import datetime, timedelta, timezone
import structlog
import hashlib

from .base import ForecastingBaseRepository
from app.models.predictions import PredictionCache
from shared.database.exceptions import DatabaseError, ValidationError

logger = structlog.get_logger()


class PredictionCacheRepository(ForecastingBaseRepository):
    """Repository for prediction cache operations"""
    
    def __init__(self, session: AsyncSession, cache_ttl: Optional[int] = 60):
        # Cache entries change very frequently, short cache time (1 minute)
        super().__init__(PredictionCache, session, cache_ttl)
    
    def _generate_cache_key(
        self,
        tenant_id: str,
        inventory_product_id: str,
        location: str,
        forecast_date: datetime
    ) -> str:
        """Generate cache key for prediction"""
        key_data = f"{tenant_id}:{inventory_product_id}:{location}:{forecast_date.isoformat()}"
        return hashlib.md5(key_data.encode()).hexdigest()
    
    async def cache_prediction(
        self,
        tenant_id: str,
        inventory_product_id: str,
        location: str,
        forecast_date: datetime,
        predicted_demand: float,
        confidence_lower: float,
        confidence_upper: float,
        model_id: str,
        expires_in_hours: int = 24
    ) -> PredictionCache:
        """Cache a prediction result"""
        try:
            cache_key = self._generate_cache_key(tenant_id, inventory_product_id, location, forecast_date)
            expires_at = datetime.now(timezone.utc) + timedelta(hours=expires_in_hours)
            
            cache_data = {
                "cache_key": cache_key,
                "tenant_id": tenant_id,
                "inventory_product_id": inventory_product_id,
                "location": location,
                "forecast_date": forecast_date,
                "predicted_demand": predicted_demand,
                "confidence_lower": confidence_lower,
                "confidence_upper": confidence_upper,
                "model_id": model_id,
                "expires_at": expires_at,
                "hit_count": 0
            }
            
            # Try to update existing cache entry first
            existing_cache = await self.get_by_field("cache_key", cache_key)
            if existing_cache:
                cache_entry = await self.update(existing_cache.id, cache_data)
                logger.debug("Updated cache entry", cache_key=cache_key)
            else:
                cache_entry = await self.create(cache_data)
                logger.debug("Created cache entry", cache_key=cache_key)
            
            return cache_entry
            
        except Exception as e:
            logger.error("Failed to cache prediction",
                        tenant_id=tenant_id,
                        inventory_product_id=inventory_product_id,
                        error=str(e))
            raise DatabaseError(f"Failed to cache prediction: {str(e)}")
    
    async def get_cached_prediction(
        self,
        tenant_id: str,
        inventory_product_id: str,
        location: str,
        forecast_date: datetime
    ) -> Optional[PredictionCache]:
        """Get cached prediction if valid"""
        try:
            cache_key = self._generate_cache_key(tenant_id, inventory_product_id, location, forecast_date)
            
            cache_entry = await self.get_by_field("cache_key", cache_key)
            
            if not cache_entry:
                logger.debug("Cache miss", cache_key=cache_key)
                return None
            
            # Check if cache entry has expired
            if cache_entry.expires_at < datetime.now(timezone.utc):
                logger.debug("Cache expired", cache_key=cache_key)
                await self.delete(cache_entry.id)
                return None
            
            # Increment hit count
            await self.update(cache_entry.id, {"hit_count": cache_entry.hit_count + 1})
            
            logger.debug("Cache hit", 
                        cache_key=cache_key,
                        hit_count=cache_entry.hit_count + 1)
            
            return cache_entry
            
        except Exception as e:
            logger.error("Failed to get cached prediction",
                        tenant_id=tenant_id,
                        inventory_product_id=inventory_product_id,
                        error=str(e))
            return None
    
    async def invalidate_cache(
        self,
        tenant_id: str,
        inventory_product_id: str = None,
        location: str = None
    ) -> int:
        """Invalidate cache entries"""
        try:
            conditions = ["tenant_id = :tenant_id"]
            params = {"tenant_id": tenant_id}
            
            if inventory_product_id:
                conditions.append("inventory_product_id = :inventory_product_id")
                params["inventory_product_id"] = inventory_product_id
                
            if location:
                conditions.append("location = :location")
                params["location"] = location
            
            query_text = f"""
                DELETE FROM prediction_cache
                WHERE {' AND '.join(conditions)}
            """
            
            result = await self.session.execute(text(query_text), params)
            invalidated_count = result.rowcount
            
            logger.info("Cache invalidated",
                       tenant_id=tenant_id,
                       inventory_product_id=inventory_product_id,
                       location=location,
                       invalidated_count=invalidated_count)
            
            return invalidated_count
            
        except Exception as e:
            logger.error("Failed to invalidate cache",
                        tenant_id=tenant_id,
                        error=str(e))
            raise DatabaseError(f"Cache invalidation failed: {str(e)}")
    
    async def cleanup_expired_cache(self) -> int:
        """Clean up expired cache entries"""
        try:
            query_text = """
                DELETE FROM prediction_cache 
                WHERE expires_at < :now
            """
            
            result = await self.session.execute(text(query_text), {"now": datetime.now(timezone.utc)})
            deleted_count = result.rowcount
            
            logger.info("Cleaned up expired cache entries",
                       deleted_count=deleted_count)
            
            return deleted_count
            
        except Exception as e:
            logger.error("Failed to cleanup expired cache",
                        error=str(e))
            raise DatabaseError(f"Cache cleanup failed: {str(e)}")
    
    async def get_cache_statistics(self, tenant_id: str = None) -> Dict[str, Any]:
        """Get cache performance statistics"""
        try:
            base_filter = "WHERE 1=1"
            params = {}
            
            if tenant_id:
                base_filter = "WHERE tenant_id = :tenant_id"
                params["tenant_id"] = tenant_id
            
            # Get cache statistics
            stats_query = text(f"""
                SELECT 
                    COUNT(*) as total_entries,
                    COUNT(CASE WHEN expires_at > :now THEN 1 END) as active_entries,
                    COUNT(CASE WHEN expires_at <= :now THEN 1 END) as expired_entries,
                    SUM(hit_count) as total_hits,
                    AVG(hit_count) as avg_hits_per_entry,
                    MAX(hit_count) as max_hits,
                    COUNT(DISTINCT inventory_product_id) as unique_products
                FROM prediction_cache 
                {base_filter}
            """)
            
            params["now"] = datetime.now(timezone.utc)
            
            result = await self.session.execute(stats_query, params)
            row = result.fetchone()
            
            if row:
                return {
                    "total_entries": int(row.total_entries or 0),
                    "active_entries": int(row.active_entries or 0),
                    "expired_entries": int(row.expired_entries or 0),
                    "total_hits": int(row.total_hits or 0),
                    "avg_hits_per_entry": float(row.avg_hits_per_entry or 0),
                    "max_hits": int(row.max_hits or 0),
                    "unique_products": int(row.unique_products or 0),
                    "cache_hit_ratio": round((row.total_hits / max(row.total_entries, 1)), 2)
                }
            
            return {
                "total_entries": 0,
                "active_entries": 0,
                "expired_entries": 0,
                "total_hits": 0,
                "avg_hits_per_entry": 0.0,
                "max_hits": 0,
                "unique_products": 0,
                "cache_hit_ratio": 0.0
            }
            
        except Exception as e:
            logger.error("Failed to get cache statistics",
                        tenant_id=tenant_id,
                        error=str(e))
            return {
                "total_entries": 0,
                "active_entries": 0,
                "expired_entries": 0,
                "total_hits": 0,
                "avg_hits_per_entry": 0.0,
                "max_hits": 0,
                "unique_products": 0,
                "cache_hit_ratio": 0.0
            }
    
    async def get_most_accessed_predictions(
        self,
        tenant_id: str = None,
        limit: int = 10
    ) -> List[Dict[str, Any]]:
        """Get most frequently accessed cached predictions"""
        try:
            base_filter = "WHERE hit_count > 0"
            params = {"limit": limit}
            
            if tenant_id:
                base_filter = "WHERE tenant_id = :tenant_id AND hit_count > 0"
                params["tenant_id"] = tenant_id
            
            query_text = f"""
                SELECT 
                    inventory_product_id,
                    location,
                    hit_count,
                    predicted_demand,
                    created_at,
                    expires_at
                FROM prediction_cache 
                {base_filter}
                ORDER BY hit_count DESC
                LIMIT :limit
            """
            
            result = await self.session.execute(text(query_text), params)
            
            popular_predictions = []
            for row in result.fetchall():
                popular_predictions.append({
                    "inventory_product_id": row.inventory_product_id,
                    "location": row.location,
                    "hit_count": int(row.hit_count),
                    "predicted_demand": float(row.predicted_demand),
                    "created_at": row.created_at.isoformat() if row.created_at else None,
                    "expires_at": row.expires_at.isoformat() if row.expires_at else None
                })
            
            return popular_predictions
            
        except Exception as e:
            logger.error("Failed to get most accessed predictions",
                        tenant_id=tenant_id,
                        error=str(e))
            return []