"""Alert Rule Engine.

Evaluates alert rules against calculated metrics and determines
whether alerts should be triggered.
"""

from datetime import datetime, timedelta
from typing import Dict, List, Optional, Any
from sqlalchemy import select, and_
from sqlalchemy.ext.asyncio import AsyncSession
from src.models.alert import (
    AlertRule,
    Alert,
    RuleEvaluationLog,
    AlertStatus,
    AlertRuleStatusEnum,
)
from src.financial.metrics import MetricsCalculator
import logging

logger = logging.getLogger(__name__)


class RuleEngine:
    """Evaluates alert rules and triggers alerts"""
    
    OPERATORS = {
        '>': lambda a, b: a > b,
        '<': lambda a, b: a < b,
        '>=': lambda a, b: a >= b,
        '<=': lambda a, b: a <= b,
        '=': lambda a, b: a == b,
        '!=': lambda a, b: a != b,
        'contains': lambda a, b: b in str(a),
        'in': lambda a, b: a in b,
    }
    
    def __init__(self, db: AsyncSession, company_id: str):
        self.db = db
        self.company_id = company_id
        self.metrics_calculator = MetricsCalculator(db, company_id)
    
    async def get_active_rules(self) -> List[AlertRule]:
        """Get all active alert rules for the company"""
        result = await self.db.execute(
            select(AlertRule).where(
                and_(
                    AlertRule.company_id == self.company_id,
                    AlertRule.enabled == True,
                    AlertRule.status == AlertRuleStatusEnum.ACTIVE,
                )
            )
        )
        return result.scalars().all()
    
    async def evaluate_rule(self, rule: AlertRule, metrics: Dict = None) -> tuple[bool, Dict]:
        """
        Evaluate a single rule against metrics.
        
        Returns: (alert_triggered, evaluation_data)
        """
        try:
            # Get metrics if not provided
            if metrics is None:
                metrics = await self.metrics_calculator.get_all_metrics()
            
            conditions_met, condition_results = await self._check_conditions(rule.conditions or {}, metrics)

            return conditions_met, {
                'condition_results': condition_results,
                'conditions_met': conditions_met,
                'evaluation_time': datetime.now().isoformat(),
            }
            
        except Exception as e:
            logger.error(f"Error evaluating rule {rule.id}: {e}")
            return False, {'error': str(e)}
    
    async def evaluate_all_rules(self) -> List[tuple[AlertRule, bool, Dict]]:
        """Evaluate all active rules for the company"""
        rules = await self.get_active_rules()
        
        # Get metrics once for efficiency
        metrics = await self.metrics_calculator.get_all_metrics()
        
        results = []
        for rule in rules:
            triggered, data = await self.evaluate_rule(rule, metrics)
            results.append((rule, triggered, data))
        
        return results
    
    async def trigger_alert(self, rule: AlertRule, evaluation_data: Dict) -> Optional[Alert]:
        """
        Create an alert instance when rule is triggered.
        
        Returns: Alert record created
        """
        try:
            # Check if recent alert already exists for this rule
            recent_alert = await self.db.execute(
                select(Alert).where(
                    and_(
                        Alert.rule_id == rule.id,
                        Alert.status == AlertStatus.UNRESOLVED,
                        Alert.created_at > (datetime.now() - timedelta(hours=1))
                    )
                ).limit(1)
            )
            
            if recent_alert.first():
                logger.info(f"Alert for rule {rule.id} already exists")
                return None
            
            # Create new alert
            alert = Alert(
                rule_id=rule.id,
                company_id=self.company_id,
                alert_type=rule.alert_type,
                severity=rule.severity,
                status=AlertStatus.UNRESOLVED,
                title=rule.name,
                message=self._generate_message(rule, evaluation_data),
                metric_data=evaluation_data
            )
            
            self.db.add(alert)
            await self.db.commit()
            await self.db.refresh(alert)
            
            logger.info(f"Alert created: {alert.id} for rule {rule.id}")
            return alert
            
        except Exception as e:
            logger.error(f"Error triggering alert: {e}")
            await self.db.rollback()
            return None
    
    async def process_rules(self) -> Dict:
        """
        Process all rules: evaluate and trigger alerts as needed.
        
        Returns summary of processing
        """
        results = await self.evaluate_all_rules()
        
        triggered_count = 0
        failed_count = 0
        
        for rule, triggered, data in results:
            alert_id = None

            # Trigger alert if conditions met
            if triggered:
                alert = await self.trigger_alert(rule, data)
                if alert:
                    triggered_count += 1
                    alert_id = alert.id
                else:
                    failed_count += 1

            # Log evaluation
            try:
                log = RuleEvaluationLog(
                    rule_id=rule.id,
                    company_id=self.company_id,
                    evaluation_result=data.get('conditions_met', False),
                    metric_values=data.get('condition_results', {}),
                    alert_id=alert_id,
                    skip_reason=None if triggered else 'below_threshold',
                    error_message=data.get('error'),
                )
                self.db.add(log)
            except Exception as e:
                logger.error(f"Error logging evaluation: {e}")
        
        try:
            await self.db.commit()
        except Exception as e:
            logger.error(f"Error committing logs: {e}")
            await self.db.rollback()
        
        return {
            'total_rules_evaluated': len(results),
            'alerts_triggered': triggered_count,
            'errors': failed_count,
            'process_time': datetime.now().isoformat()
        }
    
    def _get_metric_value(self, metrics: Dict, metric_name: str) -> Optional[float]:
        """Extract metric value from metrics dict"""
        if not metric_name:
            return None

        # Handle nested metric names like "budget_variance.variance_percent"
        parts = metric_name.split('.')
        value = metrics
        
        for part in parts:
            if isinstance(value, dict):
                value = value.get(part)
            else:
                return None
        
        # Convert to float if possible
        try:
            return float(value) if value is not None else None
        except (ValueError, TypeError):
            return None
    
    async def _check_conditions(self, conditions: Dict, metrics: Dict) -> tuple[bool, List[Dict[str, Any]]]:
        """
        Check additional conditions beyond the main rule.
        
        Example conditions:
        {
            "and": [
                {"field": "budget_variance.variance_percent", "op": ">", "value": 10},
                {"field": "expense_anomalies", "op": "count_gt", "value": 2}
            ]
        }
        """
        try:
            if not conditions:
                return False, []

            logic = str(conditions.get('type', 'AND')).upper()
            items = conditions.get('conditions', [])
            if not isinstance(items, list):
                return False, []

            results: List[Dict[str, Any]] = []
            flags: List[bool] = []
            for cond in items:
                ok, detail = await self._check_condition(cond, metrics)
                flags.append(ok)
                results.append(detail)

            matched = all(flags) if logic == 'AND' else any(flags)
            return matched, results
        except Exception as e:
            logger.error(f"Error checking conditions: {e}")
            return False, []
    
    async def _check_condition(self, cond: Dict, metrics: Dict) -> tuple[bool, Dict[str, Any]]:
        """Check a single condition"""
        field = cond.get('metric_id') or cond.get('field')
        op = cond.get('operator') or cond.get('op')
        value = cond.get('threshold') if 'threshold' in cond else cond.get('value')
        
        metric_value = self._get_metric_value(metrics, field)
        
        if metric_value is None:
            return False, {
                'metric_name': field,
                'metric_value': None,
                'threshold': value,
                'operator': op,
                'matched': False,
            }
        
        if op in self.OPERATORS:
            matched = self.OPERATORS[op](metric_value, value)
            return matched, {
                'metric_name': field,
                'metric_value': metric_value,
                'threshold': value,
                'operator': op,
                'matched': matched,
            }
        else:
            logger.warning(f"Unknown operator: {op}")
            return False, {
                'metric_name': field,
                'metric_value': metric_value,
                'threshold': value,
                'operator': op,
                'matched': False,
                'error': 'unknown_operator',
            }
    
    def _generate_message(self, rule: AlertRule, evaluation_data: Dict) -> str:
        """Generate human-readable alert message"""
        details = evaluation_data.get('condition_results') or []
        if details:
            first = details[0]
            metric = first.get('metric_name', 'Unknown')
            value = first.get('metric_value')
            threshold = first.get('threshold')
            op = first.get('operator', '>')
            return f"{rule.name}: {metric} ({value}) {op} {threshold}"

        return f"{rule.name}: rule triggered"
