"""
Performance Metric Repository
Repository for model performance metrics in forecasting service
"""

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

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

logger = structlog.get_logger()


class PerformanceMetricRepository(ForecastingBaseRepository):
    """Repository for model performance metrics operations"""
    
    def __init__(self, session: AsyncSession, cache_ttl: Optional[int] = 900):
        # Performance metrics are stable, longer cache time (15 minutes)
        super().__init__(ModelPerformanceMetric, session, cache_ttl)
    
    async def create_metric(self, metric_data: Dict[str, Any]) -> ModelPerformanceMetric:
        """Create a new performance metric"""
        try:
            # Validate metric data
            validation_result = self._validate_forecast_data(
                metric_data, 
                ["model_id", "tenant_id", "inventory_product_id", "evaluation_date"]
            )
            
            if not validation_result["is_valid"]:
                raise ValidationError(f"Invalid metric data: {validation_result['errors']}")
            
            metric = await self.create(metric_data)
            
            logger.info("Performance metric created",
                       metric_id=metric.id,
                       model_id=metric.model_id,
                       tenant_id=metric.tenant_id,
                       inventory_product_id=metric.inventory_product_id)
            
            return metric
            
        except ValidationError:
            raise
        except Exception as e:
            logger.error("Failed to create performance metric",
                        model_id=metric_data.get("model_id"),
                        error=str(e))
            raise DatabaseError(f"Failed to create metric: {str(e)}")
    
    async def get_metrics_by_model(
        self,
        model_id: str,
        skip: int = 0,
        limit: int = 100
    ) -> List[ModelPerformanceMetric]:
        """Get all metrics for a model"""
        try:
            return await self.get_multi(
                filters={"model_id": model_id},
                skip=skip,
                limit=limit,
                order_by="evaluation_date",
                order_desc=True
            )
        except Exception as e:
            logger.error("Failed to get metrics by model",
                        model_id=model_id,
                        error=str(e))
            raise DatabaseError(f"Failed to get metrics: {str(e)}")
    
    async def get_latest_metric_for_model(self, model_id: str) -> Optional[ModelPerformanceMetric]:
        """Get the latest performance metric for a model"""
        try:
            metrics = await self.get_multi(
                filters={"model_id": model_id},
                limit=1,
                order_by="evaluation_date",
                order_desc=True
            )
            return metrics[0] if metrics else None
        except Exception as e:
            logger.error("Failed to get latest metric for model",
                        model_id=model_id,
                        error=str(e))
            raise DatabaseError(f"Failed to get latest metric: {str(e)}")
    
    async def get_performance_trends(
        self,
        tenant_id: str,
        inventory_product_id: str = None,
        days: int = 30
    ) -> Dict[str, Any]:
        """Get performance trends over time"""
        try:
            start_date = datetime.now(timezone.utc) - timedelta(days=days)
            
            conditions = [
                "tenant_id = :tenant_id",
                "evaluation_date >= :start_date"
            ]
            params = {
                "tenant_id": tenant_id,
                "start_date": start_date
            }
            
            if inventory_product_id:
                conditions.append("inventory_product_id = :inventory_product_id")
                params["inventory_product_id"] = inventory_product_id
            
            query_text = f"""
                SELECT 
                    DATE(evaluation_date) as date,
                    inventory_product_id,
                    AVG(mae) as avg_mae,
                    AVG(mape) as avg_mape,
                    AVG(rmse) as avg_rmse,
                    AVG(accuracy_score) as avg_accuracy,
                    COUNT(*) as measurement_count
                FROM model_performance_metrics
                WHERE {' AND '.join(conditions)}
                GROUP BY DATE(evaluation_date), inventory_product_id
                ORDER BY date DESC, inventory_product_id
            """
            
            result = await self.session.execute(text(query_text), params)
            
            trends = []
            for row in result.fetchall():
                trends.append({
                    "date": row.date.isoformat() if row.date else None,
                    "inventory_product_id": row.inventory_product_id,
                    "metrics": {
                        "avg_mae": float(row.avg_mae) if row.avg_mae else None,
                        "avg_mape": float(row.avg_mape) if row.avg_mape else None,
                        "avg_rmse": float(row.avg_rmse) if row.avg_rmse else None,
                        "avg_accuracy": float(row.avg_accuracy) if row.avg_accuracy else None
                    },
                    "measurement_count": int(row.measurement_count)
                })
            
            return {
                "tenant_id": tenant_id,
                "inventory_product_id": inventory_product_id,
                "period_days": days,
                "trends": trends,
                "total_measurements": len(trends)
            }
            
        except Exception as e:
            logger.error("Failed to get performance trends",
                        tenant_id=tenant_id,
                        inventory_product_id=inventory_product_id,
                        error=str(e))
            return {
                "tenant_id": tenant_id,
                "inventory_product_id": inventory_product_id,
                "period_days": days,
                "trends": [],
                "total_measurements": 0
            }
    
    async def cleanup_old_metrics(self, days_old: int = 180) -> int:
        """Clean up old performance metrics"""
        return await self.cleanup_old_records(days_old=days_old)

    async def bulk_create_metrics(self, metrics: List[ModelPerformanceMetric]) -> int:
        """
        Bulk insert performance metrics for validation

        Args:
            metrics: List of ModelPerformanceMetric objects to insert

        Returns:
            Number of metrics created
        """
        try:
            if not metrics:
                return 0

            self.session.add_all(metrics)
            await self.session.flush()

            logger.info(
                "Bulk created performance metrics",
                count=len(metrics)
            )

            return len(metrics)

        except Exception as e:
            logger.error(
                "Failed to bulk create performance metrics",
                count=len(metrics),
                error=str(e)
            )
            raise DatabaseError(f"Failed to bulk create metrics: {str(e)}")

    async def get_metrics_by_date_range(
        self,
        tenant_id: str,
        start_date: datetime,
        end_date: datetime,
        inventory_product_id: Optional[str] = None
    ) -> List[ModelPerformanceMetric]:
        """
        Get performance metrics for a date range

        Args:
            tenant_id: Tenant identifier
            start_date: Start of date range
            end_date: End of date range
            inventory_product_id: Optional product filter

        Returns:
            List of performance metrics
        """
        try:
            filters = {
                "tenant_id": tenant_id
            }

            if inventory_product_id:
                filters["inventory_product_id"] = inventory_product_id

            # Build custom query for date range
            query_text = """
                SELECT *
                FROM model_performance_metrics
                WHERE tenant_id = :tenant_id
                AND evaluation_date >= :start_date
                AND evaluation_date <= :end_date
            """

            params = {
                "tenant_id": tenant_id,
                "start_date": start_date,
                "end_date": end_date
            }

            if inventory_product_id:
                query_text += " AND inventory_product_id = :inventory_product_id"
                params["inventory_product_id"] = inventory_product_id

            query_text += " ORDER BY evaluation_date DESC"

            result = await self.session.execute(text(query_text), params)
            rows = result.fetchall()

            # Convert rows to ModelPerformanceMetric objects
            metrics = []
            for row in rows:
                metric = ModelPerformanceMetric()
                for column in row._mapping.keys():
                    setattr(metric, column, row._mapping[column])
                metrics.append(metric)

            return metrics

        except Exception as e:
            logger.error(
                "Failed to get metrics by date range",
                tenant_id=tenant_id,
                error=str(e)
            )
            raise DatabaseError(f"Failed to get metrics: {str(e)}")