#!/usr/bin/env python3
"""
Server-side Telegram Notification Service for GNSS Guard

Sends alerts to Telegram for GPS validation state changes:
- Sources becoming missing or recovering
- Sources becoming stale or recovering  
- Distance threshold breaches (possible jamming/spoofing)
"""

import json
import logging
import requests
from datetime import datetime
from typing import Dict, Any, List, Optional, Set

from sqlalchemy.orm import Session

from config import get_config
from models import Asset, AssetNotificationState

logger = logging.getLogger("gnss_guard.server.telegram")


class TelegramService:
    """Server-side Telegram notification service"""
    
    def __init__(self):
        """Initialize Telegram service with config"""
        config = get_config()
        self.bot_token = config.telegram_bot_token
        self.default_chat_id = config.telegram_chat_id
        self.enabled = config.telegram_enabled
        
        if self.enabled:
            self.api_url = f"https://api.telegram.org/bot{self.bot_token}"
            logger.info("Telegram service initialized")
        else:
            self.api_url = None
            logger.info("Telegram service disabled (no bot token or chat ID configured)")
    
    @staticmethod
    def escape_html(text: str) -> str:
        """Escape HTML special characters for Telegram HTML parsing"""
        text = str(text)
        text = text.replace('&', '&amp;')
        text = text.replace('<', '&lt;')
        text = text.replace('>', '&gt;')
        return text
    
    def _send_message(self, chat_id: str, message: str) -> bool:
        """Send a message to Telegram"""
        if not self.enabled:
            return False
        
        try:
            url = f"{self.api_url}/sendMessage"
            payload = {
                "chat_id": chat_id,
                "text": message,
                "parse_mode": "HTML",
                "disable_web_page_preview": True
            }
            
            response = requests.post(url, json=payload, timeout=10)
            
            if response.status_code == 200:
                return True
            else:
                logger.error(f"Telegram API error: {response.status_code} - {response.text}")
                return False
                
        except Exception as e:
            logger.error(f"Failed to send Telegram message: {e}")
            return False
    
    def _get_chat_id_for_asset(self, asset: Asset) -> Optional[str]:
        """Get the chat ID to use for an asset (asset-specific or default)"""
        if not asset.telegram_enabled:
            return None
        return asset.telegram_chat_id or self.default_chat_id
    
    def process_validation(
        self,
        db: Session,
        asset: Asset,
        validation_data: Dict[str, Any]
    ) -> bool:
        """
        Process a validation submission and send notification if state changed.
        Also handles online/offline state transitions.
        
        Args:
            db: Database session
            asset: Asset that submitted the validation
            validation_data: Validation data from the submission
            
        Returns:
            bool: True if notification was sent
        """
        chat_id = self._get_chat_id_for_asset(asset)
        
        # Get or create notification state for this asset
        state = db.query(AssetNotificationState).filter(
            AssetNotificationState.asset_id == asset.id
        ).first()
        
        if not state:
            state = AssetNotificationState(asset_id=asset.id)
            db.add(state)
            db.flush()
        
        notification_sent = False
        now = datetime.utcnow()
        
        # Check if asset was offline and is now back online
        was_offline = state.is_online == False and state.last_validation_at is not None
        
        if was_offline and self.enabled and chat_id:
            # Calculate how long it was offline
            offline_duration = (now - state.last_validation_at).total_seconds() if state.last_validation_at else None
            
            notification_sent = self.send_asset_online_alert(
                chat_id=chat_id,
                asset_name=asset.name,
                offline_duration_seconds=offline_duration
            )
        
        # Update online status and last validation time
        state.is_online = True
        state.last_validation_at = now
        
        # Skip further processing if Telegram is disabled
        if not self.enabled or not chat_id:
            db.commit()
            return notification_sent
        
        # Parse current state from validation
        sources_missing = set(validation_data.get("sources_missing", []))
        sources_stale = set(validation_data.get("sources_stale", []))
        validation_details = validation_data.get("validation_details", {})
        threshold = validation_details.get("threshold_meters", 0)
        max_distance = validation_details.get("max_distance_meters", 0)
        threshold_breached = max_distance > threshold if max_distance and threshold else False
        
        # Parse previous state
        prev_missing = set(json.loads(state.prev_sources_missing or "[]"))
        prev_stale = set(json.loads(state.prev_sources_stale or "[]"))
        prev_threshold_breached = state.prev_threshold_breached or False
        
        # Detect changes
        missing_added = sources_missing - prev_missing
        missing_removed = prev_missing - sources_missing
        stale_added = sources_stale - prev_stale
        stale_removed = prev_stale - sources_stale
        threshold_changed = threshold_breached != prev_threshold_breached
        
        has_state_change = (
            missing_added or missing_removed or
            stale_added or stale_removed or
            threshold_changed
        )
        
        if has_state_change:
            logger.info(f"State change detected for {asset.name}")
            
            # Build and send notification
            source_coordinates = validation_data.get("source_coordinates", {})
            
            message = self._build_state_change_message(
                asset_name=asset.name,
                missing_added=missing_added,
                missing_removed=missing_removed,
                stale_added=stale_added,
                stale_removed=stale_removed,
                threshold_breached=threshold_breached,
                prev_threshold_breached=prev_threshold_breached,
                max_distance_meters=max_distance,
                threshold_meters=threshold,
                source_coordinates=source_coordinates
            )
            
            if self._send_message(chat_id, message):
                state.last_notification_at = now
                logger.info(f"Notification sent for {asset.name}")
                notification_sent = True
        
        # Update state
        state.prev_sources_missing = json.dumps(list(sources_missing))
        state.prev_sources_stale = json.dumps(list(sources_stale))
        state.prev_threshold_breached = threshold_breached
        
        db.commit()
        
        return notification_sent
    
    def _build_state_change_message(
        self,
        asset_name: str,
        missing_added: Set[str],
        missing_removed: Set[str],
        stale_added: Set[str],
        stale_removed: Set[str],
        threshold_breached: bool,
        prev_threshold_breached: bool,
        max_distance_meters: float,
        threshold_meters: float,
        source_coordinates: Dict[str, Any]
    ) -> str:
        """Build the state change notification message"""
        timestamp = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC")
        
        # Determine if this is a degradation or recovery
        is_degradation = missing_added or stale_added or (threshold_breached and not prev_threshold_breached)
        is_recovery = missing_removed or stale_removed or (not threshold_breached and prev_threshold_breached)
        
        if is_degradation and not is_recovery:
            emoji = "🚨"
            title = "GNSS STATE DEGRADED"
        elif is_recovery and not is_degradation:
            emoji = "✅"
            title = "GNSS STATE RECOVERED"
        else:
            emoji = "⚠️"
            title = "GNSS STATE CHANGED"
        
        message = (
            f"{emoji} <b>{title}</b>\n\n"
            f"📍 <b>Asset:</b> {self.escape_html(asset_name)}\n"
            f"⏰ <b>Time:</b> {timestamp}\n\n"
        )
        
        # Missing sources changes
        if missing_added:
            message += f"❌ <b>Sources now MISSING:</b> {', '.join(sorted(missing_added))}\n"
        if missing_removed:
            message += f"✅ <b>Sources RECOVERED (was missing):</b> {', '.join(sorted(missing_removed))}\n"
        
        # Stale sources changes
        if stale_added:
            message += f"⏱️ <b>Sources now STALE:</b> {', '.join(sorted(stale_added))}\n"
        if stale_removed:
            message += f"✅ <b>Sources RECOVERED (was stale):</b> {', '.join(sorted(stale_removed))}\n"
        
        # Threshold breach changes
        if threshold_breached and not prev_threshold_breached:
            message += (
                f"\n🚨 <b>DISTANCE THRESHOLD BREACHED!</b>\n"
                f"   Max distance: {max_distance_meters:.1f}m (threshold: {threshold_meters:.1f}m)\n"
                f"   ⚠️ Possible GPS jamming or spoofing!\n"
            )
        elif not threshold_breached and prev_threshold_breached:
            message += (
                f"\n✅ <b>Distance threshold OK</b>\n"
                f"   Max distance: {max_distance_meters:.1f}m (threshold: {threshold_meters:.1f}m)\n"
            )
        
        # Current coordinates summary
        if source_coordinates:
            message += f"\n📍 <b>Current Coordinates:</b>\n"
            for source, coords in source_coordinates.items():
                lat = coords.get("latitude", "N/A")
                lon = coords.get("longitude", "N/A")
                message += f"   • {self.escape_html(source)}: {lat}, {lon}\n"
        
        return message
    
    def send_asset_offline_alert(
        self,
        chat_id: str,
        asset_name: str,
        last_seen: datetime,
        offline_threshold_seconds: int = 120
    ) -> bool:
        """Send notification when an asset goes offline (no updates received)"""
        if not self.enabled:
            return False
        
        timestamp = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC")
        last_seen_str = last_seen.strftime("%Y-%m-%d %H:%M:%S UTC") if last_seen else "Unknown"
        
        message = (
            f"📴 <b>ASSET OFFLINE</b>\n\n"
            f"📍 <b>Asset:</b> {self.escape_html(asset_name)}\n"
            f"⏰ <b>Detected at:</b> {timestamp}\n"
            f"🕐 <b>Last seen:</b> {last_seen_str}\n\n"
            f"⚠️ No updates received for over {offline_threshold_seconds} seconds.\n"
            f"Check client connectivity and service status."
        )
        
        result = self._send_message(chat_id, message)
        if result:
            logger.info(f"Offline alert sent for {asset_name}")
        return result
    
    def send_asset_online_alert(
        self,
        chat_id: str,
        asset_name: str,
        offline_duration_seconds: Optional[float] = None
    ) -> bool:
        """Send notification when an asset comes back online"""
        if not self.enabled:
            return False
        
        timestamp = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC")
        
        duration_str = ""
        if offline_duration_seconds:
            if offline_duration_seconds < 60:
                duration_str = f"{int(offline_duration_seconds)} seconds"
            elif offline_duration_seconds < 3600:
                duration_str = f"{int(offline_duration_seconds / 60)} minutes"
            else:
                hours = offline_duration_seconds / 3600
                duration_str = f"{hours:.1f} hours"
        
        message = (
            f"📶 <b>ASSET BACK ONLINE</b>\n\n"
            f"📍 <b>Asset:</b> {self.escape_html(asset_name)}\n"
            f"⏰ <b>Time:</b> {timestamp}\n"
        )
        
        if duration_str:
            message += f"⏱️ <b>Was offline for:</b> {duration_str}\n"
        
        message += f"\n✅ Asset is now reporting normally."
        
        result = self._send_message(chat_id, message)
        if result:
            logger.info(f"Online alert sent for {asset_name}")
        return result
    
    def test_connection(self) -> bool:
        """Test Telegram bot connection"""
        if not self.enabled:
            return False
        
        try:
            url = f"{self.api_url}/getMe"
            response = requests.get(url, timeout=10)
            
            if response.status_code == 200:
                bot_info = response.json()
                logger.info(f"Telegram bot connected: @{bot_info['result']['username']}")
                return True
            else:
                logger.error(f"Telegram connection failed: {response.status_code}")
                return False
                
        except Exception as e:
            logger.error(f"Telegram connection error: {e}")
            return False


# Singleton instance
_telegram_service: Optional[TelegramService] = None


def get_telegram_service() -> TelegramService:
    """Get the singleton Telegram service instance"""
    global _telegram_service
    if _telegram_service is None:
        _telegram_service = TelegramService()
    return _telegram_service

