# stacksage/costs.py
from datetime import UTC, datetime, timedelta
from typing import Any, Dict, List, Optional

import boto3


def _fetch_cost_breakdown(
    *,
    session: Optional[Any],
    start: datetime.date,
    end: datetime.date,
    granularity: str,
    filter_by_regions: Optional[List[str]] = None,
) -> Dict[str, Any]:
    ce = session.client("ce") if session is not None else boto3.client("ce")

    params: Dict[str, Any] = {
        "TimePeriod": {
            "Start": start.strftime("%Y-%m-%d"),
            "End": end.strftime("%Y-%m-%d"),
        },
        "Granularity": granularity,
        "Metrics": ["UnblendedCost"],
        "GroupBy": [
            {"Type": "DIMENSION", "Key": "SERVICE"},
            {"Type": "DIMENSION", "Key": "REGION"},
        ],
    }

    if filter_by_regions:
        region_values = [r for r in filter_by_regions if r]
        if region_values:
            params["Filter"] = {
                "Dimensions": {"Key": "REGION", "Values": region_values}
            }

    resp = ce.get_cost_and_usage(**params)

    total = 0.0
    breakdown: Dict[str, Dict[str, float]] = {}
    for result in resp.get("ResultsByTime", []):
        groups = result.get("Groups", [])
        for g in groups:
            keys = g.get("Keys", [])
            if len(keys) >= 2:
                service, region = keys[0], keys[1]
            elif len(keys) == 1:
                service, region = keys[0], "Unknown"
            else:
                continue
            amount = float(
                g.get("Metrics", {}).get("UnblendedCost", {}).get("Amount", "0") or 0
            )
            total += amount
            breakdown.setdefault(service, {})
            breakdown[service][region] = breakdown[service].get(region, 0.0) + amount

    return {
        "total_unblended_cost": round(total, 2),
        "breakdown_by_service_region": breakdown,
    }


def get_cost_summary(
    session: Optional[Any] = None,
    last_n_days: int = 30,
    granularity: str = "MONTHLY",
    filter_by_regions: List[str] = None,
) -> Dict[str, Any]:
    """
    Retrieve historical AWS costs using Cost Explorer for the last N days.
    Returns a summary with total unblended cost and breakdowns by service and region.
    Requires ce:GetCostAndUsage permission.
    """
    end = datetime.now(UTC).date()
    start = end - timedelta(days=last_n_days)

    data = _fetch_cost_breakdown(
        session=session,
        start=start,
        end=end,
        granularity=granularity,
        filter_by_regions=filter_by_regions,
    )

    return {
        "time_window": {
            "start": start.strftime("%Y-%m-%d"),
            "end": end.strftime("%Y-%m-%d"),
        },
        "granularity": granularity,
        "total_unblended_cost": data.get("total_unblended_cost", 0.0),
        "breakdown_by_service_region": data.get("breakdown_by_service_region", {}),
    }


def get_spend_movers(
    session: Optional[Any] = None,
    lookback_days: int = 7,
    compare_days: int = 7,
    granularity: str = "DAILY",
    filter_by_regions: List[str] = None,
    max_items: int = 6,
    min_delta_usd: float = 5.0,
) -> Dict[str, Any]:
    """
    Compare current and previous spend windows to identify top movers.
    Returns deltas by service and region using aggregate Cost Explorer totals.
    Requires ce:GetCostAndUsage permission.
    """
    end = datetime.now(UTC).date()
    current_start = end - timedelta(days=lookback_days)
    previous_end = current_start
    previous_start = previous_end - timedelta(days=compare_days)

    current = _fetch_cost_breakdown(
        session=session,
        start=current_start,
        end=end,
        granularity=granularity,
        filter_by_regions=filter_by_regions,
    )
    previous = _fetch_cost_breakdown(
        session=session,
        start=previous_start,
        end=previous_end,
        granularity=granularity,
        filter_by_regions=filter_by_regions,
    )

    def aggregate_by_service(
        breakdown: Dict[str, Dict[str, float]],
    ) -> Dict[str, float]:
        return {
            service: round(sum(regions.values()), 2)
            for service, regions in breakdown.items()
        }

    def aggregate_by_region(breakdown: Dict[str, Dict[str, float]]) -> Dict[str, float]:
        totals: Dict[str, float] = {}
        for regions in breakdown.values():
            for region, amount in regions.items():
                totals[region] = totals.get(region, 0.0) + float(amount or 0)
        return {region: round(total, 2) for region, total in totals.items()}

    def build_movers(current_map: Dict[str, float], previous_map: Dict[str, float]):
        movers = []
        keys = set(current_map.keys()) | set(previous_map.keys())
        for key in keys:
            current_cost = float(current_map.get(key, 0.0) or 0)
            previous_cost = float(previous_map.get(key, 0.0) or 0)
            delta = round(current_cost - previous_cost, 2)
            if abs(delta) < min_delta_usd:
                continue
            pct_change = None
            new_spend = False
            stopped_spend = False
            if previous_cost > 0:
                pct_change = round((delta / previous_cost) * 100, 2)
            elif current_cost > 0:
                new_spend = True
            elif previous_cost > 0:
                stopped_spend = True
            movers.append(
                {
                    "key": key,
                    "current_cost": round(current_cost, 2),
                    "previous_cost": round(previous_cost, 2),
                    "delta": delta,
                    "pct_change": pct_change,
                    "new_spend": new_spend,
                    "stopped_spend": stopped_spend,
                }
            )
        return movers

    current_by_service = aggregate_by_service(
        current.get("breakdown_by_service_region", {})
    )
    previous_by_service = aggregate_by_service(
        previous.get("breakdown_by_service_region", {})
    )
    current_by_region = aggregate_by_region(
        current.get("breakdown_by_service_region", {})
    )
    previous_by_region = aggregate_by_region(
        previous.get("breakdown_by_service_region", {})
    )

    movers_by_service = build_movers(current_by_service, previous_by_service)
    movers_by_region = build_movers(current_by_region, previous_by_region)

    top_increases_by_service = sorted(
        movers_by_service, key=lambda m: m["delta"], reverse=True
    )[:max_items]
    top_decreases_by_service = sorted(movers_by_service, key=lambda m: m["delta"])[
        :max_items
    ]
    top_increases_by_region = sorted(
        movers_by_region, key=lambda m: m["delta"], reverse=True
    )[:max_items]
    top_decreases_by_region = sorted(movers_by_region, key=lambda m: m["delta"])[
        :max_items
    ]

    total_current = round(sum(current_by_service.values()), 2)
    total_previous = round(sum(previous_by_service.values()), 2)
    total_delta = round(total_current - total_previous, 2)
    total_pct_change = (
        round((total_delta / total_previous) * 100, 2) if total_previous > 0 else None
    )

    return {
        "time_window": {
            "current": {
                "start": current_start.strftime("%Y-%m-%d"),
                "end": end.strftime("%Y-%m-%d"),
            },
            "previous": {
                "start": previous_start.strftime("%Y-%m-%d"),
                "end": previous_end.strftime("%Y-%m-%d"),
            },
        },
        "granularity": granularity,
        "lookback_days": int(lookback_days),
        "compare_days": int(compare_days),
        "min_delta_usd": round(float(min_delta_usd), 2),
        "total_current": total_current,
        "total_previous": total_previous,
        "total_delta": total_delta,
        "total_pct_change": total_pct_change,
        "top_increases_by_service": top_increases_by_service,
        "top_decreases_by_service": top_decreases_by_service,
        "top_increases_by_region": top_increases_by_region,
        "top_decreases_by_region": top_decreases_by_region,
    }
