"""
Log Repository
Repository for notification log operations
"""

from typing import Optional, List, Dict, Any
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, text, and_
from datetime import datetime, timedelta
import structlog
import json

from .base import NotificationBaseRepository
from app.models.notifications import NotificationLog, NotificationStatus
from shared.database.exceptions import DatabaseError, ValidationError

logger = structlog.get_logger()


class LogRepository(NotificationBaseRepository):
    """Repository for notification log operations"""
    
    def __init__(self, session: AsyncSession, cache_ttl: Optional[int] = 120):
        # Logs are very dynamic, very short cache time (2 minutes)
        super().__init__(NotificationLog, session, cache_ttl)
    
    async def create_log_entry(self, log_data: Dict[str, Any]) -> NotificationLog:
        """Create a new notification log entry"""
        try:
            # Validate log data
            validation_result = self._validate_notification_data(
                log_data, 
                ["notification_id", "attempt_number", "status"]
            )
            
            if not validation_result["is_valid"]:
                raise ValidationError(f"Invalid log data: {validation_result['errors']}")
            
            # Set default values
            if "attempted_at" not in log_data:
                log_data["attempted_at"] = datetime.utcnow()
            
            # Serialize metadata if it's a dict
            if "log_metadata" in log_data and isinstance(log_data["log_metadata"], dict):
                log_data["log_metadata"] = json.dumps(log_data["log_metadata"])
            
            # Serialize provider response if it's a dict
            if "provider_response" in log_data and isinstance(log_data["provider_response"], dict):
                log_data["provider_response"] = json.dumps(log_data["provider_response"])
            
            # Create log entry
            log_entry = await self.create(log_data)
            
            logger.debug("Notification log entry created",
                        log_id=log_entry.id,
                        notification_id=log_entry.notification_id,
                        attempt_number=log_entry.attempt_number,
                        status=log_entry.status.value)
            
            return log_entry
            
        except ValidationError:
            raise
        except Exception as e:
            logger.error("Failed to create log entry",
                        notification_id=log_data.get("notification_id"),
                        error=str(e))
            raise DatabaseError(f"Failed to create log entry: {str(e)}")
    
    async def get_logs_for_notification(
        self, 
        notification_id: str,
        skip: int = 0,
        limit: int = 50
    ) -> List[NotificationLog]:
        """Get all log entries for a specific notification"""
        try:
            return await self.get_multi(
                filters={"notification_id": notification_id},
                skip=skip,
                limit=limit,
                order_by="attempt_number",
                order_desc=False
            )
            
        except Exception as e:
            logger.error("Failed to get logs for notification",
                        notification_id=notification_id,
                        error=str(e))
            return []
    
    async def get_latest_log_for_notification(
        self, 
        notification_id: str
    ) -> Optional[NotificationLog]:
        """Get the most recent log entry for a notification"""
        try:
            logs = await self.get_multi(
                filters={"notification_id": notification_id},
                limit=1,
                order_by="attempt_number",
                order_desc=True
            )
            return logs[0] if logs else None
            
        except Exception as e:
            logger.error("Failed to get latest log for notification",
                        notification_id=notification_id,
                        error=str(e))
            return None
    
    async def get_failed_delivery_logs(
        self,
        hours_back: int = 24,
        provider: str = None,
        limit: int = 100
    ) -> List[NotificationLog]:
        """Get failed delivery logs for analysis"""
        try:
            cutoff_time = datetime.utcnow() - timedelta(hours=hours_back)
            
            conditions = [
                "status = 'failed'",
                "attempted_at >= :cutoff_time"
            ]
            params = {"cutoff_time": cutoff_time, "limit": limit}
            
            if provider:
                conditions.append("provider = :provider")
                params["provider"] = provider
            
            query_text = f"""
                SELECT * FROM notification_logs
                WHERE {' AND '.join(conditions)}
                ORDER BY attempted_at DESC
                LIMIT :limit
            """
            
            result = await self.session.execute(text(query_text), params)
            
            logs = []
            for row in result.fetchall():
                record_dict = dict(row._mapping)
                # Convert enum string back to enum object
                record_dict["status"] = NotificationStatus(record_dict["status"])
                log_entry = self.model(**record_dict)
                logs.append(log_entry)
            
            return logs
            
        except Exception as e:
            logger.error("Failed to get failed delivery logs",
                        hours_back=hours_back,
                        provider=provider,
                        error=str(e))
            return []
    
    async def get_delivery_performance_stats(
        self,
        hours_back: int = 24,
        provider: str = None
    ) -> Dict[str, Any]:
        """Get delivery performance statistics"""
        try:
            cutoff_time = datetime.utcnow() - timedelta(hours=hours_back)
            
            conditions = ["attempted_at >= :cutoff_time"]
            params = {"cutoff_time": cutoff_time}
            
            if provider:
                conditions.append("provider = :provider")
                params["provider"] = provider
            
            where_clause = " AND ".join(conditions)
            
            # Get overall statistics
            stats_query = text(f"""
                SELECT 
                    COUNT(*) as total_attempts,
                    COUNT(CASE WHEN status = 'sent' OR status = 'delivered' THEN 1 END) as successful_attempts,
                    COUNT(CASE WHEN status = 'failed' THEN 1 END) as failed_attempts,
                    AVG(response_time_ms) as avg_response_time_ms,
                    MIN(response_time_ms) as min_response_time_ms,
                    MAX(response_time_ms) as max_response_time_ms
                FROM notification_logs 
                WHERE {where_clause}
            """)
            
            result = await self.session.execute(stats_query, params)
            stats = result.fetchone()
            
            total = stats.total_attempts or 0
            successful = stats.successful_attempts or 0
            failed = stats.failed_attempts or 0
            
            success_rate = (successful / total * 100) if total > 0 else 0
            failure_rate = (failed / total * 100) if total > 0 else 0
            
            # Get error breakdown
            error_query = text(f"""
                SELECT error_code, COUNT(*) as count
                FROM notification_logs 
                WHERE {where_clause} AND status = 'failed' AND error_code IS NOT NULL
                GROUP BY error_code
                ORDER BY count DESC
                LIMIT 10
            """)
            
            result = await self.session.execute(error_query, params)
            error_breakdown = {row.error_code: row.count for row in result.fetchall()}
            
            # Get provider breakdown if not filtering by provider
            provider_breakdown = {}
            if not provider:
                provider_query = text(f"""
                    SELECT provider, 
                           COUNT(*) as total,
                           COUNT(CASE WHEN status = 'sent' OR status = 'delivered' THEN 1 END) as successful
                    FROM notification_logs 
                    WHERE {where_clause} AND provider IS NOT NULL
                    GROUP BY provider
                    ORDER BY total DESC
                """)
                
                result = await self.session.execute(provider_query, params)
                for row in result.fetchall():
                    provider_success_rate = (row.successful / row.total * 100) if row.total > 0 else 0
                    provider_breakdown[row.provider] = {
                        "total": row.total,
                        "successful": row.successful,
                        "success_rate_percent": round(provider_success_rate, 2)
                    }
            
            return {
                "total_attempts": total,
                "successful_attempts": successful,
                "failed_attempts": failed,
                "success_rate_percent": round(success_rate, 2),
                "failure_rate_percent": round(failure_rate, 2),
                "avg_response_time_ms": float(stats.avg_response_time_ms or 0),
                "min_response_time_ms": int(stats.min_response_time_ms or 0),
                "max_response_time_ms": int(stats.max_response_time_ms or 0),
                "error_breakdown": error_breakdown,
                "provider_breakdown": provider_breakdown,
                "hours_analyzed": hours_back
            }
            
        except Exception as e:
            logger.error("Failed to get delivery performance stats",
                        hours_back=hours_back,
                        provider=provider,
                        error=str(e))
            return {
                "total_attempts": 0,
                "successful_attempts": 0,
                "failed_attempts": 0,
                "success_rate_percent": 0.0,
                "failure_rate_percent": 0.0,
                "avg_response_time_ms": 0.0,
                "min_response_time_ms": 0,
                "max_response_time_ms": 0,
                "error_breakdown": {},
                "provider_breakdown": {},
                "hours_analyzed": hours_back
            }
    
    async def get_logs_by_provider(
        self,
        provider: str,
        hours_back: int = 24,
        status: NotificationStatus = None,
        limit: int = 100
    ) -> List[NotificationLog]:
        """Get logs for a specific provider"""
        try:
            cutoff_time = datetime.utcnow() - timedelta(hours=hours_back)
            
            conditions = [
                "provider = :provider",
                "attempted_at >= :cutoff_time"
            ]
            params = {"provider": provider, "cutoff_time": cutoff_time, "limit": limit}
            
            if status:
                conditions.append("status = :status")
                params["status"] = status.value
            
            query_text = f"""
                SELECT * FROM notification_logs
                WHERE {' AND '.join(conditions)}
                ORDER BY attempted_at DESC
                LIMIT :limit
            """
            
            result = await self.session.execute(text(query_text), params)
            
            logs = []
            for row in result.fetchall():
                record_dict = dict(row._mapping)
                # Convert enum string back to enum object
                record_dict["status"] = NotificationStatus(record_dict["status"])
                log_entry = self.model(**record_dict)
                logs.append(log_entry)
            
            return logs
            
        except Exception as e:
            logger.error("Failed to get logs by provider",
                        provider=provider,
                        error=str(e))
            return []
    
    async def cleanup_old_logs(self, days_old: int = 30) -> int:
        """Clean up old notification logs"""
        try:
            cutoff_date = datetime.utcnow() - timedelta(days=days_old)
            
            # Only delete logs for successfully delivered or permanently failed notifications
            query_text = """
                DELETE FROM notification_logs 
                WHERE attempted_at < :cutoff_date
                AND status IN ('delivered', 'failed')
            """
            
            result = await self.session.execute(text(query_text), {"cutoff_date": cutoff_date})
            deleted_count = result.rowcount
            
            logger.info("Cleaned up old notification logs",
                       deleted_count=deleted_count,
                       days_old=days_old)
            
            return deleted_count
            
        except Exception as e:
            logger.error("Failed to cleanup old logs", error=str(e))
            raise DatabaseError(f"Cleanup failed: {str(e)}")
    
    async def get_notification_timeline(
        self, 
        notification_id: str
    ) -> Dict[str, Any]:
        """Get complete timeline for a notification including all attempts"""
        try:
            logs = await self.get_logs_for_notification(notification_id)
            
            timeline = []
            for log in logs:
                entry = {
                    "attempt_number": log.attempt_number,
                    "status": log.status.value,
                    "attempted_at": log.attempted_at.isoformat() if log.attempted_at else None,
                    "provider": log.provider,
                    "provider_message_id": log.provider_message_id,
                    "response_time_ms": log.response_time_ms,
                    "error_code": log.error_code,
                    "error_message": log.error_message
                }
                
                # Parse metadata if present
                if log.log_metadata:
                    try:
                        entry["metadata"] = json.loads(log.log_metadata)
                    except json.JSONDecodeError:
                        entry["metadata"] = log.log_metadata
                
                # Parse provider response if present
                if log.provider_response:
                    try:
                        entry["provider_response"] = json.loads(log.provider_response)
                    except json.JSONDecodeError:
                        entry["provider_response"] = log.provider_response
                
                timeline.append(entry)
            
            # Calculate summary statistics
            total_attempts = len(logs)
            successful_attempts = len([log for log in logs if log.status in [NotificationStatus.SENT, NotificationStatus.DELIVERED]])
            failed_attempts = len([log for log in logs if log.status == NotificationStatus.FAILED])
            
            avg_response_time = 0
            if logs:
                response_times = [log.response_time_ms for log in logs if log.response_time_ms is not None]
                avg_response_time = sum(response_times) / len(response_times) if response_times else 0
            
            return {
                "notification_id": notification_id,
                "total_attempts": total_attempts,
                "successful_attempts": successful_attempts,
                "failed_attempts": failed_attempts,
                "avg_response_time_ms": round(avg_response_time, 2),
                "timeline": timeline
            }
            
        except Exception as e:
            logger.error("Failed to get notification timeline",
                        notification_id=notification_id,
                        error=str(e))
            return {
                "notification_id": notification_id,
                "error": str(e),
                "timeline": []
            }
    
    async def get_retry_analysis(self, days_back: int = 7) -> Dict[str, Any]:
        """Analyze retry patterns and success rates"""
        try:
            cutoff_date = datetime.utcnow() - timedelta(days=days_back)
            
            # Get retry statistics
            retry_query = text("""
                SELECT 
                    attempt_number,
                    COUNT(*) as total_attempts,
                    COUNT(CASE WHEN status = 'sent' OR status = 'delivered' THEN 1 END) as successful_attempts
                FROM notification_logs 
                WHERE attempted_at >= :cutoff_date
                GROUP BY attempt_number
                ORDER BY attempt_number
            """)
            
            result = await self.session.execute(retry_query, {"cutoff_date": cutoff_date})
            
            retry_stats = {}
            for row in result.fetchall():
                success_rate = (row.successful_attempts / row.total_attempts * 100) if row.total_attempts > 0 else 0
                retry_stats[row.attempt_number] = {
                    "total_attempts": row.total_attempts,
                    "successful_attempts": row.successful_attempts,
                    "success_rate_percent": round(success_rate, 2)
                }
            
            # Get common failure patterns
            failure_query = text("""
                SELECT 
                    error_code,
                    attempt_number,
                    COUNT(*) as count
                FROM notification_logs 
                WHERE attempted_at >= :cutoff_date 
                AND status = 'failed' 
                AND error_code IS NOT NULL
                GROUP BY error_code, attempt_number
                ORDER BY count DESC
                LIMIT 20
            """)
            
            result = await self.session.execute(failure_query, {"cutoff_date": cutoff_date})
            
            failure_patterns = []
            for row in result.fetchall():
                failure_patterns.append({
                    "error_code": row.error_code,
                    "attempt_number": row.attempt_number,
                    "count": row.count
                })
            
            return {
                "retry_statistics": retry_stats,
                "failure_patterns": failure_patterns,
                "days_analyzed": days_back
            }
            
        except Exception as e:
            logger.error("Failed to get retry analysis", error=str(e))
            return {
                "retry_statistics": {},
                "failure_patterns": [],
                "days_analyzed": days_back,
                "error": str(e)
            }