"""
Notification Service - Business logic for user notifications

This service handles:
- Creating notifications for various events
- Fetching user notifications
- Marking notifications as read
- Workflow approval notifications
"""
from typing import List, Optional, Dict, Any
from sqlalchemy.orm import Session
from sqlalchemy import and_, or_, desc, func, String
from datetime import datetime, timedelta
import json

from app.models.notifications import Notification, NotificationUser
from app.models.workflow import WorkflowInstance, WorkflowNotification, WorkflowState, WorkflowStatus
from app.models.user import User
from app.models.role import Role


class NotificationService:
    """Service for managing user notifications"""

    def __init__(self, db: Session):
        self.db = db

    # ========== Create Notifications ==========

    def create_notification(
        self,
        title: str,
        message: str,
        user_ids: List[str],
        notification_type: str = "info",
        category: str = "system",
        priority: str = "normal",
        action_url: Optional[str] = None,
        created_by: Optional[str] = None,
        branch_id: Optional[str] = None,
        expires_at: Optional[datetime] = None,
        data: Optional[str] = None
    ) -> Notification:
        """Create a notification and send to specified users"""
        notification = Notification(
            title=title,
            message=message,
            notification_type=notification_type,
            category=category,
            priority=priority,
            action_url=action_url,
            created_by=created_by,
            branch_id=branch_id,
            expires_at=expires_at,
            data=data,
            kind=self._get_kind_from_type(notification_type)
        )
        self.db.add(notification)
        self.db.flush()  # Get the notification ID

        # Create user associations
        for user_id in user_ids:
            notification_user = NotificationUser(
                notification_id=notification.id,
                user_id=user_id,
                read=False
            )
            self.db.add(notification_user)

        self.db.commit()
        self.db.refresh(notification)
        return notification

    def create_approval_notification(
        self,
        workflow_instance: WorkflowInstance,
        approver_user_ids: List[str],
        entity_display_name: str,
        action_url: Optional[str] = None
    ) -> Notification:
        """Create a notification for pending approval"""
        title = f"Approval Required: {workflow_instance.entity_type.replace('_', ' ').title()}"
        message = f"{entity_display_name} is waiting for your approval"

        return self.create_notification(
            title=title,
            message=message,
            user_ids=approver_user_ids,
            notification_type="warning",
            category="approval",
            priority="high",
            action_url=action_url or f"/workflows/{workflow_instance.id}",
            data=f'{{"workflow_instance_id": "{workflow_instance.id}", "entity_type": "{workflow_instance.entity_type}", "entity_id": "{workflow_instance.entity_id}"}}'
        )

    def create_workflow_status_notification(
        self,
        workflow_instance: WorkflowInstance,
        action_type: str,
        performed_by_user: User,
        recipient_user_ids: List[str],
        comment: Optional[str] = None
    ) -> Notification:
        """Create notification when workflow status changes (approved, rejected, etc.)"""
        status_messages = {
            "approve": ("Approved", "success", "Your request has been approved"),
            "reject": ("Rejected", "danger", "Your request has been rejected"),
            "submit": ("Submitted", "info", "Your request has been submitted for approval"),
            "complete": ("Completed", "success", "The workflow has been completed"),
            "cancel": ("Cancelled", "warning", "The workflow has been cancelled"),
            "revise": ("Revision Required", "warning", "Your request requires revision"),
        }

        status_info = status_messages.get(action_type, ("Updated", "info", "The workflow status has changed"))
        title = f"{workflow_instance.entity_type.replace('_', ' ').title()} {status_info[0]}"
        message = status_info[2]
        if comment:
            message += f" - {comment}"

        return self.create_notification(
            title=title,
            message=message,
            user_ids=recipient_user_ids,
            notification_type=status_info[1],
            category="workflow",
            priority="normal" if action_type in ["submit", "complete"] else "high",
            action_url=f"/workflows/{workflow_instance.id}",
            data=f'{{"workflow_instance_id": "{workflow_instance.id}", "action": "{action_type}", "performed_by": "{performed_by_user.username if performed_by_user else "system"}"}}'
        )

    # ========== Fetch Notifications ==========

    def get_user_notifications(
        self,
        user_id: str,
        limit: int = 50,
        offset: int = 0,
        unread_only: bool = False,
        category: Optional[str] = None
    ) -> List[Dict[str, Any]]:
        """Get notifications for a specific user"""
        query = self.db.query(
            Notification,
            NotificationUser
        ).join(
            NotificationUser,
            Notification.id == NotificationUser.notification_id
        ).filter(
            NotificationUser.user_id == user_id,
            NotificationUser.dismissed == False
        )

        if unread_only:
            query = query.filter(NotificationUser.read == False)

        if category:
            query = query.filter(Notification.category == category)

        # Filter out expired notifications
        query = query.filter(
            or_(
                Notification.expires_at.is_(None),
                Notification.expires_at > datetime.utcnow()
            )
        )

        results = query.order_by(
            desc(Notification.created_at)
        ).offset(offset).limit(limit).all()

        notifications = []
        for notification, notification_user in results:
            notifications.append({
                "id": notification.id,
                "title": notification.title,
                "message": notification.message,
                "notification_type": notification.notification_type,
                "category": notification.category,
                "priority": notification.priority,
                "action_url": notification.action_url,
                "data": notification.data,
                "read": notification_user.read,
                "read_at": notification_user.read_at.isoformat() if notification_user.read_at else None,
                "created_at": notification.created_at.isoformat() if notification.created_at else None,
                "expires_at": notification.expires_at.isoformat() if notification.expires_at else None
            })

        return notifications

    def get_unread_count(self, user_id: str) -> int:
        """Get count of unread notifications for a user"""
        return self.db.query(func.count(NotificationUser.id)).join(
            Notification,
            Notification.id == NotificationUser.notification_id
        ).filter(
            NotificationUser.user_id == user_id,
            NotificationUser.read == False,
            NotificationUser.dismissed == False,
            or_(
                Notification.expires_at.is_(None),
                Notification.expires_at > datetime.utcnow()
            )
        ).scalar() or 0

    def get_pending_approvals(self, user_id: str) -> List[Dict[str, Any]]:
        """Get pending approval items for a user based on their role"""
        user = self.db.query(User).filter(User.id == user_id).first()
        if not user or not user.role_id:
            return []

        # Get workflow states where user's role can approve
        # Using JSON contains for PostgreSQL
        role_tokens = [str(user.role_id)]
        if getattr(user, "role", None):
            role_tokens.append(str(user.role).lower())

        pending_instances = self.db.query(WorkflowInstance).join(
            WorkflowState,
            WorkflowInstance.current_state_id == WorkflowState.id
        ).filter(
            WorkflowInstance.status.in_([
                WorkflowStatus.PENDING_APPROVAL,
                WorkflowStatus.SUBMITTED
            ]),
            WorkflowState.requires_approval == True,
            # Check role id or role name tokens in allowed_roles JSON text.
            or_(*[func.lower(func.cast(WorkflowState.allowed_roles, String)).contains(token.lower()) for token in role_tokens])
        ).order_by(desc(WorkflowInstance.initiated_at)).limit(50).all()

        approvals = []
        for instance in pending_instances:
            # Try to get entity name based on type
            entity_name = self._get_entity_name(instance.entity_type, instance.entity_id)

            approvals.append({
                "id": instance.id,
                "entity_type": instance.entity_type,
                "entity_id": instance.entity_id,
                "entity_name": entity_name,
                "status": instance.status.value if instance.status else "unknown",
                "current_state": instance.current_state.name if instance.current_state else "Unknown",
                "initiated_by": instance.initiated_by_user.username if instance.initiated_by_user else "Unknown",
                "submitted_at": instance.initiated_at.isoformat() if instance.initiated_at else None,
                "priority": instance.priority,
                "due_date": instance.due_date.isoformat() if instance.due_date else None
            })

        return approvals

    def sync_attention_notifications_for_user(self, user_id: str) -> int:
        """
        Ensure workflow transactions needing this user's attention exist as in-app notifications.
        Creates missing high-priority approval notifications and avoids duplicates per workflow instance.
        """
        user = self.db.query(User).filter(User.id == user_id).first()
        if not user:
            return 0

        pending = self.get_pending_approvals(user_id)
        if not pending:
            return 0

        created = 0
        for item in pending:
            workflow_instance_id = item.get("id")
            if not workflow_instance_id:
                continue

            duplicate = self.db.query(NotificationUser).join(
                Notification, Notification.id == NotificationUser.notification_id
            ).filter(
                NotificationUser.user_id == user_id,
                NotificationUser.dismissed == False,
                Notification.category == "approval",
                Notification.data.isnot(None),
                Notification.data.contains(workflow_instance_id),
            ).first()
            if duplicate:
                continue

            entity_type = (item.get("entity_type") or "Transaction").replace("_", " ").title()
            entity_name = item.get("entity_name") or item.get("entity_id") or "Transaction"
            action_url = f"/static/notifications.html#workflow={workflow_instance_id}"
            payload = json.dumps(
                {
                    "workflow_instance_id": workflow_instance_id,
                    "entity_type": item.get("entity_type"),
                    "entity_id": item.get("entity_id"),
                    "current_state": item.get("current_state"),
                }
            )
            self.create_notification(
                title=f"Approval Required: {entity_type}",
                message=f"{entity_name} needs your attention and approval.",
                user_ids=[user_id],
                notification_type="approval_pending",
                category="approval",
                priority="high",
                action_url=action_url,
                data=payload,
            )
            created += 1

        return created

    def _get_entity_name(self, entity_type: str, entity_id: str) -> Optional[str]:
        """Get a display name for the entity"""
        try:
            entity_type_lower = entity_type.lower()

            if entity_type_lower in ['purchase_order', 'purchaseorder']:
                from app.models.purchase_order import PurchaseOrder
                entity = self.db.query(PurchaseOrder).filter(PurchaseOrder.id == entity_id).first()
                if entity:
                    return f"PO-{entity.order_number}" if hasattr(entity, 'order_number') and entity.order_number else entity_id[:8]

            elif entity_type_lower in ['sales_invoice', 'salesinvoice', 'invoice']:
                from app.models.invoice import Invoice
                entity = self.db.query(Invoice).filter(Invoice.id == entity_id).first()
                if entity:
                    return f"INV-{entity.invoice_number}" if hasattr(entity, 'invoice_number') and entity.invoice_number else entity_id[:8]

            elif entity_type_lower in ['journal_entry', 'journalentry']:
                from app.models.journal_entry import JournalEntry
                entity = self.db.query(JournalEntry).filter(JournalEntry.id == entity_id).first()
                if entity:
                    return f"JE-{entity.entry_number}" if hasattr(entity, 'entry_number') and entity.entry_number else entity_id[:8]

            elif entity_type_lower == 'payment':
                from app.models.payment import Payment
                entity = self.db.query(Payment).filter(Payment.id == entity_id).first()
                if entity:
                    return f"PAY-{entity.reference}" if hasattr(entity, 'reference') and entity.reference else entity_id[:8]

            elif entity_type_lower == 'receipt':
                from app.models.receipt import Receipt
                entity = self.db.query(Receipt).filter(Receipt.id == entity_id).first()
                if entity:
                    return f"REC-{entity.receipt_number}" if hasattr(entity, 'receipt_number') and entity.receipt_number else entity_id[:8]

            elif entity_type_lower in ['stock_transfer', 'stocktransfer']:
                from app.models.stock_transfer import StockTransfer
                entity = self.db.query(StockTransfer).filter(StockTransfer.id == entity_id).first()
                if entity:
                    return f"ST-{entity.transfer_number}" if hasattr(entity, 'transfer_number') and entity.transfer_number else entity_id[:8]

        except Exception as e:
            # Log but don't fail
            import logging
            logging.debug(f"Could not get entity name for {entity_type}/{entity_id}: {e}")

        return entity_id[:8] + "..." if entity_id else None

    # ========== Update Notifications ==========

    def mark_as_read(
        self,
        user_id: str,
        notification_ids: Optional[List[str]] = None
    ) -> int:
        """Mark notifications as read. If no IDs provided, marks all as read."""
        query = self.db.query(NotificationUser).filter(
            NotificationUser.user_id == user_id,
            NotificationUser.read == False
        )

        if notification_ids:
            query = query.filter(NotificationUser.notification_id.in_(notification_ids))

        now = datetime.utcnow()
        count = query.update({
            NotificationUser.read: True,
            NotificationUser.read_at: now
        }, synchronize_session=False)

        self.db.commit()
        return count

    def dismiss_notification(self, user_id: str, notification_id: str) -> bool:
        """Dismiss a notification (hide from list)"""
        notification_user = self.db.query(NotificationUser).filter(
            NotificationUser.user_id == user_id,
            NotificationUser.notification_id == notification_id
        ).first()

        if notification_user:
            notification_user.dismissed = True
            notification_user.dismissed_at = datetime.utcnow()
            self.db.commit()
            return True
        return False

    # ========== Workflow Notifications ==========

    def get_workflow_notifications(
        self,
        user_id: str,
        unread_only: bool = True,
        limit: int = 20
    ) -> List[Dict[str, Any]]:
        """Get workflow-specific notifications for a user"""
        query = self.db.query(WorkflowNotification).filter(
            WorkflowNotification.recipient_user_id == user_id
        )

        if unread_only:
            query = query.filter(WorkflowNotification.is_read == False)

        results = query.order_by(
            desc(WorkflowNotification.sent_at)
        ).limit(limit).all()

        return [{
            "id": n.id,
            "subject": n.subject,
            "message": n.message,
            "notification_type": n.notification_type,
            "workflow_instance_id": n.workflow_instance_id,
            "is_read": n.is_read,
            "sent_at": n.sent_at.isoformat() if n.sent_at else None,
            "read_at": n.read_at.isoformat() if n.read_at else None
        } for n in results]

    def mark_workflow_notification_read(
        self,
        notification_id: str,
        user_id: str
    ) -> bool:
        """Mark a workflow notification as read"""
        notification = self.db.query(WorkflowNotification).filter(
            WorkflowNotification.id == notification_id,
            WorkflowNotification.recipient_user_id == user_id
        ).first()

        if notification:
            notification.is_read = True
            notification.read_at = datetime.utcnow()
            self.db.commit()
            return True
        return False

    # ========== Helper Methods ==========

    def _get_kind_from_type(self, notification_type: str) -> int:
        """Convert notification type to kind integer"""
        kinds = {
            "info": 1,
            "success": 2,
            "warning": 3,
            "danger": 4,
            "error": 4
        }
        return kinds.get(notification_type, 1)

    def notify_role_users(
        self,
        role_ids: List[str],
        title: str,
        message: str,
        **kwargs
    ) -> Optional[Notification]:
        """Send notification to all users with specified roles"""
        users = self.db.query(User).filter(
            User.role_id.in_(role_ids),
            User.active == True
        ).all()

        if not users:
            return None

        user_ids = [u.id for u in users]
        return self.create_notification(
            title=title,
            message=message,
            user_ids=user_ids,
            **kwargs
        )


# Import String type for cast
from sqlalchemy import String
