from typing import Dict, List, Optional
import logging
import os
import asyncio
from datetime import datetime, timedelta
from dotenv import load_dotenv

load_dotenv()

logger = logging.getLogger(__name__)

# Check if emergentintegrations is available
try:
    from emergentintegrations.llm.chat import LlmChat, UserMessage
    EMERGENT_AVAILABLE = True
except ImportError:
    EMERGENT_AVAILABLE = False
    logger.warning("emergentintegrations not available - AI troubleshooting will be limited")


class AITroubleshooter:
    """AI-powered network troubleshooting engine"""
    
    def __init__(self, db):
        self.db = db
        self.llm_key = os.getenv('EMERGENT_LLM_KEY')
        self.manual_llm_key = None
        
        # Common network issues database
        self.known_issues = {
            'high_cpu': {
                'symptoms': ['slow response', 'timeout', 'latency'],
                'causes': ['heavy traffic', 'routing loop', 'attack', 'process issue'],
                'solutions': ['check processes', 'review traffic', 'analyze logs']
            },
            'link_down': {
                'symptoms': ['no connectivity', 'interface down', 'no carrier'],
                'causes': ['cable issue', 'port disabled', 'SFP failure', 'speed mismatch'],
                'solutions': ['check cable', 'verify port config', 'replace SFP', 'match speed/duplex']
            },
            'packet_loss': {
                'symptoms': ['intermittent connectivity', 'slow transfer', 'retransmissions'],
                'causes': ['congestion', 'bad cable', 'duplex mismatch', 'buffer overflow'],
                'solutions': ['upgrade bandwidth', 'replace cable', 'fix duplex', 'enable QoS']
            },
            'onu_offline': {
                'symptoms': ['customer offline', 'ONU unreachable', 'no signal'],
                'causes': ['fiber break', 'ONU power', 'OLT port issue', 'distance exceeded'],
                'solutions': ['check fiber', 'verify ONU power', 'check OLT port', 'test optical levels']
            },
            'dhcp_failure': {
                'symptoms': ['no IP address', 'APIPA address', 'limited connectivity'],
                'causes': ['DHCP server down', 'pool exhausted', 'VLAN mismatch', 'relay issue'],
                'solutions': ['verify DHCP service', 'expand pool', 'check VLAN', 'configure relay']
            }
        }
    
    def set_manual_key(self, key: str, provider: str = 'openai'):
        """Set manual LLM key"""
        self.manual_llm_key = key
        self.manual_provider = provider
    
    def _get_llm_chat(self, session_id: str, system_message: str) -> Optional['LlmChat']:
        """Initialize LLM chat"""
        if not EMERGENT_AVAILABLE:
            return None
        
        key = self.manual_llm_key or self.llm_key
        if not key:
            return None
        
        try:
            chat = LlmChat(
                api_key=key,
                session_id=session_id,
                system_message=system_message
            ).with_model("openai", "gpt-5.4")
            return chat
        except Exception as e:
            logger.error(f"Failed to initialize LLM: {e}")
            return None
    
    async def analyze_device_health(self, device_id: str, org_id: str = None) -> Dict:
        """Comprehensive device health analysis"""
        query = {'id': device_id}
        if org_id:
            query['organization_id'] = org_id
        device = await self.db.devices.find_one(query)
        if not device:
            raise Exception("Device not found")
        
        issues = []
        recommendations = []
        health_score = 100
        
        # Check device status
        if device.get('status') == 'offline':
            issues.append({
                'type': 'critical',
                'issue': 'Device is offline',
                'impact': 'No management access'
            })
            health_score -= 50
        
        # Check CPU
        cpu = device.get('cpu_usage') or 0
        if cpu and cpu > 90:
            issues.append({
                'type': 'critical',
                'issue': f'Critical CPU usage: {cpu}%',
                'impact': 'Performance degradation'
            })
            health_score -= 30
        elif cpu > 70:
            issues.append({
                'type': 'warning',
                'issue': f'High CPU usage: {cpu}%',
                'impact': 'Potential performance issues'
            })
            health_score -= 15
        
        # Check memory
        memory = device.get('memory_usage') or 0
        if memory and memory > 90:
            issues.append({
                'type': 'critical',
                'issue': f'Critical memory usage: {memory}%',
                'impact': 'Risk of service crash'
            })
            health_score -= 25
        elif memory > 70:
            issues.append({
                'type': 'warning',
                'issue': f'High memory usage: {memory}%',
                'impact': 'Reduced buffer capacity'
            })
            health_score -= 10
        
        # Check last seen
        last_seen = device.get('last_seen')
        if last_seen:
            hours_ago = (datetime.utcnow() - last_seen).total_seconds() / 3600
            if hours_ago > 24:
                issues.append({
                    'type': 'warning',
                    'issue': f'Device not polled for {int(hours_ago)} hours',
                    'impact': 'Stale monitoring data'
                })
                health_score -= 10
        
        # Check SNMP
        if not device.get('snmp_enabled'):
            recommendations.append({
                'type': 'improvement',
                'suggestion': 'Enable SNMP for better monitoring',
                'priority': 'medium'
            })
            health_score -= 5
        
        # Check backup status
        if not device.get('last_backup_at'):
            recommendations.append({
                'type': 'risk',
                'suggestion': 'No backup found - create configuration backup',
                'priority': 'high'
            })
            health_score -= 10
        
        # Ensure score is between 0 and 100
        health_score = max(0, min(100, health_score))
        
        return {
            'device_id': device_id,
            'device_ip': device.get('ip'),
            'vendor': device.get('vendor'),
            'health_score': health_score,
            'health_status': self._get_health_status(health_score),
            'issues': issues,
            'recommendations': recommendations,
            'analyzed_at': datetime.utcnow().isoformat()
        }
    
    def _get_health_status(self, score: int) -> str:
        """Convert health score to status"""
        if score >= 90:
            return 'healthy'
        elif score >= 70:
            return 'warning'
        elif score >= 50:
            return 'degraded'
        else:
            return 'critical'
    
    async def troubleshoot_issue(self, device_id: str, issue_description: str, org_id: str = None) -> Dict:
        """AI-powered troubleshooting for specific issue"""
        query = {'id': device_id}
        if org_id:
            query['organization_id'] = org_id
        device = await self.db.devices.find_one(query)
        if not device:
            raise Exception("Device not found")
        
        # Get recent alerts for context
        recent_alerts = await self.db.alerts.find({
            'device_id': device_id,
            'timestamp': {'$gte': datetime.utcnow() - timedelta(hours=24)}
        }).to_list(10)
        
        # Get device metrics for context
        recent_metrics = await self.db.device_metrics.find({
            'device_id': device_id
        }).sort('timestamp', -1).limit(5).to_list(5)
        
        # Build context
        context = {
            'device': {
                'ip': device.get('ip'),
                'vendor': device.get('vendor'),
                'device_type': device.get('device_type'),
                'status': device.get('status'),
                'cpu': device.get('cpu_usage'),
                'memory': device.get('memory_usage')
            },
            'recent_alerts': [
                {'severity': a.get('severity'), 'message': a.get('message')}
                for a in recent_alerts
            ],
            'metrics_trend': 'available' if recent_metrics else 'no data'
        }
        
        # Try AI troubleshooting
        system_message = f"""You are an expert network troubleshooter specializing in {device.get('vendor', 'network')} devices.
Analyze issues and provide step-by-step troubleshooting guides.
Be specific with commands and procedures.
Consider the device context provided.
Format output clearly with numbered steps."""

        chat = self._get_llm_chat(f"troubleshoot-{device_id}", system_message)
        
        if chat:
            try:
                user_message = UserMessage(
                    text=f"""Device Context:
{context}

Issue Description:
{issue_description}

Please provide:
1. Possible causes
2. Step-by-step troubleshooting procedure
3. Specific commands to diagnose
4. Resolution steps"""
                )
                
                response = await chat.send_message(user_message)
                
                return {
                    'device_id': device_id,
                    'issue': issue_description,
                    'ai_analysis': True,
                    'troubleshooting_guide': response.text,
                    'context': context,
                    'generated_at': datetime.utcnow().isoformat()
                }
            except Exception as e:
                logger.error(f"AI troubleshooting failed: {e}")
        
        # Fallback to rule-based troubleshooting
        return await self._rule_based_troubleshoot(device_id, device, issue_description, context)
    
    async def _rule_based_troubleshoot(self, device_id: str, device: Dict, 
                                       issue: str, context: Dict) -> Dict:
        """Rule-based troubleshooting when AI is not available"""
        issue_lower = issue.lower()
        
        matched_issues = []
        for issue_type, data in self.known_issues.items():
            # Check if any symptom matches
            for symptom in data['symptoms']:
                if symptom in issue_lower:
                    matched_issues.append({
                        'type': issue_type,
                        'data': data
                    })
                    break
        
        if not matched_issues:
            return {
                'device_id': device_id,
                'issue': issue,
                'ai_analysis': False,
                'troubleshooting_guide': self._generate_generic_guide(device, issue),
                'context': context,
                'message': 'AI not available - using rule-based analysis'
            }
        
        # Build guide from matched issues
        guide = f"# Troubleshooting Guide for: {issue}\n\n"
        
        for match in matched_issues:
            issue_type = match['type'].replace('_', ' ').title()
            data = match['data']
            
            guide += f"## Possible Issue: {issue_type}\n\n"
            guide += "### Possible Causes:\n"
            for cause in data['causes']:
                guide += f"- {cause}\n"
            
            guide += "\n### Recommended Solutions:\n"
            for i, solution in enumerate(data['solutions'], 1):
                guide += f"{i}. {solution}\n"
            guide += "\n"
        
        # Add vendor-specific commands
        vendor = device.get('vendor', '').lower()
        guide += self._get_vendor_diagnostic_commands(vendor)
        
        return {
            'device_id': device_id,
            'issue': issue,
            'ai_analysis': False,
            'troubleshooting_guide': guide,
            'matched_patterns': [m['type'] for m in matched_issues],
            'context': context,
            'message': 'AI not available - using rule-based analysis'
        }
    
    def _generate_generic_guide(self, device: Dict, issue: str) -> str:
        """Generate generic troubleshooting guide"""
        vendor = device.get('vendor', 'Unknown')
        
        return f"""# Generic Troubleshooting Guide

## Issue: {issue}

### Basic Steps:

1. **Verify Connectivity**
   - Ping the device from management station
   - Check physical connections
   - Verify power status

2. **Check Device Status**
   - Review device logs
   - Check CPU and memory utilization
   - Verify interface status

3. **Review Recent Changes**
   - Check configuration changes
   - Review recent updates
   - Verify any scheduled maintenance

4. **Collect Diagnostic Data**
   - Capture current configuration
   - Export logs
   - Document symptoms

### Vendor-Specific ({vendor}):
{self._get_vendor_diagnostic_commands(vendor)}

### If Issue Persists:
- Escalate to vendor support
- Check for known bugs/issues
- Consider rollback if recent change related
"""
    
    def _get_vendor_diagnostic_commands(self, vendor: str) -> str:
        """Get vendor-specific diagnostic commands"""
        vendor = vendor.lower()
        
        if 'mikrotik' in vendor:
            return """
### MikroTik Diagnostic Commands:
```
/system resource print
/interface print detail
/log print
/ip route print
/queue simple print
```
"""
        elif 'cisco' in vendor:
            return """
### Cisco Diagnostic Commands:
```
show version
show ip interface brief
show logging
show processes cpu
show interface status
```
"""
        elif 'huawei' in vendor:
            return """
### Huawei Diagnostic Commands:
```
display version
display interface brief
display logbuffer
display cpu-usage
display memory-usage
```
"""
        else:
            return """
### Generic Diagnostic Commands:
- Check system status
- View interface status
- Review logs
- Check routing table
"""
    
    async def ai_analyze_logs(self, device_id: str, logs: str, org_id: str = None) -> Dict:
        """AI-powered log analysis"""
        query = {'id': device_id}
        if org_id:
            query['organization_id'] = org_id
        device = await self.db.devices.find_one(query)
        vendor = device.get('vendor', 'Unknown') if device else 'Unknown'
        
        system_message = f"""You are an expert at analyzing {vendor} device logs.
Identify errors, warnings, and anomalies.
Correlate events to find root causes.
Provide actionable insights and recommendations.
Be concise but thorough."""

        chat = self._get_llm_chat(f"log-analysis-{device_id}", system_message)
        
        if not chat:
            return {
                'device_id': device_id,
                'ai_analysis': False,
                'summary': 'AI log analysis not available',
                'recommendation': 'Please review logs manually or enable AI features'
            }
        
        try:
            user_message = UserMessage(
                text=f"""Analyze these device logs and provide:
1. Summary of key events
2. Any errors or warnings
3. Potential issues identified
4. Recommended actions

Logs:
{logs[:8000]}"""  # Limit log size
            )
            
            response = await chat.send_message(user_message)
            
            return {
                'device_id': device_id,
                'ai_analysis': True,
                'analysis': response.text,
                'log_size': len(logs),
                'analyzed_at': datetime.utcnow().isoformat()
            }
        except Exception as e:
            logger.error(f"AI log analysis failed: {e}")
            return {
                'device_id': device_id,
                'ai_analysis': False,
                'error': str(e)
            }
    
    async def get_network_health_summary(self, org_id: str = None) -> Dict:
        """Get overall network health summary"""
        org_filter = {"organization_id": org_id} if org_id else {}
        
        total = await self.db.devices.count_documents(org_filter)
        online = await self.db.devices.count_documents({**org_filter, 'status': 'online'})
        offline = await self.db.devices.count_documents({**org_filter, 'status': 'offline'})
        warning = await self.db.devices.count_documents({**org_filter, 'status': 'warning'})
        
        critical_alerts = await self.db.alerts.find({
            **org_filter,
            'severity': 'critical',
            'acknowledged': {'$ne': True},
            'timestamp': {'$gte': datetime.utcnow() - timedelta(hours=24)}
        }).to_list(10)
        
        problem_devices = await self.db.devices.find({
            **org_filter,
            '$or': [
                {'cpu_usage': {'$gt': 80}},
                {'memory_usage': {'$gt': 80}},
                {'status': 'offline'}
            ]
        }).to_list(20)
        
        # Calculate overall health
        if total == 0:
            overall_health = 100
        else:
            health_penalty = (offline * 10) + (warning * 5) + (len(critical_alerts) * 5)
            overall_health = max(0, 100 - (health_penalty / total * 10))
        
        return {
            'overall_health': round(overall_health, 1),
            'health_status': self._get_health_status(int(overall_health)),
            'device_summary': {
                'total': total,
                'online': online,
                'offline': offline,
                'warning': warning
            },
            'critical_alerts_count': len(critical_alerts),
            'critical_alerts': [
                {
                    'device_id': a.get('device_id'),
                    'message': a.get('message'),
                    'timestamp': a.get('timestamp').isoformat() if a.get('timestamp') else None
                }
                for a in critical_alerts
            ],
            'problem_devices': [
                {
                    'id': d.get('id'),
                    'ip': d.get('ip'),
                    'status': d.get('status'),
                    'cpu': d.get('cpu_usage'),
                    'memory': d.get('memory_usage')
                }
                for d in problem_devices
            ],
            'generated_at': datetime.utcnow().isoformat()
        }
    
    async def generate_ai_response(self, system_prompt: str, user_message: str) -> str:
        """Generate AI response for chat assistant"""
        chat = self._get_llm_chat(f"chat-{datetime.utcnow().timestamp()}", system_prompt)
        
        if not chat:
            return "I'm sorry, but the AI assistant is currently not available. Please try again later or contact support."
        
        try:
            message = UserMessage(text=user_message)
            response = await chat.send_message(message)
            return response.text
        except Exception as e:
            logger.error(f"AI response generation failed: {e}")
            return f"I apologize, but I encountered an error while processing your request. Please try again."

    async def predict_issues(self, device_id: str, org_id: str = None) -> Dict:
        """Predict potential issues based on trends"""
        query = {'id': device_id}
        if org_id:
            query['organization_id'] = org_id
        device = await self.db.devices.find_one(query)
        if not device:
            raise Exception("Device not found")
        
        # Get metrics history
        metrics = await self.db.device_metrics.find({
            'device_id': device_id
        }).sort('timestamp', -1).limit(100).to_list(100)
        
        predictions = []
        
        if not metrics:
            return {
                'device_id': device_id,
                'predictions': [],
                'message': 'Not enough historical data for predictions'
            }
        
        # Analyze CPU trend
        cpu_values = [m.get('cpu_usage', 0) for m in metrics if m.get('cpu_usage')]
        if len(cpu_values) >= 10:
            avg_cpu = sum(cpu_values[:10]) / 10  # Recent average
            old_avg = sum(cpu_values[-10:]) / 10 if len(cpu_values) >= 20 else avg_cpu
            
            if avg_cpu > old_avg * 1.2:  # 20% increase
                predictions.append({
                    'type': 'cpu_trending_up',
                    'severity': 'warning',
                    'message': f'CPU usage trending up: {old_avg:.1f}% -> {avg_cpu:.1f}%',
                    'predicted_impact': 'Possible performance degradation',
                    'recommendation': 'Monitor closely, consider capacity planning'
                })
        
        # Analyze memory trend
        mem_values = [m.get('memory_usage', 0) for m in metrics if m.get('memory_usage')]
        if len(mem_values) >= 10:
            avg_mem = sum(mem_values[:10]) / 10
            old_avg = sum(mem_values[-10:]) / 10 if len(mem_values) >= 20 else avg_mem
            
            if avg_mem > old_avg * 1.15:  # 15% increase
                predictions.append({
                    'type': 'memory_trending_up',
                    'severity': 'warning',
                    'message': f'Memory usage trending up: {old_avg:.1f}% -> {avg_mem:.1f}%',
                    'predicted_impact': 'Possible memory exhaustion',
                    'recommendation': 'Check for memory leaks, plan upgrade'
                })
        
        return {
            'device_id': device_id,
            'predictions': predictions,
            'data_points_analyzed': len(metrics),
            'analyzed_at': datetime.utcnow().isoformat()
        }


# Global instance
ai_troubleshooter = None
