"""Availability and nines calculator."""

from __future__ import annotations

import math
from typing import List

from sre_beacon.models import AvailabilityInput, AvailabilityResult


# Standard nines thresholds and their allowed downtime per 30 days
NINES_TABLE = {
    2:   {"label": "Two nines (99%)",       "monthly_downtime_min": 432.0},
    2.5: {"label": "Two-and-a-half nines",  "monthly_downtime_min": 136.6},
    3:   {"label": "Three nines (99.9%)",   "monthly_downtime_min": 43.2},
    3.5: {"label": "Three-and-a-half nines","monthly_downtime_min": 13.7},
    4:   {"label": "Four nines (99.99%)",   "monthly_downtime_min": 4.32},
    4.5: {"label": "Four-and-a-half nines", "monthly_downtime_min": 1.37},
    5:   {"label": "Five nines (99.999%)",  "monthly_downtime_min": 0.432},
}


def calculate_availability(inp: AvailabilityInput) -> AvailabilityResult:
    """Calculate availability percentage and nines from uptime data."""
    uptime = inp.total_minutes - inp.downtime_minutes
    if inp.total_minutes <= 0:
        availability_pct = 0.0
        nines = 0.0
    else:
        availability_pct = (uptime / inp.total_minutes) * 100.0
        if availability_pct >= 100.0:
            nines = float("inf")
        elif availability_pct <= 0.0:
            nines = 0.0
        else:
            nines = -math.log10(1 - availability_pct / 100.0)

    # Build allowed downtime comparison table (scaled to input period)
    scale = inp.total_minutes / (30 * 24 * 60) if inp.total_minutes > 0 else 1.0
    allowed = {}
    for n, info in NINES_TABLE.items():
        allowed[n] = {
            "label": info["label"],
            "allowed_downtime_min": round(info["monthly_downtime_min"] * scale, 2),
            "meets": inp.downtime_minutes <= info["monthly_downtime_min"] * scale,
        }

    return AvailabilityResult(
        service=inp.service,
        availability_pct=round(availability_pct, 6),
        nines=round(nines, 2) if not math.isinf(nines) else float("inf"),
        uptime_minutes=round(uptime, 2),
        downtime_minutes=round(inp.downtime_minutes, 2),
        total_minutes=round(inp.total_minutes, 2),
        period=inp.period,
        allowed_downtime_by_nines=allowed,
    )


def calculate_composite_availability(components: List[AvailabilityResult]) -> float:
    """Calculate composite serial availability from multiple components.

    For serial dependencies: A_total = A1 × A2 × ... × An.
    """
    if not components:
        return 0.0
    composite = 1.0
    for c in components:
        composite *= c.availability_pct / 100.0
    return round(composite * 100.0, 6)


def downtime_for_nines(nines_count: float, period_days: int = 30) -> float:
    """Return allowed downtime in minutes for a given number of nines."""
    if nines_count <= 0:
        return period_days * 24 * 60
    if math.isinf(nines_count):
        return 0.0
    total_minutes = period_days * 24 * 60
    return total_minutes * (10 ** (-nines_count))
