from fastapi import APIRouter, Depends, HTTPException, Query, Body, WebSocket, WebSocketDisconnect
from sqlalchemy.orm import Session
from typing import Optional, List, Dict, Any
from ...shared.config.database import get_db
from ...shared.config.logging_config import get_logger
from ...security.middleware.auth import authorize_roles, get_current_user
from ...auth.models.user import User
from ...auth.models.role import Role
from ..models.notification import NotificationChannel, NotificationStatus, NotificationType
from ..services.notification_service import NotificationService
from pydantic import BaseModel
from datetime import datetime
import json

logger = get_logger(__name__)
router = APIRouter(prefix='/notifications', tags=['notifications'])

# Dedicated Notification Connection Manager (separate from chat)
class NotificationConnectionManager:
    """Manages WebSocket connections for real-time notifications"""
    
    def __init__(self):
        self.user_connections: dict[int, WebSocket] = {}
        self.role_connections: dict[str, List[WebSocket]] = {
            'admin': [],
            'staff': [],
            'housekeeping': [],
            'accountant': []
        }
    
    def connect_user(self, user_id: int, websocket: WebSocket, role_name: str):
        """Connect a user to the notification system"""
        self.user_connections[user_id] = websocket
        if role_name in self.role_connections:
            if websocket not in self.role_connections[role_name]:
                self.role_connections[role_name].append(websocket)
        logger.debug(f'User {user_id} ({role_name}) connected to notification system')
    
    def disconnect_user(self, user_id: int, role_name: str):
        """Disconnect a user from the notification system"""
        websocket = None
        if user_id in self.user_connections:
            websocket = self.user_connections[user_id]
            del self.user_connections[user_id]
        if role_name in self.role_connections and websocket:
            self.role_connections[role_name] = [
                ws for ws in self.role_connections[role_name]
                if ws != websocket
            ]
        logger.debug(f'User {user_id} ({role_name}) disconnected from notification system')
    
    async def send_to_user(self, user_id: int, message: dict):
        """Send notification to a specific user"""
        if user_id in self.user_connections:
            try:
                websocket = self.user_connections[user_id]
                await websocket.send_json(message)
                return True
            except Exception as e:
                logger.error(f'Error sending notification to user {user_id}: {str(e)}', exc_info=True)
                # Remove broken connection
                if user_id in self.user_connections:
                    del self.user_connections[user_id]
                return False
        return False
    
    async def send_to_role(self, role_name: str, message: dict):
        """Send notification to all users with a specific role"""
        if role_name not in self.role_connections:
            return 0
        
        disconnected = []
        sent_count = 0
        
        for websocket in self.role_connections[role_name]:
            try:
                await websocket.send_json(message)
                sent_count += 1
            except Exception as e:
                logger.error(f'Error sending notification to {role_name} user: {str(e)}', exc_info=True)
                disconnected.append(websocket)
        
        # Clean up disconnected websockets
        for ws in disconnected:
            if ws in self.role_connections[role_name]:
                self.role_connections[role_name].remove(ws)
        
        return sent_count
    
    async def broadcast(self, message: dict):
        """Broadcast notification to all connected users"""
        disconnected = []
        sent_count = 0
        
        for user_id, websocket in self.user_connections.items():
            try:
                await websocket.send_json(message)
                sent_count += 1
            except Exception as e:
                logger.error(f'Error broadcasting to user {user_id}: {str(e)}', exc_info=True)
                disconnected.append(user_id)
        
        # Clean up disconnected websockets
        for user_id in disconnected:
            if user_id in self.user_connections:
                del self.user_connections[user_id]
        
        return sent_count

# Global notification manager instance
notification_manager = NotificationConnectionManager()

# Request/Response Models
class NotificationSendRequest(BaseModel):
    user_id: Optional[int] = None
    notification_type: str
    channel: str
    content: str
    subject: Optional[str] = None
    template_id: Optional[int] = None
    priority: Optional[str] = 'normal'
    scheduled_at: Optional[str] = None
    booking_id: Optional[int] = None
    payment_id: Optional[int] = None
    meta_data: Optional[Dict[str, Any]] = None

class TemplateCreateRequest(BaseModel):
    name: str
    notification_type: str
    channel: str
    content: str
    subject: Optional[str] = None
    variables: Optional[List[str]] = None

class PreferencesUpdateRequest(BaseModel):
    email_enabled: Optional[bool] = None
    sms_enabled: Optional[bool] = None
    push_enabled: Optional[bool] = None
    whatsapp_enabled: Optional[bool] = None
    in_app_enabled: Optional[bool] = None
    booking_confirmation_email: Optional[bool] = None
    booking_confirmation_sms: Optional[bool] = None
    payment_receipt_email: Optional[bool] = None
    payment_receipt_sms: Optional[bool] = None
    pre_arrival_reminder_email: Optional[bool] = None
    pre_arrival_reminder_sms: Optional[bool] = None
    check_in_reminder_email: Optional[bool] = None
    check_in_reminder_sms: Optional[bool] = None
    check_out_reminder_email: Optional[bool] = None
    check_out_reminder_sms: Optional[bool] = None
    marketing_campaign_email: Optional[bool] = None
    marketing_campaign_sms: Optional[bool] = None
    loyalty_update_email: Optional[bool] = None
    loyalty_update_sms: Optional[bool] = None
    system_alert_email: Optional[bool] = None
    system_alert_push: Optional[bool] = None

# Notifications
@router.get('/')
async def get_notifications(
    user_id: Optional[int] = Query(None),
    notification_type: Optional[str] = Query(None),
    channel: Optional[str] = Query(None),
    status: Optional[str] = Query(None),
    skip: int = Query(0, ge=0),
    limit: int = Query(100, ge=1, le=1000),
    current_user: User = Depends(authorize_roles('admin', 'staff')),
    db: Session = Depends(get_db)
):
    """Get notifications"""
    try:
        notifications = NotificationService.get_notifications(
            db=db,
            user_id=user_id,
            notification_type=NotificationType(notification_type) if notification_type else None,
            channel=NotificationChannel(channel) if channel else None,
            status=NotificationStatus(status) if status else None,
            skip=skip,
            limit=limit
        )
        return {'status': 'success', 'data': [{
            'id': n.id,
            'user_id': n.user_id,
            'notification_type': n.notification_type.value,
            'channel': n.channel.value,
            'subject': n.subject,
            'content': n.content,
            'status': n.status.value,
            'priority': n.priority,
            'sent_at': n.sent_at.isoformat() if n.sent_at else None,
            'delivered_at': n.delivered_at.isoformat() if n.delivered_at else None,
            'read_at': n.read_at.isoformat() if n.read_at else None,
            'created_at': n.created_at.isoformat(),
        } for n in notifications]}
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@router.get('/my-notifications')
async def get_my_notifications(
    status: Optional[str] = Query(None),
    skip: int = Query(0, ge=0),
    limit: int = Query(50, ge=1, le=100),
    current_user: User = Depends(get_current_user),
    db: Session = Depends(get_db)
):
    """Get current user's notifications"""
    try:
        notifications = NotificationService.get_notifications(
            db=db,
            user_id=current_user.id,
            status=NotificationStatus(status) if status else None,
            skip=skip,
            limit=limit
        )
        return {'status': 'success', 'data': [{
            'id': n.id,
            'notification_type': n.notification_type.value,
            'channel': n.channel.value,
            'subject': n.subject,
            'content': n.content,
            'status': n.status.value,
            'read_at': n.read_at.isoformat() if n.read_at else None,
            'created_at': n.created_at.isoformat(),
        } for n in notifications]}
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@router.post('/send')
async def send_notification(
    notification_data: NotificationSendRequest,
    current_user: User = Depends(authorize_roles('admin', 'staff')),
    db: Session = Depends(get_db)
):
    """Send notification"""
    try:
        scheduled_at = None
        if notification_data.scheduled_at:
            scheduled_at = datetime.fromisoformat(notification_data.scheduled_at.replace('Z', '+00:00'))
        
        notification = NotificationService.send_notification(
            db=db,
            user_id=notification_data.user_id,
            notification_type=NotificationType(notification_data.notification_type),
            channel=NotificationChannel(notification_data.channel),
            content=notification_data.content,
            subject=notification_data.subject,
            template_id=notification_data.template_id,
            priority=notification_data.priority or 'normal',
            scheduled_at=scheduled_at,
            booking_id=notification_data.booking_id,
            payment_id=notification_data.payment_id,
            meta_data=notification_data.meta_data
        )
        return {'status': 'success', 'data': {
            'id': notification.id,
            'status': notification.status.value,
            'created_at': notification.created_at.isoformat()
        }}
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@router.post('/{notification_id}/read')
async def mark_notification_read(
    notification_id: int,
    current_user: User = Depends(get_current_user),
    db: Session = Depends(get_db)
):
    """Mark notification as read"""
    try:
        notification = NotificationService.mark_as_read(db, notification_id, current_user.id)
        if not notification:
            raise HTTPException(status_code=404, detail='Notification not found')
        
        return {'status': 'success', 'data': {
            'id': notification.id,
            'status': notification.status.value,
            'read_at': notification.read_at.isoformat() if notification.read_at else None
        }}
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

# Templates
@router.post('/templates')
async def create_template(
    template_data: TemplateCreateRequest,
    current_user: User = Depends(authorize_roles('admin')),
    db: Session = Depends(get_db)
):
    """Create notification template"""
    try:
        template = NotificationService.create_template(
            db=db,
            name=template_data.name,
            notification_type=NotificationType(template_data.notification_type),
            channel=NotificationChannel(template_data.channel),
            content=template_data.content,
            created_by=current_user.id,
            subject=template_data.subject,
            variables=template_data.variables
        )
        return {'status': 'success', 'data': {
            'id': template.id,
            'name': template.name,
            'created_at': template.created_at.isoformat()
        }}
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@router.get('/templates')
async def get_templates(
    notification_type: Optional[str] = Query(None),
    channel: Optional[str] = Query(None),
    current_user: User = Depends(authorize_roles('admin', 'staff')),
    db: Session = Depends(get_db)
):
    """Get notification templates"""
    try:
        from ..models.notification import NotificationTemplate
        query = db.query(NotificationTemplate)
        
        if notification_type:
            query = query.filter(NotificationTemplate.notification_type == NotificationType(notification_type))
        if channel:
            query = query.filter(NotificationTemplate.channel == NotificationChannel(channel))
        
        templates = query.filter(NotificationTemplate.is_active == True).all()
        return {'status': 'success', 'data': [{
            'id': t.id,
            'name': t.name,
            'notification_type': t.notification_type.value,
            'channel': t.channel.value,
            'subject': t.subject,
            'content': t.content,
            'variables': t.variables,
        } for t in templates]}
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

# Preferences
@router.get('/preferences')
async def get_preferences(
    current_user: User = Depends(get_current_user),
    db: Session = Depends(get_db)
):
    """Get user notification preferences"""
    try:
        preferences = NotificationService.get_user_preferences(db, current_user.id)
        return {'status': 'success', 'data': {
            'email_enabled': preferences.email_enabled,
            'sms_enabled': preferences.sms_enabled,
            'push_enabled': preferences.push_enabled,
            'whatsapp_enabled': preferences.whatsapp_enabled,
            'in_app_enabled': preferences.in_app_enabled,
            'booking_confirmation_email': preferences.booking_confirmation_email,
            'booking_confirmation_sms': preferences.booking_confirmation_sms,
            'payment_receipt_email': preferences.payment_receipt_email,
            'payment_receipt_sms': preferences.payment_receipt_sms,
            'pre_arrival_reminder_email': preferences.pre_arrival_reminder_email,
            'pre_arrival_reminder_sms': preferences.pre_arrival_reminder_sms,
            'check_in_reminder_email': preferences.check_in_reminder_email,
            'check_in_reminder_sms': preferences.check_in_reminder_sms,
            'check_out_reminder_email': preferences.check_out_reminder_email,
            'check_out_reminder_sms': preferences.check_out_reminder_sms,
            'marketing_campaign_email': preferences.marketing_campaign_email,
            'marketing_campaign_sms': preferences.marketing_campaign_sms,
            'loyalty_update_email': preferences.loyalty_update_email,
            'loyalty_update_sms': preferences.loyalty_update_sms,
            'system_alert_email': preferences.system_alert_email,
            'system_alert_push': preferences.system_alert_push,
        }}
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@router.put('/preferences')
async def update_preferences(
    preferences_data: PreferencesUpdateRequest,
    current_user: User = Depends(get_current_user),
    db: Session = Depends(get_db)
):
    """Update user notification preferences"""
    try:
        prefs_dict = preferences_data.dict(exclude_unset=True)
        preferences = NotificationService.update_user_preferences(db, current_user.id, prefs_dict)
        return {'status': 'success', 'data': {
            'email_enabled': preferences.email_enabled,
            'sms_enabled': preferences.sms_enabled,
            'push_enabled': preferences.push_enabled,
            'whatsapp_enabled': preferences.whatsapp_enabled,
            'in_app_enabled': preferences.in_app_enabled,
        }}
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

# WebSocket endpoint for real-time notifications
@router.websocket('/ws')
async def websocket_notifications(websocket: WebSocket):
    """WebSocket endpoint for real-time notifications (separate from chat)"""
    current_user = None
    role_name = None
    try:
        await websocket.accept()
        
        # Security: Read token from cookies
        token = None
        if websocket.cookies:
            token = websocket.cookies.get('accessToken')
        
        # Fallback to query parameter for backward compatibility
        if not token:
            query_params = dict(websocket.query_params)
            token = query_params.get('token')
        
        if not token:
            await websocket.close(code=1008, reason='Token required')
            return
        
        try:
            from ...security.middleware.auth import verify_token
            from ...shared.config.database import get_db
            payload = verify_token(token)
            user_id = payload.get('userId')
            if not user_id:
                await websocket.close(code=1008, reason='Invalid token payload')
                return
            
            db_gen = get_db()
            db = next(db_gen)
            try:
                current_user = db.query(User).filter(User.id == user_id).first()
                if not current_user:
                    await websocket.close(code=1008, reason='User not found')
                    return
                
                role = db.query(Role).filter(Role.id == current_user.role_id).first()
                if not role:
                    await websocket.close(code=1008, reason='User role not found')
                    return
                
                role_name = role.name
            finally:
                db.close()
        except Exception as e:
            logger.error(f'WebSocket token verification error: {str(e)}', exc_info=True)
            await websocket.close(code=1008, reason=f'Token verification failed: {str(e)}')
            return
        
        # Connect user to notification system
        notification_manager.connect_user(current_user.id, websocket, role_name)
        
        try:
            await websocket.send_json({
                'type': 'connected',
                'data': {
                    'message': 'Connected to notification system',
                    'user_id': current_user.id,
                    'role': role_name
                }
            })
        except Exception as e:
            logger.error(f'Error sending initial notification message: {str(e)}', exc_info=True, extra={'user_id': current_user.id})
        
        # Keep connection alive and handle ping/pong
        while True:
            try:
                data = await websocket.receive_text()
                try:
                    message_data = json.loads(data)
                    if message_data.get('type') == 'ping':
                        await websocket.send_json({'type': 'pong', 'data': 'pong'})
                except json.JSONDecodeError:
                    await websocket.send_json({'type': 'pong', 'data': 'pong'})
            except WebSocketDisconnect:
                logger.info('Notification WebSocket disconnected normally', extra={'user_id': current_user.id})
                break
            except Exception as e:
                logger.error(f'Notification WebSocket receive error: {str(e)}', exc_info=True, extra={'user_id': current_user.id})
                break
    except WebSocketDisconnect:
        logger.info('Notification WebSocket disconnected')
    except Exception as e:
        logger.error(f'Notification WebSocket error: {str(e)}', exc_info=True)
    finally:
        if current_user and role_name:
            try:
                notification_manager.disconnect_user(current_user.id, role_name)
            except:
                pass

