"""
Notification Repository
Repository for notification operations
"""

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

from .base import NotificationBaseRepository
from app.models.notifications import Notification, NotificationStatus, NotificationType, NotificationPriority
from shared.database.exceptions import DatabaseError, ValidationError, DuplicateRecordError

logger = structlog.get_logger()


class NotificationRepository(NotificationBaseRepository):
    """Repository for notification operations"""
    
    def __init__(self, session: AsyncSession, cache_ttl: Optional[int] = 300):
        # Notifications are very dynamic, short cache time (5 minutes)
        super().__init__(Notification, session, cache_ttl)
    
    async def create_notification(self, notification_data: Dict[str, Any]) -> Notification:
        """Create a new notification with validation"""
        try:
            # Validate notification data
            validation_result = self._validate_notification_data(
                notification_data, 
                ["tenant_id", "sender_id", "type", "message"]
            )
            
            if not validation_result["is_valid"]:
                raise ValidationError(f"Invalid notification data: {validation_result['errors']}")
            
            # Set default values
            if "status" not in notification_data:
                notification_data["status"] = NotificationStatus.PENDING
            if "priority" not in notification_data:
                notification_data["priority"] = NotificationPriority.NORMAL
            if "retry_count" not in notification_data:
                notification_data["retry_count"] = 0
            if "max_retries" not in notification_data:
                notification_data["max_retries"] = 3
            if "broadcast" not in notification_data:
                notification_data["broadcast"] = False
            if "read" not in notification_data:
                notification_data["read"] = False
            
            # Create notification
            notification = await self.create(notification_data)
            
            logger.info("Notification created successfully",
                       notification_id=notification.id,
                       tenant_id=notification.tenant_id,
                       type=notification.type.value,
                       recipient_id=notification.recipient_id,
                       priority=notification.priority.value)
            
            return notification
            
        except ValidationError:
            raise
        except Exception as e:
            logger.error("Failed to create notification",
                        tenant_id=notification_data.get("tenant_id"),
                        type=notification_data.get("type"),
                        error=str(e))
            raise DatabaseError(f"Failed to create notification: {str(e)}")
    
    async def get_pending_notifications(self, limit: int = 100) -> List[Notification]:
        """Get pending notifications ready for processing"""
        try:
            # Get notifications that are pending and either not scheduled or scheduled for now/past
            now = datetime.utcnow()
            
            query_text = """
                SELECT * FROM notifications
                WHERE status = 'pending'
                AND (scheduled_at IS NULL OR scheduled_at <= :now)
                AND retry_count < max_retries
                ORDER BY priority DESC, created_at ASC
                LIMIT :limit
            """
            
            result = await self.session.execute(text(query_text), {
                "now": now,
                "limit": limit
            })
            
            notifications = []
            for row in result.fetchall():
                record_dict = dict(row._mapping)
                # Convert enum strings back to enum objects
                record_dict["status"] = NotificationStatus(record_dict["status"])
                record_dict["type"] = NotificationType(record_dict["type"])
                record_dict["priority"] = NotificationPriority(record_dict["priority"])
                notification = self.model(**record_dict)
                notifications.append(notification)
            
            return notifications
            
        except Exception as e:
            logger.error("Failed to get pending notifications", error=str(e))
            return []
    
    async def get_notifications_by_recipient(
        self,
        recipient_id: str,
        tenant_id: str = None,
        status: NotificationStatus = None,
        notification_type: NotificationType = None,
        unread_only: bool = False,
        skip: int = 0,
        limit: int = 50
    ) -> List[Notification]:
        """Get notifications for a specific recipient with filters"""
        try:
            filters = {"recipient_id": recipient_id}
            
            if tenant_id:
                filters["tenant_id"] = tenant_id
            
            if status:
                filters["status"] = status
                
            if notification_type:
                filters["type"] = notification_type
                
            if unread_only:
                filters["read"] = False
            
            return await self.get_multi(
                filters=filters,
                skip=skip,
                limit=limit,
                order_by="created_at",
                order_desc=True
            )
            
        except Exception as e:
            logger.error("Failed to get notifications by recipient",
                        recipient_id=recipient_id,
                        error=str(e))
            return []
    
    async def get_broadcast_notifications(
        self,
        tenant_id: str,
        skip: int = 0,
        limit: int = 50
    ) -> List[Notification]:
        """Get broadcast notifications for a tenant"""
        try:
            return await self.get_multi(
                filters={
                    "tenant_id": tenant_id,
                    "broadcast": True
                },
                skip=skip,
                limit=limit,
                order_by="created_at",
                order_desc=True
            )
            
        except Exception as e:
            logger.error("Failed to get broadcast notifications",
                        tenant_id=tenant_id,
                        error=str(e))
            return []
    
    async def update_notification_status(
        self,
        notification_id: str,
        new_status: NotificationStatus,
        error_message: str = None,
        provider_message_id: str = None,
        metadata: Dict[str, Any] = None
    ) -> Optional[Notification]:
        """Update notification status and related fields"""
        try:
            update_data = {
                "status": new_status,
                "updated_at": datetime.utcnow()
            }
            
            # Set timestamp based on status
            if new_status == NotificationStatus.SENT:
                update_data["sent_at"] = datetime.utcnow()
            elif new_status == NotificationStatus.DELIVERED:
                update_data["delivered_at"] = datetime.utcnow()
                if "sent_at" not in update_data:
                    update_data["sent_at"] = datetime.utcnow()
            
            # Add error message if provided
            if error_message:
                update_data["error_message"] = error_message
            
            # Add metadata if provided
            if metadata:
                update_data["log_metadata"] = json.dumps(metadata)
            
            updated_notification = await self.update(notification_id, update_data)
            
            logger.info("Notification status updated",
                       notification_id=notification_id,
                       new_status=new_status.value,
                       provider_message_id=provider_message_id)
            
            return updated_notification
            
        except Exception as e:
            logger.error("Failed to update notification status",
                        notification_id=notification_id,
                        new_status=new_status.value,
                        error=str(e))
            raise DatabaseError(f"Failed to update status: {str(e)}")
    
    async def increment_retry_count(self, notification_id: str) -> Optional[Notification]:
        """Increment retry count for a notification"""
        try:
            notification = await self.get_by_id(notification_id)
            if not notification:
                return None
            
            new_retry_count = notification.retry_count + 1
            update_data = {
                "retry_count": new_retry_count,
                "updated_at": datetime.utcnow()
            }
            
            # If max retries exceeded, mark as failed
            if new_retry_count >= notification.max_retries:
                update_data["status"] = NotificationStatus.FAILED
                update_data["error_message"] = "Maximum retry attempts exceeded"
            
            updated_notification = await self.update(notification_id, update_data)
            
            logger.info("Notification retry count incremented",
                       notification_id=notification_id,
                       retry_count=new_retry_count,
                       max_retries=notification.max_retries)
            
            return updated_notification
            
        except Exception as e:
            logger.error("Failed to increment retry count",
                        notification_id=notification_id,
                        error=str(e))
            raise DatabaseError(f"Failed to increment retry count: {str(e)}")
    
    async def mark_as_read(self, notification_id: str) -> Optional[Notification]:
        """Mark notification as read"""
        try:
            updated_notification = await self.update(notification_id, {
                "read": True,
                "read_at": datetime.utcnow()
            })
            
            logger.info("Notification marked as read",
                       notification_id=notification_id)
            
            return updated_notification
            
        except Exception as e:
            logger.error("Failed to mark notification as read",
                        notification_id=notification_id,
                        error=str(e))
            raise DatabaseError(f"Failed to mark as read: {str(e)}")
    
    async def mark_multiple_as_read(
        self,
        recipient_id: str,
        notification_ids: List[str] = None,
        tenant_id: str = None
    ) -> int:
        """Mark multiple notifications as read"""
        try:
            conditions = ["recipient_id = :recipient_id", "read = false"]
            params = {"recipient_id": recipient_id}
            
            if notification_ids:
                placeholders = ", ".join([f":id_{i}" for i in range(len(notification_ids))])
                conditions.append(f"id IN ({placeholders})")
                for i, notification_id in enumerate(notification_ids):
                    params[f"id_{i}"] = notification_id
            
            if tenant_id:
                conditions.append("tenant_id = :tenant_id")
                params["tenant_id"] = tenant_id
            
            query_text = f"""
                UPDATE notifications
                SET read = true, read_at = :read_at
                WHERE {' AND '.join(conditions)}
            """
            
            params["read_at"] = datetime.utcnow()
            
            result = await self.session.execute(text(query_text), params)
            updated_count = result.rowcount
            
            logger.info("Multiple notifications marked as read",
                       recipient_id=recipient_id,
                       updated_count=updated_count)
            
            return updated_count
            
        except Exception as e:
            logger.error("Failed to mark multiple notifications as read",
                        recipient_id=recipient_id,
                        error=str(e))
            raise DatabaseError(f"Failed to mark multiple as read: {str(e)}")
    
    async def get_failed_notifications_for_retry(self, hours_ago: int = 1) -> List[Notification]:
        """Get failed notifications that can be retried"""
        try:
            cutoff_time = datetime.utcnow() - timedelta(hours=hours_ago)
            
            query_text = """
                SELECT * FROM notifications
                WHERE status = 'failed'
                AND retry_count < max_retries
                AND updated_at >= :cutoff_time
                ORDER BY priority DESC, updated_at ASC
                LIMIT 100
            """
            
            result = await self.session.execute(text(query_text), {
                "cutoff_time": cutoff_time
            })
            
            notifications = []
            for row in result.fetchall():
                record_dict = dict(row._mapping)
                # Convert enum strings back to enum objects
                record_dict["status"] = NotificationStatus(record_dict["status"])
                record_dict["type"] = NotificationType(record_dict["type"])
                record_dict["priority"] = NotificationPriority(record_dict["priority"])
                notification = self.model(**record_dict)
                notifications.append(notification)
            
            return notifications
            
        except Exception as e:
            logger.error("Failed to get failed notifications for retry", error=str(e))
            return []
    
    async def get_notification_statistics(
        self,
        tenant_id: str = None,
        days_back: int = 30
    ) -> Dict[str, Any]:
        """Get notification statistics"""
        try:
            cutoff_date = datetime.utcnow() - timedelta(days=days_back)
            
            # Build base query conditions
            conditions = ["created_at >= :cutoff_date"]
            params = {"cutoff_date": cutoff_date}
            
            if tenant_id:
                conditions.append("tenant_id = :tenant_id")
                params["tenant_id"] = tenant_id
            
            where_clause = " AND ".join(conditions)
            
            # Get statistics by status
            status_query = text(f"""
                SELECT status, COUNT(*) as count 
                FROM notifications 
                WHERE {where_clause}
                GROUP BY status
                ORDER BY count DESC
            """)
            
            result = await self.session.execute(status_query, params)
            status_stats = {row.status: row.count for row in result.fetchall()}
            
            # Get statistics by type
            type_query = text(f"""
                SELECT type, COUNT(*) as count 
                FROM notifications 
                WHERE {where_clause}
                GROUP BY type
                ORDER BY count DESC
            """)
            
            result = await self.session.execute(type_query, params)
            type_stats = {row.type: row.count for row in result.fetchall()}
            
            # Get delivery rate
            delivery_query = text(f"""
                SELECT 
                    COUNT(*) as total_notifications,
                    COUNT(CASE WHEN status = 'delivered' THEN 1 END) as delivered_count,
                    COUNT(CASE WHEN status = 'failed' THEN 1 END) as failed_count,
                    AVG(CASE WHEN sent_at IS NOT NULL AND delivered_at IS NOT NULL 
                        THEN EXTRACT(EPOCH FROM (delivered_at - sent_at)) END) as avg_delivery_time_seconds
                FROM notifications 
                WHERE {where_clause}
            """)
            
            result = await self.session.execute(delivery_query, params)
            delivery_row = result.fetchone()
            
            total = delivery_row.total_notifications or 0
            delivered = delivery_row.delivered_count or 0
            failed = delivery_row.failed_count or 0
            delivery_rate = (delivered / total * 100) if total > 0 else 0
            failure_rate = (failed / total * 100) if total > 0 else 0
            
            # Get unread count (if tenant_id provided)
            unread_count = 0
            if tenant_id:
                unread_query = text(f"""
                    SELECT COUNT(*) as count
                    FROM notifications 
                    WHERE tenant_id = :tenant_id AND read = false
                """)
                
                result = await self.session.execute(unread_query, {"tenant_id": tenant_id})
                unread_count = result.scalar() or 0
            
            return {
                "total_notifications": total,
                "by_status": status_stats,
                "by_type": type_stats,
                "delivery_rate_percent": round(delivery_rate, 2),
                "failure_rate_percent": round(failure_rate, 2),
                "avg_delivery_time_seconds": float(delivery_row.avg_delivery_time_seconds or 0),
                "unread_count": unread_count,
                "days_analyzed": days_back
            }
            
        except Exception as e:
            logger.error("Failed to get notification statistics",
                        tenant_id=tenant_id,
                        error=str(e))
            return {
                "total_notifications": 0,
                "by_status": {},
                "by_type": {},
                "delivery_rate_percent": 0.0,
                "failure_rate_percent": 0.0,
                "avg_delivery_time_seconds": 0.0,
                "unread_count": 0,
                "days_analyzed": days_back
            }
    
    async def cancel_notification(self, notification_id: str, reason: str = None) -> Optional[Notification]:
        """Cancel a pending notification"""
        try:
            notification = await self.get_by_id(notification_id)
            if not notification:
                return None
            
            if notification.status != NotificationStatus.PENDING:
                raise ValidationError("Can only cancel pending notifications")
            
            update_data = {
                "status": NotificationStatus.CANCELLED,
                "updated_at": datetime.utcnow()
            }
            
            if reason:
                update_data["error_message"] = f"Cancelled: {reason}"
            
            updated_notification = await self.update(notification_id, update_data)
            
            logger.info("Notification cancelled",
                       notification_id=notification_id,
                       reason=reason)
            
            return updated_notification
            
        except ValidationError:
            raise
        except Exception as e:
            logger.error("Failed to cancel notification",
                        notification_id=notification_id,
                        error=str(e))
            raise DatabaseError(f"Failed to cancel notification: {str(e)}")
    
    async def schedule_notification(
        self,
        notification_id: str,
        scheduled_at: datetime
    ) -> Optional[Notification]:
        """Schedule a notification for future delivery"""
        try:
            if scheduled_at <= datetime.utcnow():
                raise ValidationError("Scheduled time must be in the future")
            
            updated_notification = await self.update(notification_id, {
                "scheduled_at": scheduled_at,
                "updated_at": datetime.utcnow()
            })
            
            logger.info("Notification scheduled",
                       notification_id=notification_id,
                       scheduled_at=scheduled_at)
            
            return updated_notification
            
        except ValidationError:
            raise
        except Exception as e:
            logger.error("Failed to schedule notification",
                        notification_id=notification_id,
                        error=str(e))
            raise DatabaseError(f"Failed to schedule notification: {str(e)}")