from flask import request
from models import Notif, NotifType, db, EmailTemplate, Mail, KeyValueSettings, User
from typing import Optional, Dict, Any, List
from datetime import datetime, timedelta
from flask_login import current_user
from sqlalchemy import desc, and_
import logging
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.utils import formatdate
import json

logger = logging.getLogger(__name__)

def get_smtp_settings() -> Optional[Dict[str, Any]]:
    """
    Get SMTP settings from the database.
    
    Returns:
        Dictionary containing SMTP settings or None if not configured
    """
    try:
        smtp_settings = KeyValueSettings.get_value('smtp_settings')
        if not smtp_settings:
            logger.warning("No SMTP settings found in database")
            return None
        return smtp_settings
    except Exception as e:
        logger.error(f"Error retrieving SMTP settings: {str(e)}")
        return None

def send_email_via_smtp(mail: Mail) -> bool:
    """Send an email synchronously"""
    try:
        # Get SMTP settings
        smtp_settings = get_smtp_settings()
        if not smtp_settings:
            logger.error("SMTP settings not found")
            mail.status = 'failed'
            mail.error_message = "SMTP settings not found"
            db.session.commit()
            return False

        # Create message
        msg = MIMEMultipart()
        msg['From'] = smtp_settings.sender_email
        msg['To'] = mail.recipient_email
        msg['Subject'] = mail.subject
        msg['Date'] = formatdate(localtime=True)

        # Add HTML content
        msg.attach(MIMEText(mail.content, 'html'))

        # Send email
        with smtplib.SMTP(smtp_settings.smtp_server, smtp_settings.smtp_port) as server:
            if smtp_settings.use_tls:
                server.starttls()
            if smtp_settings.username and smtp_settings.password:
                server.login(smtp_settings.username, smtp_settings.password)
            server.send_message(msg)

        # Update mail status
        mail.status = 'sent'
        mail.sent_at = datetime.utcnow()
        db.session.commit()
        return True

    except Exception as e:
        logger.error(f"Error sending email: {str(e)}")
        mail.status = 'failed'
        mail.error_message = str(e)
        db.session.commit()
        return False

def generate_mail_from_notification(notif: Notif) -> Optional[Mail]:
    """
    Generate a mail record from a notification using the appropriate email template.
    
    Args:
        notif: The notification object to generate mail from
    
    Returns:
        The created Mail object or None if no template was found
    """
    logger.debug(f"Generating mail for notification: {notif}")
    
    # Convert notification type to template name format (e.g., 'account_created' -> 'Account Created')
    template_name = ' '.join(word.capitalize() for word in notif.notif_type.split('_'))
    
    # Find the corresponding email template based on notif_type
    template = EmailTemplate.query.filter_by(name=template_name).first()
    if not template:
        logger.warning(f"No email template found for notification type: {notif.notif_type} (template name: {template_name})")
        return None

    try:
        # Fill in the template with notification details
        filled_body = template.body
        
        # Add user information
        if notif.user:
            filled_body = filled_body.replace('{{ user.username }}', notif.user.username + ' ' + notif.user.last_name)
            filled_body = filled_body.replace('{{ user.email }}', notif.user.email)
            if hasattr(notif.user, 'company'):
                filled_body = filled_body.replace('{{ user.company }}', notif.user.company or '')
            if hasattr(notif.user, 'position'):
                filled_body = filled_body.replace('{{ user.position }}', notif.user.position or '')
        
        # Add sender information if available
        if notif.sender:
            filled_body = filled_body.replace('{{ sender.username }}', notif.sender.username + ' ' + notif.sender.last_name)
            filled_body = filled_body.replace('{{ sender.email }}', notif.sender.email)
        
        # Add site information
        from models import SiteSettings
        site_settings = SiteSettings.query.first()
        if site_settings:
            filled_body = filled_body.replace('{{ site.company_name }}', site_settings.company_name or '')
            filled_body = filled_body.replace('{{ site.company_website }}', site_settings.company_website or '')
        
        # Add notification details
        if notif.details:
            for key, value in notif.details.items():
                # Handle nested keys (e.g., room.name -> room_name)
                if '.' in key:
                    parts = key.split('.')
                    if len(parts) == 2:
                        obj_name, attr = parts
                        if obj_name in notif.details and isinstance(notif.details[obj_name], dict):
                            if attr in notif.details[obj_name]:
                                filled_body = filled_body.replace(f'{{{{ {key} }}}}', str(notif.details[obj_name][attr]))
                else:
                    # Special handling for setup_link to ensure it's a proper URL
                    if key == 'setup_link' and value.startswith('http://http//'):
                        value = value.replace('http://http//', 'http://')
                    filled_body = filled_body.replace(f'{{{{ {key} }}}}', str(value))
        
        # Handle special URL variables
        if 'room_link' in filled_body and 'room_id' in notif.details:
            from flask import url_for
            room_link = url_for('rooms.room', room_id=notif.details['room_id'], _external=True)
            filled_body = filled_body.replace('{{ room_link }}', room_link)
        
        if 'conversation_link' in filled_body and 'conversation_id' in notif.details:
            from flask import url_for
            conversation_link = url_for('conversations.conversation', conversation_id=notif.details['conversation_id'], _external=True)
            filled_body = filled_body.replace('{{ conversation_link }}', conversation_link)
        
        # Add timestamps
        filled_body = filled_body.replace('{{ created_at }}', notif.timestamp.strftime('%Y-%m-%d %H:%M:%S'))
        if 'updated_at' in filled_body:
            filled_body = filled_body.replace('{{ updated_at }}', datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'))
        if 'deleted_at' in filled_body:
            filled_body = filled_body.replace('{{ deleted_at }}', datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'))
        if 'removed_at' in filled_body:
            filled_body = filled_body.replace('{{ removed_at }}', datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'))

        # Create a new Mail record
        mail = Mail(
            recipient=notif.user.email,
            subject=template.subject,
            body=filled_body,
            status='pending',
            template_id=template.id,
            notif_id=notif.id
        )
        db.session.add(mail)
        
        # Try to send the email immediately
        if send_email_via_smtp(mail):
            logger.debug(f"Email sent successfully for notification: {notif}")
        else:
            logger.warning(f"Failed to send email for notification: {notif}")
        
        return mail
    except Exception as e:
        logger.error(f"Error generating mail from notification: {str(e)}")
        return None

def create_notification(
    notif_type: str,
    user_id: int,
    sender_id: Optional[int] = None,
    details: Optional[Dict[str, Any]] = None,
    generate_mail: bool = True
) -> Notif:
    """
    Create a notification in the database and optionally generate a mail record.
    
    Args:
        notif_type: The type of notification (must match NotifType enum)
        user_id: The ID of the user to notify
        sender_id: Optional ID of the user who triggered the notification
        details: Optional dictionary containing notification details
        generate_mail: Whether to generate a mail record for this notification
    
    Returns:
        The created Notif object
    """
    logger.debug(f"Creating notification of type: {notif_type}")
    logger.debug(f"Notification details: {details}")
    
    try:
        notif = Notif(
            notif_type=notif_type,
            user_id=user_id,
            sender_id=sender_id,
            timestamp=datetime.utcnow(),
            details=details or {},
            read=False
        )
        
        logger.debug(f"Created notification object: {notif}")
        db.session.add(notif)
        
        # Generate mail if requested
        if generate_mail:
            mail = generate_mail_from_notification(notif)
            if mail:
                logger.debug(f"Generated mail record for notification: {mail}")
        
        # Don't commit here - let the caller handle the transaction
        logger.debug("Notification object added to session")
        return notif
    except Exception as e:
        logger.error(f"Error creating notification: {str(e)}")
        raise

def get_user_notifications(user_id: int, limit: int = 50, unread_only: bool = False) -> List[Notif]:
    """Get recent notifications for a specific user"""
    query = Notif.query.filter_by(user_id=user_id)
    if unread_only:
        query = query.filter_by(read=False)
    return query.order_by(desc(Notif.timestamp)).limit(limit).all()

def mark_notification_read(notif_id: int) -> bool:
    """Mark a notification as read"""
    notif = Notif.query.get(notif_id)
    if notif:
        notif.read = True
        db.session.commit()
        return True
    return False

def mark_all_notifications_read(user_id: int) -> int:
    """Mark all notifications as read for a user"""
    result = Notif.query.filter_by(user_id=user_id, read=False).update({'read': True})
    db.session.commit()
    return result

def get_unread_count(user_id: int) -> int:
    """Get count of unread notifications for a user"""
    return Notif.query.filter_by(user_id=user_id, read=False).count()

def delete_notification(notif_id: int) -> bool:
    """Delete a notification"""
    notif = Notif.query.get(notif_id)
    if notif:
        db.session.delete(notif)
        db.session.commit()
        return True
    return False

def delete_old_notifications(days: int = 30) -> int:
    """Delete notifications older than specified days"""
    cutoff_date = datetime.utcnow() - timedelta(days=days)
    result = Notif.query.filter(Notif.timestamp < cutoff_date).delete()
    db.session.commit()
    return result 