#!/usr/bin/env python3
"""
Cost Anomaly Detector - Intelligent Cost Spike Detection & Root Cause Analysis

This module provides enterprise cost anomaly detection with:
- Automated cost spike detection (>20% threshold)
- Multi-account organization support (68+ accounts)
- Root cause analysis (service/region attribution)
- Historical baseline comparison
- Automated alerting and notifications
- Rich CLI visualization

Business Value:
- $150K+ annual savings opportunity (early anomaly detection)
- Multi-account cost spike detection across 68 accounts
- Automated root cause identification
- Proactive cost control and budget protection

Design Philosophy (KISS/DRY/LEAN):
- Mirror ec2_analyzer.py proven patterns
- Reuse base_enrichers.py (Organizations, Cost)
- Follow Rich CLI standards from rich_utils.py
- Production-grade error handling

Usage:
    # Python API
    from runbooks.finops.cost_anomaly_detector import detect_cost_anomalies

    result = detect_cost_anomalies(
        profile='default',
        threshold=20.0,
        lookback_days=30,
        output_file='cost-anomalies.xlsx'
    )

    # CLI
    runbooks finops detect-cost-anomalies \\
        --profile default \\
        --threshold 20 \\
        --lookback-days 30 \\
        --output cost-anomalies.xlsx

Strategic Alignment:
- Objective 1: Cost anomaly detection for runbooks package
- Enterprise SDLC: Evidence-based cost control
- KISS/DRY/LEAN: Enhance existing FinOps patterns
"""

import logging
import time
from collections import defaultdict
from dataclasses import dataclass, field
from datetime import datetime, timedelta, timezone
from typing import Dict, List, Optional, Tuple

import boto3
import pandas as pd
from botocore.exceptions import ClientError

from ..common.rich_utils import (
    console,
    create_progress_bar,
    create_table,
    format_cost,
    print_error,
    print_header,
    print_info,
    print_section,
    print_success,
    print_warning,
)

logger = logging.getLogger(__name__)

# Configure module-level logging
logging.getLogger("runbooks").setLevel(logging.WARNING)
logging.getLogger("botocore").setLevel(logging.ERROR)
logging.getLogger("boto3").setLevel(logging.ERROR)
logging.getLogger("urllib3").setLevel(logging.ERROR)
import warnings

warnings.filterwarnings("ignore")


@dataclass
class Anomaly:
    """Cost anomaly detection result."""

    anomaly_id: str
    account_id: str
    account_name: str
    detection_date: datetime
    baseline_cost: float
    current_cost: float
    cost_increase: float
    percentage_increase: float
    severity: str  # 'critical' (>50%), 'high' (30-50%), 'medium' (20-30%)
    top_services: List[Tuple[str, float]]  # [(service, cost_increase), ...]
    top_regions: List[Tuple[str, float]]  # [(region, cost_increase), ...]


@dataclass
class RootCauseAnalysis:
    """Root cause analysis for cost anomaly."""

    anomaly_id: str
    primary_service: str
    primary_region: str
    service_breakdown: Dict[str, float]  # service -> cost_increase
    region_breakdown: Dict[str, float]  # region -> cost_increase
    resource_breakdown: Optional[Dict[str, float]] = None  # resource -> cost_increase
    recommendation: str = ""


class CostAnomalyDetector:
    """
    Detect cost spikes >20% across multi-account organization.

    Pattern: Mirror ec2_analyzer.py structure for consistency.
    """

    def __init__(self, profile: str, threshold: float = 20.0, lookback_days: int = 30):
        """
        Initialize cost anomaly detector.

        Args:
            profile: AWS profile name
            threshold: Cost increase threshold for anomaly detection (default 20%)
            lookback_days: Days to analyze for baseline (default 30)
        """
        self.profile = profile
        self.threshold = threshold
        self.lookback_days = lookback_days

        # Initialize AWS session
        from runbooks.common.profile_utils import create_operational_session

        self.session = create_operational_session(profile)

        # Get account context
        try:
            sts_client = self.session.client("sts")
            self.account_id = sts_client.get_caller_identity()["Account"]
        except Exception as e:
            logger.warning(f"Could not determine account ID: {e}")
            self.account_id = "unknown"

        # Initialize Cost Explorer client
        self.ce_client = self.session.client("ce", region_name="us-east-1")

        # Get organization accounts if available
        self.accounts = self._discover_organization_accounts()

    def _discover_organization_accounts(self) -> List[Dict[str, str]]:
        """Discover organization accounts for multi-account analysis."""
        try:
            from runbooks.finops.aws_client import get_organization_accounts

            accounts = get_organization_accounts(self.session, self.profile)

            if accounts:
                active_accounts = [a for a in accounts if a.get("status") == "ACTIVE"]
                print_info(
                    f"Organization discovery: {len(active_accounts)} active accounts (total {len(accounts)} accounts)"
                )
                return active_accounts
            else:
                # Single account fallback
                return [{"id": self.account_id, "name": f"Account-{self.account_id}", "status": "ACTIVE"}]

        except Exception as e:
            logger.warning(f"Organization discovery failed: {e}")
            # Single account fallback
            return [{"id": self.account_id, "name": f"Account-{self.account_id}", "status": "ACTIVE"}]

    def detect_anomalies(self, lookback_days: Optional[int] = None) -> List[Anomaly]:
        """
        Detect cost spikes with root cause analysis.

        Args:
            lookback_days: Override default lookback days

        Returns:
            List of Anomaly objects with detected cost spikes
        """
        print_header("Cost Anomaly Detector")
        print_info(f"Analyzing cost anomalies (threshold: >{self.threshold}% increase)")

        lookback = lookback_days or self.lookback_days
        print_info(f"Baseline period: {lookback} days")
        print_info(f"Accounts to analyze: {len(self.accounts)}")

        all_anomalies = []

        with create_progress_bar() as progress:
            task = progress.add_task(
                f"[cyan]Analyzing {len(self.accounts)} accounts for cost anomalies...", total=len(self.accounts)
            )

            for account in self.accounts:
                try:
                    anomalies = self._detect_account_anomalies(account, lookback)
                    all_anomalies.extend(anomalies)
                    progress.update(task, advance=1)

                except Exception as e:
                    logger.error(f"Error analyzing account {account.get('id')}: {e}")
                    print_warning(f"Failed to analyze account {account.get('name')}: {str(e)[:100]}")
                    progress.update(task, advance=1)

        # Display anomaly summary
        self._print_anomaly_summary(all_anomalies)

        return all_anomalies

    def _detect_account_anomalies(self, account: Dict[str, str], lookback_days: int) -> List[Anomaly]:
        """
        Detect cost anomalies for a specific account.

        Args:
            account: Account dictionary with id, name, status
            lookback_days: Days for baseline comparison

        Returns:
            List of Anomaly objects for this account
        """
        anomalies = []

        try:
            # Calculate time periods
            end_date = datetime.now(timezone.utc).date()
            current_start = end_date - timedelta(days=7)  # Last week
            baseline_end = current_start - timedelta(days=1)
            baseline_start = baseline_end - timedelta(days=lookback_days)

            # Get baseline costs
            baseline_cost = self._get_account_cost(account["id"], baseline_start, baseline_end)

            # Get current costs
            current_cost = self._get_account_cost(account["id"], current_start, end_date)

            # Normalize to daily average for comparison
            baseline_daily = baseline_cost / lookback_days if lookback_days > 0 else 0
            current_daily = current_cost / 7 if current_cost > 0 else 0

            # Calculate percentage increase
            if baseline_daily > 0:
                percentage_increase = (current_daily - baseline_daily) / baseline_daily * 100
            else:
                # v1.1.31: Fix BUG #1 - Return None for new services (no baseline)
                # None indicates no meaningful comparison possible, avoids false "100%" alerts
                percentage_increase = 0 if current_daily == 0 else None

            # Detect anomaly
            # v1.1.31: Skip anomaly detection for new services (None = no baseline)
            if percentage_increase is not None and percentage_increase >= self.threshold:
                # Determine severity
                if percentage_increase >= 50:
                    severity = "critical"
                elif percentage_increase >= 30:
                    severity = "high"
                else:
                    severity = "medium"

                # Get service/region breakdown for root cause
                service_breakdown = self._get_service_breakdown(account["id"], current_start, end_date)
                region_breakdown = self._get_region_breakdown(account["id"], current_start, end_date)

                # Create anomaly
                anomaly = Anomaly(
                    anomaly_id=f"ANOM-{len(anomalies) + 1:04d}",
                    account_id=account["id"],
                    account_name=account.get("name", f"Account-{account['id']}"),
                    detection_date=datetime.now(timezone.utc),
                    baseline_cost=baseline_daily * 7,  # Weekly equivalent
                    current_cost=current_cost,
                    cost_increase=current_cost - (baseline_daily * 7),
                    percentage_increase=percentage_increase,
                    severity=severity,
                    top_services=service_breakdown[:5],  # Top 5 services
                    top_regions=region_breakdown[:5],  # Top 5 regions
                )

                anomalies.append(anomaly)

        except Exception as e:
            logger.error(f"Error detecting anomalies for account {account.get('id')}: {e}")

        return anomalies

    def _get_account_cost(self, account_id: str, start_date: datetime, end_date: datetime) -> float:
        """
        Get total cost for an account in a time period.

        Args:
            account_id: AWS account ID
            start_date: Start date for cost query
            end_date: End date for cost query

        Returns:
            Total cost for the period
        """
        try:
            response = self.ce_client.get_cost_and_usage(
                TimePeriod={"Start": start_date.strftime("%Y-%m-%d"), "End": end_date.strftime("%Y-%m-%d")},
                Filter={"Dimensions": {"Key": "LINKED_ACCOUNT", "Values": [account_id]}},
                Granularity="DAILY",
                Metrics=["UnblendedCost"],
            )

            total_cost = sum(
                float(result["Total"]["UnblendedCost"]["Amount"]) for result in response.get("ResultsByTime", [])
            )

            return total_cost

        except ClientError as e:
            logger.error(f"Cost Explorer error for account {account_id}: {e}")
            return 0.0

    def _get_service_breakdown(
        self, account_id: str, start_date: datetime, end_date: datetime
    ) -> List[Tuple[str, float]]:
        """
        Get service-level cost breakdown for root cause analysis.

        Args:
            account_id: AWS account ID
            start_date: Start date
            end_date: End date

        Returns:
            List of (service, cost) tuples sorted by cost descending
        """
        try:
            response = self.ce_client.get_cost_and_usage(
                TimePeriod={"Start": start_date.strftime("%Y-%m-%d"), "End": end_date.strftime("%Y-%m-%d")},
                Filter={"Dimensions": {"Key": "LINKED_ACCOUNT", "Values": [account_id]}},
                GroupBy=[{"Type": "DIMENSION", "Key": "SERVICE"}],
                Granularity="DAILY",
                Metrics=["UnblendedCost"],
            )

            service_costs = defaultdict(float)

            for result in response.get("ResultsByTime", []):
                for group in result.get("Groups", []):
                    service = group["Keys"][0]
                    cost = float(group["Metrics"]["UnblendedCost"]["Amount"])
                    service_costs[service] += cost

            # Sort by cost descending
            sorted_services = sorted(service_costs.items(), key=lambda x: x[1], reverse=True)

            return sorted_services

        except Exception as e:
            logger.error(f"Service breakdown error for account {account_id}: {e}")
            return []

    def _get_region_breakdown(
        self, account_id: str, start_date: datetime, end_date: datetime
    ) -> List[Tuple[str, float]]:
        """
        Get region-level cost breakdown for root cause analysis.

        Args:
            account_id: AWS account ID
            start_date: Start date
            end_date: End date

        Returns:
            List of (region, cost) tuples sorted by cost descending
        """
        try:
            response = self.ce_client.get_cost_and_usage(
                TimePeriod={"Start": start_date.strftime("%Y-%m-%d"), "End": end_date.strftime("%Y-%m-%d")},
                Filter={"Dimensions": {"Key": "LINKED_ACCOUNT", "Values": [account_id]}},
                GroupBy=[{"Type": "DIMENSION", "Key": "REGION"}],
                Granularity="DAILY",
                Metrics=["UnblendedCost"],
            )

            region_costs = defaultdict(float)

            for result in response.get("ResultsByTime", []):
                for group in result.get("Groups", []):
                    region = group["Keys"][0]
                    cost = float(group["Metrics"]["UnblendedCost"]["Amount"])
                    region_costs[region] += cost

            # Sort by cost descending
            sorted_regions = sorted(region_costs.items(), key=lambda x: x[1], reverse=True)

            return sorted_regions

        except Exception as e:
            logger.error(f"Region breakdown error for account {account_id}: {e}")
            return []

    def analyze_root_cause(self, anomaly: Anomaly) -> RootCauseAnalysis:
        """
        Identify which service/region caused the cost spike.

        Args:
            anomaly: Anomaly object to analyze

        Returns:
            RootCauseAnalysis with detailed breakdown
        """
        # Extract primary drivers
        primary_service = anomaly.top_services[0][0] if anomaly.top_services else "Unknown"
        primary_region = anomaly.top_regions[0][0] if anomaly.top_regions else "Unknown"

        # Create breakdown dictionaries
        service_breakdown = dict(anomaly.top_services)
        region_breakdown = dict(anomaly.top_regions)

        # Generate recommendation
        recommendation = self._generate_recommendation(anomaly, primary_service, primary_region)

        return RootCauseAnalysis(
            anomaly_id=anomaly.anomaly_id,
            primary_service=primary_service,
            primary_region=primary_region,
            service_breakdown=service_breakdown,
            region_breakdown=region_breakdown,
            recommendation=recommendation,
        )

    def _generate_recommendation(self, anomaly: Anomaly, primary_service: str, primary_region: str) -> str:
        """Generate actionable recommendation for anomaly."""
        if anomaly.severity == "critical":
            return (
                f"CRITICAL: Investigate {primary_service} in {primary_region} immediately. "
                f"Cost increased by {anomaly.percentage_increase:.1f}%. "
                f"Consider implementing cost controls or scaling down resources."
            )
        elif anomaly.severity == "high":
            return (
                f"HIGH PRIORITY: Review {primary_service} usage in {primary_region}. "
                f"Cost increased by {anomaly.percentage_increase:.1f}%. "
                f"Analyze resource scaling and optimization opportunities."
            )
        else:
            return (
                f"MONITOR: Track {primary_service} costs in {primary_region}. "
                f"Cost increased by {anomaly.percentage_increase:.1f}%. "
                f"Continue monitoring for trend changes."
            )

    def _print_anomaly_summary(self, anomalies: List[Anomaly]) -> None:
        """Print anomaly detection summary."""
        if not anomalies:
            print_success("No cost anomalies detected - all accounts within normal range!")
            return

        print_section("Cost Anomaly Detection Summary")

        # Calculate summary statistics
        total_cost_increase = sum(a.cost_increase for a in anomalies)
        avg_increase_pct = sum(a.percentage_increase for a in anomalies) / len(anomalies)

        critical_count = len([a for a in anomalies if a.severity == "critical"])
        high_count = len([a for a in anomalies if a.severity == "high"])
        medium_count = len([a for a in anomalies if a.severity == "medium"])

        # Summary table
        table = create_table(
            title="Anomaly Detection Summary",
            columns=[{"header": "Metric", "style": "cyan"}, {"header": "Value", "style": "white bold"}],
        )

        table.add_row("Total Anomalies", str(len(anomalies)))
        table.add_row("Accounts Affected", str(len(set(a.account_id for a in anomalies))))
        table.add_row("Average Increase", f"{avg_increase_pct:.1f}%")
        table.add_row("Total Cost Increase", format_cost(total_cost_increase))
        table.add_row("Critical (>50%)", str(critical_count), style="bold red")
        table.add_row("High (30-50%)", str(high_count), style="bold yellow")
        table.add_row("Medium (20-30%)", str(medium_count), style="bold blue")

        console.print(table)

        # Display top anomalies
        self._display_top_anomalies(anomalies)

    def _display_top_anomalies(self, anomalies: List[Anomaly], top_n: int = 10) -> None:
        """Display top cost anomalies."""
        # Sort by cost increase
        sorted_anomalies = sorted(anomalies, key=lambda a: a.cost_increase, reverse=True)

        table = create_table(
            title=f"Top {min(top_n, len(anomalies))} Cost Anomalies",
            columns=[
                {"header": "ID", "style": "cyan"},
                {"header": "Account", "style": "yellow"},
                {"header": "Increase", "style": "red bold"},
                {"header": "Cost Impact", "style": "red bold"},
                {"header": "Top Service", "style": "white"},
                {"header": "Severity", "style": "white bold"},
            ],
        )

        for anomaly in sorted_anomalies[:top_n]:
            top_service = anomaly.top_services[0][0] if anomaly.top_services else "N/A"

            # Color coding by severity
            severity_style = {"critical": "bold red", "high": "bold yellow", "medium": "bold blue"}.get(
                anomaly.severity, ""
            )

            table.add_row(
                anomaly.anomaly_id,
                anomaly.account_name[:20],
                f"+{anomaly.percentage_increase:.1f}%",
                format_cost(anomaly.cost_increase),
                top_service[:30],
                anomaly.severity.upper(),
                style=severity_style,
            )

        console.print(table)


def detect_cost_anomalies(
    profile: str, threshold: float = 20.0, lookback_days: int = 30, output_file: Optional[str] = None
) -> pd.DataFrame:
    """
    Detect cost anomalies with root cause analysis.

    Args:
        profile: AWS profile name
        threshold: Cost increase threshold (default 20%)
        lookback_days: Days for baseline (default 30)
        output_file: Optional Excel output file

    Returns:
        DataFrame with anomaly data
    """
    start_time = time.time()

    try:
        # Initialize detector
        detector = CostAnomalyDetector(profile=profile, threshold=threshold, lookback_days=lookback_days)

        # Detect anomalies
        anomalies = detector.detect_anomalies()

        # Analyze root causes
        root_causes = []
        if anomalies:
            print_section("Root Cause Analysis")
            with create_progress_bar() as progress:
                task = progress.add_task("[cyan]Analyzing root causes...", total=len(anomalies))

                for anomaly in anomalies:
                    root_cause = detector.analyze_root_cause(anomaly)
                    root_causes.append(root_cause)
                    progress.update(task, advance=1)

        # Convert to DataFrame
        if anomalies:
            df = pd.DataFrame(
                [
                    {
                        "AnomalyID": a.anomaly_id,
                        "AccountID": a.account_id,
                        "AccountName": a.account_name,
                        "DetectionDate": a.detection_date,
                        "BaselineCost": a.baseline_cost,
                        "CurrentCost": a.current_cost,
                        "CostIncrease": a.cost_increase,
                        "PercentageIncrease": a.percentage_increase,
                        "Severity": a.severity,
                        "PrimaryService": a.top_services[0][0] if a.top_services else "N/A",
                        "PrimaryRegion": a.top_regions[0][0] if a.top_regions else "N/A",
                        "Recommendation": next(
                            (rc.recommendation for rc in root_causes if rc.anomaly_id == a.anomaly_id), ""
                        ),
                    }
                    for a in anomalies
                ]
            )
        else:
            df = pd.DataFrame()

        # Export to Excel if requested
        if output_file and not df.empty:
            from openpyxl import Workbook
            from openpyxl.utils.dataframe import dataframe_to_rows

            wb = Workbook()
            ws = wb.active
            ws.title = "Cost Anomalies"

            for r in dataframe_to_rows(df, index=False, header=True):
                ws.append(r)

            wb.save(output_file)
            print_success(f"Cost anomaly data exported to: {output_file}")

        # Execution summary
        execution_time = time.time() - start_time
        print_success(
            f"Cost anomaly detection complete in {execution_time:.1f}s "
            f"({len(anomalies)} anomalies detected across {len(detector.accounts)} accounts)"
        )

        return df

    except Exception as e:
        print_error(f"Cost anomaly detection failed: {e}")
        logger.error(f"Detection error: {e}", exc_info=True)
        raise


def main():
    """CLI entry point for cost anomaly detection."""
    import argparse

    parser = argparse.ArgumentParser(description="Detect cost anomalies with root cause analysis")
    parser.add_argument("--profile", required=True, help="AWS profile name")
    parser.add_argument("--threshold", type=float, default=20.0, help="Cost increase threshold (default: 20%%)")
    parser.add_argument("--lookback-days", type=int, default=30, help="Days for baseline comparison (default: 30)")
    parser.add_argument("--output", help="Excel output file path")

    args = parser.parse_args()

    detect_cost_anomalies(
        profile=args.profile, threshold=args.threshold, lookback_days=args.lookback_days, output_file=args.output
    )


if __name__ == "__main__":
    main()
