"""
Tiered pricing helper for Climeter.

Supports volume-based pricing tiers — as call volume increases,
the per-call price decreases.

Usage:
    from climeter.pricing import TieredPricing

    tiers = TieredPricing([
        (0, 1000, 0.01),       # 0-1000 calls: $0.01
        (1000, 10000, 0.008),  # 1001-10000: $0.008
        (10000, None, 0.005),  # 10001+: $0.005
    ])

    price = tiers.price_for_call(call_number=1500)  # -> 0.008
"""

from typing import List, Optional, Tuple


class TieredPricing:
    """
    Volume-based tiered pricing calculator.

    Args:
        tiers: List of (min_calls, max_calls, price_per_call) tuples.
               - min_calls: inclusive lower bound (0, 1000, 10000, ...)
               - max_calls: exclusive upper bound (1000, 10000, None, ...)
               - price_per_call: USD price per call in this tier
    """

    def __init__(self, tiers: List[Tuple[int, Optional[int], float]]):
        """
        Initialize with pricing tiers.

        Args:
            tiers: List of (min, max, price) where:
                   - min: inclusive lower bound
                   - max: exclusive upper bound (None for unlimited)
                   - price: price per call in USD

        Example:
            TieredPricing([
                (0, 1000, 0.01),
                (1000, 10000, 0.008),
                (10000, None, 0.005),
            ])
        """
        if not tiers:
            raise ValueError("At least one pricing tier is required")

        # Sort by min_calls to ensure correct ordering
        self._tiers = sorted(tiers, key=lambda t: t[0])

        # Validate tiers don't overlap
        for i in range(len(self._tiers) - 1):
            current_min, current_max, _ = self._tiers[i]
            next_min, _, _ = self._tiers[i + 1]
            if current_max is not None and current_max != next_min:
                raise ValueError(
                    f"Tiers overlap or have gaps: {current_max} != {next_min}"
                )

    def price_for_call(self, call_number: int) -> float:
        """
        Get the price for a specific call number.

        Args:
            call_number: The 1-based index of the call (e.g., 1st call, 1500th call)

        Returns:
            The price per call in USD for the tier this call falls into.

        Example:
            tiers = TieredPricing([(0, 1000, 0.01), (1000, None, 0.008)])
            tiers.price_for_call(1)      # -> 0.01
            tiers.price_for_call(500)    # -> 0.01
            tiers.price_for_call(1000)   # -> 0.01
            tiers.price_for_call(1001)   # -> 0.008
            tiers.price_for_call(50000)   # -> 0.008
        """
        if call_number < 1:
            raise ValueError("call_number must be >= 1")

        for min_calls, max_calls, price in self._tiers:
            if max_calls is None:
                # Unlimited tier — applies to this call and all higher
                return price
            if min_calls < call_number <= max_calls:
                return price

        # Should not reach here if tiers are configured correctly
        # But handle edge case: return highest tier price
        _, _, fallback_price = self._tiers[-1]
        return fallback_price

    def price_for_volume(self, total_calls: int) -> float:
        """
        Calculate total price for a given volume of calls.

        Args:
            total_calls: Total number of calls

        Returns:
            Total price in USD for all calls

        Example:
            tiers = TieredPricing([(0, 1000, 0.01), (1000, None, 0.008)])
            tiers.price_for_volume(500)   # -> 5.00
            tiers.price_for_volume(1500)  # -> 10.00 + 4.00 = 14.00
        """
        if total_calls < 0:
            raise ValueError("total_calls must be >= 0")
        if total_calls == 0:
            return 0.0

        total_price = 0.0
        remaining = total_calls

        for min_calls, max_calls, price in self._tiers:
            if remaining <= 0:
                break

            tier_start = min_calls + 1  # 1-based
            tier_end = max_calls  # exclusive

            # Calculate how many calls fall in this tier
            if max_calls is None:
                # Unlimited tier
                calls_in_tier = remaining
            else:
                tier_size = max_calls - min_calls
                calls_in_previous_tiers = max(0, min_calls)
                calls_remaining = total_calls - calls_in_previous_tiers
                calls_in_tier = min(calls_remaining, tier_size)

            calls_in_tier = min(calls_in_tier, remaining)
            total_price += calls_in_tier * price
            remaining -= calls_in_tier

        return total_price

    @property
    def tiers(self) -> List[Tuple[int, Optional[int], float]]:
        """Return the pricing tiers (read-only)."""
        return list(self._tiers)


# Pre-configured tier presets
class PricingPresets:
    """Common pricing tier configurations."""

    STANDARD = TieredPricing([
        (0, 1000, 0.01),
        (1000, 10000, 0.008),
        (10000, None, 0.005),
    ])

    ENTERPRISE = TieredPricing([
        (0, 10000, 0.008),
        (10000, 100000, 0.005),
        (100000, None, 0.003),
    ])

    STARTER = TieredPricing([
        (0, 500, 0.015),
        (500, 5000, 0.01),
        (5000, None, 0.008),
    ])
