"""
Alert Service

Manages in-app alerts for device status changes and system events.
Provides foundation for future notification channels (email, webhook).
"""

import json
import uuid
import logging
from datetime import datetime, timedelta
from typing import Optional, List, Dict, Any

from sqlalchemy.orm import Session
from sqlalchemy import and_, or_

from backend.models import Alert, RosterUnit

logger = logging.getLogger(__name__)


class AlertService:
    """
    Service for managing alerts.

    Handles alert lifecycle:
    - Create alerts from various triggers
    - Query active alerts
    - Acknowledge/resolve/dismiss alerts
    - (Future) Dispatch to notification channels
    """

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

    def create_alert(
        self,
        alert_type: str,
        title: str,
        message: str = None,
        severity: str = "warning",
        unit_id: str = None,
        project_id: str = None,
        location_id: str = None,
        schedule_id: str = None,
        metadata: dict = None,
        expires_hours: int = 24,
    ) -> Alert:
        """
        Create a new alert.

        Args:
            alert_type: Type of alert (device_offline, device_online, schedule_failed)
            title: Short alert title
            message: Detailed description
            severity: info, warning, or critical
            unit_id: Related unit ID (optional)
            project_id: Related project ID (optional)
            location_id: Related location ID (optional)
            schedule_id: Related schedule ID (optional)
            metadata: Additional JSON data
            expires_hours: Hours until auto-expiry (default 24)

        Returns:
            Created Alert instance
        """
        alert = Alert(
            id=str(uuid.uuid4()),
            alert_type=alert_type,
            title=title,
            message=message,
            severity=severity,
            unit_id=unit_id,
            project_id=project_id,
            location_id=location_id,
            schedule_id=schedule_id,
            alert_metadata=json.dumps(metadata) if metadata else None,
            status="active",
            expires_at=datetime.utcnow() + timedelta(hours=expires_hours),
        )

        self.db.add(alert)
        self.db.commit()
        self.db.refresh(alert)

        logger.info(f"Created alert: {alert.title} ({alert.alert_type})")
        return alert

    def create_device_offline_alert(
        self,
        unit_id: str,
        consecutive_failures: int = 0,
        last_error: str = None,
    ) -> Optional[Alert]:
        """
        Create alert when device becomes unreachable.

        Only creates if no active offline alert exists for this device.

        Args:
            unit_id: The unit that went offline
            consecutive_failures: Number of consecutive poll failures
            last_error: Last error message from polling

        Returns:
            Created Alert or None if alert already exists
        """
        # Check if active offline alert already exists
        existing = self.db.query(Alert).filter(
            and_(
                Alert.unit_id == unit_id,
                Alert.alert_type == "device_offline",
                Alert.status == "active",
            )
        ).first()

        if existing:
            logger.debug(f"Offline alert already exists for {unit_id}")
            return None

        # Get unit info for title
        unit = self.db.query(RosterUnit).filter_by(id=unit_id).first()
        unit_name = unit.id if unit else unit_id

        # Determine severity based on failure count
        severity = "critical" if consecutive_failures >= 5 else "warning"

        return self.create_alert(
            alert_type="device_offline",
            title=f"{unit_name} is offline",
            message=f"Device has been unreachable after {consecutive_failures} failed connection attempts."
                    + (f" Last error: {last_error}" if last_error else ""),
            severity=severity,
            unit_id=unit_id,
            metadata={
                "consecutive_failures": consecutive_failures,
                "last_error": last_error,
            },
            expires_hours=48,  # Offline alerts stay longer
        )

    def resolve_device_offline_alert(self, unit_id: str) -> Optional[Alert]:
        """
        Auto-resolve offline alert when device comes back online.

        Also creates an "device_online" info alert to notify user.

        Args:
            unit_id: The unit that came back online

        Returns:
            The resolved Alert or None if no alert existed
        """
        # Find active offline alert
        alert = self.db.query(Alert).filter(
            and_(
                Alert.unit_id == unit_id,
                Alert.alert_type == "device_offline",
                Alert.status == "active",
            )
        ).first()

        if not alert:
            return None

        # Resolve the offline alert
        alert.status = "resolved"
        alert.resolved_at = datetime.utcnow()
        self.db.commit()

        logger.info(f"Resolved offline alert for {unit_id}")

        # Create online notification
        unit = self.db.query(RosterUnit).filter_by(id=unit_id).first()
        unit_name = unit.id if unit else unit_id

        self.create_alert(
            alert_type="device_online",
            title=f"{unit_name} is back online",
            message="Device connection has been restored.",
            severity="info",
            unit_id=unit_id,
            expires_hours=6,  # Info alerts expire quickly
        )

        return alert

    def create_schedule_failed_alert(
        self,
        schedule_id: str,
        action_type: str,
        unit_id: str = None,
        error_message: str = None,
        project_id: str = None,
        location_id: str = None,
    ) -> Alert:
        """
        Create alert when a scheduled action fails.

        Args:
            schedule_id: The ScheduledAction or RecurringSchedule ID
            action_type: start, stop, download, cycle
            unit_id: Related unit
            error_message: Error from execution
            project_id: Related project
            location_id: Related location

        Returns:
            Created Alert
        """
        return self.create_alert(
            alert_type="schedule_failed",
            title=f"Scheduled {action_type} failed",
            message=error_message or f"The scheduled {action_type} action did not complete successfully.",
            severity="warning",
            unit_id=unit_id,
            project_id=project_id,
            location_id=location_id,
            schedule_id=schedule_id,
            metadata={"action_type": action_type},
            expires_hours=24,
        )

    def create_schedule_completed_alert(
        self,
        schedule_id: str,
        action_type: str,
        unit_id: str = None,
        project_id: str = None,
        location_id: str = None,
        metadata: dict = None,
    ) -> Alert:
        """
        Create alert when a scheduled action completes successfully.

        Args:
            schedule_id: The ScheduledAction ID
            action_type: start, stop, download, cycle
            unit_id: Related unit
            project_id: Related project
            location_id: Related location
            metadata: Additional info (e.g., downloaded folder, index numbers)

        Returns:
            Created Alert
        """
        # Build descriptive message based on action type and metadata
        if action_type == "stop" and metadata:
            download_folder = metadata.get("downloaded_folder")
            download_success = metadata.get("download_success", False)
            if download_success and download_folder:
                message = f"Measurement stopped and data downloaded ({download_folder})"
            elif download_success is False and metadata.get("download_attempted"):
                message = "Measurement stopped but download failed"
            else:
                message = "Measurement stopped successfully"
        elif action_type == "start" and metadata:
            new_index = metadata.get("new_index")
            if new_index is not None:
                message = f"Measurement started (index {new_index:04d})"
            else:
                message = "Measurement started successfully"
        else:
            message = f"Scheduled {action_type} completed successfully"

        return self.create_alert(
            alert_type="schedule_completed",
            title=f"Scheduled {action_type} completed",
            message=message,
            severity="info",
            unit_id=unit_id,
            project_id=project_id,
            location_id=location_id,
            schedule_id=schedule_id,
            metadata={"action_type": action_type, **(metadata or {})},
            expires_hours=12,  # Info alerts expire quickly
        )

    def get_active_alerts(
        self,
        project_id: str = None,
        unit_id: str = None,
        alert_type: str = None,
        min_severity: str = None,
        limit: int = 50,
    ) -> List[Alert]:
        """
        Query active alerts with optional filters.

        Args:
            project_id: Filter by project
            unit_id: Filter by unit
            alert_type: Filter by alert type
            min_severity: Minimum severity (info, warning, critical)
            limit: Maximum results

        Returns:
            List of matching alerts
        """
        query = self.db.query(Alert).filter(Alert.status == "active")

        if project_id:
            query = query.filter(Alert.project_id == project_id)

        if unit_id:
            query = query.filter(Alert.unit_id == unit_id)

        if alert_type:
            query = query.filter(Alert.alert_type == alert_type)

        if min_severity:
            # Map severity to numeric for comparison
            severity_levels = {"info": 1, "warning": 2, "critical": 3}
            min_level = severity_levels.get(min_severity, 1)

            if min_level == 2:
                query = query.filter(Alert.severity.in_(["warning", "critical"]))
            elif min_level == 3:
                query = query.filter(Alert.severity == "critical")

        return query.order_by(Alert.created_at.desc()).limit(limit).all()

    def get_all_alerts(
        self,
        status: str = None,
        project_id: str = None,
        unit_id: str = None,
        alert_type: str = None,
        limit: int = 50,
        offset: int = 0,
    ) -> List[Alert]:
        """
        Query all alerts with optional filters (includes non-active).

        Args:
            status: Filter by status (active, acknowledged, resolved, dismissed)
            project_id: Filter by project
            unit_id: Filter by unit
            alert_type: Filter by alert type
            limit: Maximum results
            offset: Pagination offset

        Returns:
            List of matching alerts
        """
        query = self.db.query(Alert)

        if status:
            query = query.filter(Alert.status == status)

        if project_id:
            query = query.filter(Alert.project_id == project_id)

        if unit_id:
            query = query.filter(Alert.unit_id == unit_id)

        if alert_type:
            query = query.filter(Alert.alert_type == alert_type)

        return (
            query.order_by(Alert.created_at.desc())
            .offset(offset)
            .limit(limit)
            .all()
        )

    def get_active_alert_count(self) -> int:
        """Get count of active alerts for badge display."""
        return self.db.query(Alert).filter(Alert.status == "active").count()

    def acknowledge_alert(self, alert_id: str) -> Optional[Alert]:
        """
        Mark alert as acknowledged.

        Args:
            alert_id: Alert to acknowledge

        Returns:
            Updated Alert or None if not found
        """
        alert = self.db.query(Alert).filter_by(id=alert_id).first()
        if not alert:
            return None

        alert.status = "acknowledged"
        alert.acknowledged_at = datetime.utcnow()
        self.db.commit()

        logger.info(f"Acknowledged alert: {alert.title}")
        return alert

    def dismiss_alert(self, alert_id: str) -> Optional[Alert]:
        """
        Dismiss alert (user chose to ignore).

        Args:
            alert_id: Alert to dismiss

        Returns:
            Updated Alert or None if not found
        """
        alert = self.db.query(Alert).filter_by(id=alert_id).first()
        if not alert:
            return None

        alert.status = "dismissed"
        self.db.commit()

        logger.info(f"Dismissed alert: {alert.title}")
        return alert

    def resolve_alert(self, alert_id: str) -> Optional[Alert]:
        """
        Manually resolve an alert.

        Args:
            alert_id: Alert to resolve

        Returns:
            Updated Alert or None if not found
        """
        alert = self.db.query(Alert).filter_by(id=alert_id).first()
        if not alert:
            return None

        alert.status = "resolved"
        alert.resolved_at = datetime.utcnow()
        self.db.commit()

        logger.info(f"Resolved alert: {alert.title}")
        return alert

    def cleanup_expired_alerts(self) -> int:
        """
        Remove alerts past their expiration time.

        Returns:
            Number of alerts cleaned up
        """
        now = datetime.utcnow()
        expired = self.db.query(Alert).filter(
            and_(
                Alert.expires_at.isnot(None),
                Alert.expires_at < now,
                Alert.status == "active",
            )
        ).all()

        count = len(expired)
        for alert in expired:
            alert.status = "dismissed"

        if count > 0:
            self.db.commit()
            logger.info(f"Cleaned up {count} expired alerts")

        return count


def get_alert_service(db: Session) -> AlertService:
    """Get an AlertService instance with the given database session."""
    return AlertService(db)
