"""Tests for agent.credits_tracker — CreditsState + parse_credits_headers.

Covers the 9-state matrix plus validation edge cases.  All header values
arrive as STRINGS (the producer calls String(...) on every field).
"""

from __future__ import annotations

import logging
import time
from typing import Optional
import pytest

from agent.credits_tracker import CreditsState, parse_credits_headers


# ── Helpers ─────────────────────────────────────────────────────────────────


def micros(dollars: float) -> str:
    """Convert a dollar amount to a micros string for header fixtures."""
    return str(round(dollars * 1_000_000))


# ── 9-State matrix fixtures ──────────────────────────────────────────────────


def _base_headers(**overrides) -> dict:
    """Base headers present in every valid response."""
    h = {
        "x-nous-credits-version": "1",
        "x-nous-credits-remaining-micros": micros(0),
        "x-nous-credits-remaining-usd": "0.00",
        "x-nous-credits-subscription-micros": micros(0),
        "x-nous-credits-subscription-usd": "0.00",
        "x-nous-credits-rollover-micros": micros(0),
        "x-nous-credits-purchased-micros": micros(0),
        "x-nous-credits-purchased-usd": "0.00",
        "x-nous-tool-pool-micros": micros(0),
        "x-nous-tool-pool-gated-off": "false",
        "x-nous-credits-denominator-kind": "none",
        "x-nous-credits-paid-access": "true",
        "x-nous-credits-as-of-ms": "1717000000000",
    }
    h.update(overrides)
    return h


# ── 9 STATES ────────────────────────────────────────────────────────────────


HEALTHY_HEADERS = _base_headers(
    **{
        "x-nous-credits-remaining-micros": micros(30.34),
        "x-nous-credits-remaining-usd": "30.34",
        "x-nous-credits-subscription-micros": micros(18.00),
        "x-nous-credits-subscription-usd": "18.00",
        "x-nous-credits-subscription-limit-micros": micros(20.00),
        "x-nous-credits-subscription-limit-usd": "20.00",
        "x-nous-credits-rollover-micros": micros(0),
        "x-nous-credits-purchased-micros": micros(12.34),
        "x-nous-credits-purchased-usd": "12.34",
        "x-nous-tool-pool-micros": micros(2.00),
        "x-nous-tool-pool-gated-off": "true",
        "x-nous-credits-denominator-kind": "subscription_cap",
        "x-nous-credits-paid-access": "true",
    }
)

SUB_90PCT_HEADERS = _base_headers(
    **{
        "x-nous-credits-remaining-micros": micros(2.00),
        "x-nous-credits-remaining-usd": "2.00",
        "x-nous-credits-subscription-micros": micros(2.00),
        "x-nous-credits-subscription-usd": "2.00",
        "x-nous-credits-subscription-limit-micros": micros(20.00),
        "x-nous-credits-subscription-limit-usd": "20.00",
        "x-nous-credits-purchased-micros": micros(0),
        "x-nous-credits-purchased-usd": "0.00",
        "x-nous-credits-denominator-kind": "subscription_cap",
        "x-nous-credits-paid-access": "true",
    }
)

GRANT_EXHAUSTED_HEADERS = _base_headers(
    **{
        "x-nous-credits-remaining-micros": micros(12.34),
        "x-nous-credits-remaining-usd": "12.34",
        "x-nous-credits-subscription-micros": micros(0),
        "x-nous-credits-subscription-usd": "0.00",
        "x-nous-credits-subscription-limit-micros": micros(20.00),
        "x-nous-credits-subscription-limit-usd": "20.00",
        "x-nous-credits-purchased-micros": micros(12.34),
        "x-nous-credits-purchased-usd": "12.34",
        "x-nous-credits-denominator-kind": "subscription_cap",
        "x-nous-credits-paid-access": "true",
    }
)

PURCHASED_ONLY_HEADERS = _base_headers(
    **{
        "x-nous-credits-remaining-micros": micros(30.00),
        "x-nous-credits-remaining-usd": "30.00",
        "x-nous-credits-subscription-micros": micros(0),
        "x-nous-credits-subscription-usd": "0.00",
        "x-nous-credits-purchased-micros": micros(30.00),
        "x-nous-credits-purchased-usd": "30.00",
        "x-nous-credits-denominator-kind": "none",
        "x-nous-credits-paid-access": "true",
        # No limit pair — denominator_kind=none
    }
)

TOOL_POOL_FREE_HEADERS = _base_headers(
    **{
        "x-nous-credits-remaining-micros": micros(0.05),
        "x-nous-credits-remaining-usd": "0.05",
        "x-nous-tool-pool-micros": micros(0.05),
        "x-nous-tool-pool-gated-off": "false",
        "x-nous-credits-paid-access": "true",
    }
)

DEPLETED_HEADERS = _base_headers(
    **{
        "x-nous-credits-remaining-micros": micros(0),
        "x-nous-credits-remaining-usd": "0.00",
        "x-nous-credits-subscription-micros": micros(0),
        "x-nous-credits-subscription-usd": "0.00",
        "x-nous-credits-purchased-micros": micros(0),
        "x-nous-credits-purchased-usd": "0.00",
        "x-nous-credits-paid-access": "false",
        "x-nous-credits-disabled-reason": "out_of_credits",
    }
)

DEBT_HEADERS = _base_headers(
    **{
        "x-nous-credits-remaining-micros": micros(0),
        "x-nous-credits-remaining-usd": "0.00",
        "x-nous-credits-subscription-micros": str(-5_000_000),
        "x-nous-credits-subscription-usd": "-5.00",
        "x-nous-credits-purchased-micros": micros(0),
        "x-nous-credits-purchased-usd": "0.00",
        "x-nous-credits-paid-access": "false",
    }
)


# ── State 1: healthy ─────────────────────────────────────────────────────────


class TestHealthyState:
    def test_parses_successfully(self):
        state = parse_credits_headers(HEALTHY_HEADERS)
        assert state is not None

    def test_from_header_set(self):
        state = parse_credits_headers(HEALTHY_HEADERS)
        assert state.from_header is True

    def test_captured_at_set(self):
        before = time.time()
        state = parse_credits_headers(HEALTHY_HEADERS)
        after = time.time()
        assert before <= state.captured_at <= after

    def test_remaining_fields(self):
        state = parse_credits_headers(HEALTHY_HEADERS)
        assert state.remaining_micros == round(30.34 * 1_000_000)
        assert state.remaining_usd == "30.34"

    def test_subscription_fields(self):
        state = parse_credits_headers(HEALTHY_HEADERS)
        assert state.subscription_micros == round(18.00 * 1_000_000)
        assert state.subscription_usd == "18.00"
        assert state.subscription_limit_micros == round(20.00 * 1_000_000)
        assert state.subscription_limit_usd == "20.00"

    def test_rollover_and_purchased(self):
        state = parse_credits_headers(HEALTHY_HEADERS)
        assert state.rollover_micros == 0
        assert state.purchased_micros == round(12.34 * 1_000_000)
        assert state.purchased_usd == "12.34"

    def test_tool_pool(self):
        state = parse_credits_headers(HEALTHY_HEADERS)
        assert state.tool_pool_micros == round(2.00 * 1_000_000)
        assert state.tool_pool_gated_off is True

    def test_denominator_and_access(self):
        state = parse_credits_headers(HEALTHY_HEADERS)
        assert state.denominator_kind == "subscription_cap"
        assert state.paid_access is True
        assert state.disabled_reason is None

    def test_used_fraction(self):
        state = parse_credits_headers(HEALTHY_HEADERS)
        # (20.00 - 18.00) / 20.00 = 0.10
        assert state.used_fraction == pytest.approx(0.10)

    def test_has_data(self):
        state = parse_credits_headers(HEALTHY_HEADERS)
        assert state.has_data is True

    def test_not_depleted(self):
        state = parse_credits_headers(HEALTHY_HEADERS)
        assert state.depleted is False

    def test_age_seconds_reasonable(self):
        state = parse_credits_headers(HEALTHY_HEADERS)
        # Should be very small — just parsed
        assert 0 <= state.age_seconds < 5


# ── State 2: sub_90pct ───────────────────────────────────────────────────────


class TestSub90Pct:
    def test_parses_successfully(self):
        state = parse_credits_headers(SUB_90PCT_HEADERS)
        assert state is not None

    def test_used_fraction_90pct(self):
        state = parse_credits_headers(SUB_90PCT_HEADERS)
        # (20.00 - 2.00) / 20.00 = 0.90
        assert state.used_fraction == pytest.approx(0.90)

    def test_paid_access(self):
        state = parse_credits_headers(SUB_90PCT_HEADERS)
        assert state.paid_access is True
        assert state.depleted is False


# ── State 3: grant_exhausted ─────────────────────────────────────────────────


class TestGrantExhausted:
    def test_used_fraction_100pct(self):
        state = parse_credits_headers(GRANT_EXHAUSTED_HEADERS)
        assert state is not None
        # subscription_micros=0, limit=20.00 → (20-0)/20 = 1.0
        assert state.used_fraction == pytest.approx(1.0)

    def test_paid_access_still_true(self):
        state = parse_credits_headers(GRANT_EXHAUSTED_HEADERS)
        assert state.paid_access is True
        assert state.depleted is False


# ── State 4: purchased_only ──────────────────────────────────────────────────


class TestPurchasedOnly:
    def test_parses_successfully(self):
        state = parse_credits_headers(PURCHASED_ONLY_HEADERS)
        assert state is not None

    def test_denominator_kind_none(self):
        state = parse_credits_headers(PURCHASED_ONLY_HEADERS)
        assert state.denominator_kind == "none"

    def test_used_fraction_is_none_no_limit(self):
        state = parse_credits_headers(PURCHASED_ONLY_HEADERS)
        # No subscription_limit_micros → used_fraction is None
        assert state.used_fraction is None

    def test_no_limit_pair(self):
        state = parse_credits_headers(PURCHASED_ONLY_HEADERS)
        assert state.subscription_limit_micros is None
        assert state.subscription_limit_usd is None


# ── State 5: tool_pool_free ──────────────────────────────────────────────────


class TestToolPoolFree:
    def test_parses_successfully(self):
        state = parse_credits_headers(TOOL_POOL_FREE_HEADERS)
        assert state is not None

    def test_tool_pool_gated_off_false(self):
        state = parse_credits_headers(TOOL_POOL_FREE_HEADERS)
        assert state.tool_pool_gated_off is False

    def test_tool_pool_micros(self):
        state = parse_credits_headers(TOOL_POOL_FREE_HEADERS)
        assert state.tool_pool_micros == round(0.05 * 1_000_000)

    def test_paid_access(self):
        state = parse_credits_headers(TOOL_POOL_FREE_HEADERS)
        assert state.paid_access is True


# ── State 6: depleted ────────────────────────────────────────────────────────


class TestDepleted:
    def test_parses_successfully(self):
        state = parse_credits_headers(DEPLETED_HEADERS)
        assert state is not None

    def test_paid_access_false(self):
        state = parse_credits_headers(DEPLETED_HEADERS)
        assert state.paid_access is False

    def test_depleted_true(self):
        state = parse_credits_headers(DEPLETED_HEADERS)
        assert state.depleted is True

    def test_disabled_reason(self):
        state = parse_credits_headers(DEPLETED_HEADERS)
        assert state.disabled_reason == "out_of_credits"

    def test_remaining_zero(self):
        state = parse_credits_headers(DEPLETED_HEADERS)
        assert state.remaining_micros == 0


# ── State 7: debt ────────────────────────────────────────────────────────────


class TestDebt:
    def test_parses_successfully(self):
        # Negative subscription_micros should NOT cause the parse to fail
        state = parse_credits_headers(DEBT_HEADERS)
        assert state is not None

    def test_negative_subscription_accepted(self):
        state = parse_credits_headers(DEBT_HEADERS)
        assert state.subscription_micros == -5_000_000

    def test_negative_subscription_usd_accepted(self):
        state = parse_credits_headers(DEBT_HEADERS)
        assert state.subscription_usd == "-5.00"

    def test_paid_access_false(self):
        state = parse_credits_headers(DEBT_HEADERS)
        assert state.paid_access is False
        assert state.depleted is True


# ── State 8: missing ─────────────────────────────────────────────────────────


class TestMissing:
    def test_no_credits_headers_returns_none(self):
        state = parse_credits_headers({})
        assert state is None

    def test_completely_empty_dict(self):
        assert parse_credits_headers({}) is None


# ── State 9: no_org ──────────────────────────────────────────────────────────


class TestNoOrg:
    def test_irrelevant_headers_return_none(self):
        headers = {
            "content-type": "application/json",
            "x-request-id": "abc123",
            "server": "nginx",
        }
        state = parse_credits_headers(headers)
        assert state is None

    def test_api_key_path_no_org_returns_none(self):
        # Headers that might appear on an api-key path with no org
        headers = {
            "content-type": "application/json",
            "authorization": "Bearer sk-test",
        }
        assert parse_credits_headers(headers) is None


# ── Version validation ───────────────────────────────────────────────────────


class TestVersionValidation:
    def test_version_string_1_parses(self):
        headers = _base_headers(**{"x-nous-credits-version": "1"})
        state = parse_credits_headers(headers)
        assert state is not None
        assert state.version == 1

    def test_version_2_returns_none(self):
        headers = _base_headers(**{"x-nous-credits-version": "2"})
        state = parse_credits_headers(headers)
        assert state is None

    def test_version_absent_returns_none(self):
        headers = {k: v for k, v in _base_headers().items() if k != "x-nous-credits-version"}
        state = parse_credits_headers(headers)
        assert state is None

    def test_version_greater_than_1_warns_once(self, caplog):
        """Version > 1 must log a warning, and ONLY ONCE across multiple calls."""
        import agent.credits_tracker as ct

        original = ct._version_warning_emitted
        try:
            # Reset the warn-once latch so this test starts clean regardless of order
            ct._version_warning_emitted = False

            headers = _base_headers(**{"x-nous-credits-version": "3"})
            with caplog.at_level(logging.WARNING, logger="agent.credits_tracker"):
                parse_credits_headers(headers)
                parse_credits_headers(headers)
                parse_credits_headers(headers)

            warning_records = [r for r in caplog.records if "unsupported" in r.message.lower() or "version" in r.message.lower()]
            assert len(warning_records) == 1, (
                f"Expected exactly 1 version warning, got {len(warning_records)}: {[r.message for r in warning_records]}"
            )
        finally:
            ct._version_warning_emitted = original

    def test_version_0_returns_none(self):
        headers = _base_headers(**{"x-nous-credits-version": "0"})
        assert parse_credits_headers(headers) is None

    def test_version_non_int_returns_none(self):
        headers = _base_headers(**{"x-nous-credits-version": "abc"})
        assert parse_credits_headers(headers) is None


# ── Bool-string trap ─────────────────────────────────────────────────────────


class TestBoolStringTrap:
    """Explicit tests for the bool("false") == True trap."""

    def test_paid_access_string_false_means_depleted(self):
        """paid_access='false' must yield paid_access=False — NOT True."""
        headers = _base_headers(**{"x-nous-credits-paid-access": "false"})
        state = parse_credits_headers(headers)
        assert state is not None
        assert state.paid_access is False
        assert state.depleted is True

    def test_paid_access_string_true_means_not_depleted(self):
        headers = _base_headers(**{"x-nous-credits-paid-access": "true"})
        state = parse_credits_headers(headers)
        assert state is not None
        assert state.paid_access is True
        assert state.depleted is False

    def test_paid_access_case_insensitive_FALSE(self):
        headers = _base_headers(**{"x-nous-credits-paid-access": "FALSE"})
        state = parse_credits_headers(headers)
        assert state is not None
        assert state.paid_access is False

    def test_paid_access_case_insensitive_True(self):
        headers = _base_headers(**{"x-nous-credits-paid-access": "True"})
        state = parse_credits_headers(headers)
        assert state is not None
        assert state.paid_access is True

    def test_tool_pool_gated_off_false(self):
        headers = _base_headers(**{"x-nous-tool-pool-gated-off": "false"})
        state = parse_credits_headers(headers)
        assert state is not None
        assert state.tool_pool_gated_off is False

    def test_tool_pool_gated_off_true(self):
        headers = _base_headers(**{"x-nous-tool-pool-gated-off": "true"})
        state = parse_credits_headers(headers)
        assert state is not None
        assert state.tool_pool_gated_off is True


# ── Tool-pool optional headers ────────────────────────────────────────────────


class TestToolPoolOptional:
    """x-nous-tool-pool-* headers are optional; absent → defaults; present-but-malformed → miss."""

    def _no_tool_pool_headers(self) -> dict:
        """Base headers with BOTH tool-pool headers removed."""
        h = _base_headers()
        h.pop("x-nous-tool-pool-micros", None)
        h.pop("x-nous-tool-pool-gated-off", None)
        return h

    def test_absent_tool_pool_headers_parse_succeeds(self):
        """Valid credits headers with no x-nous-tool-pool-* → parse succeeds."""
        state = parse_credits_headers(self._no_tool_pool_headers())
        assert state is not None

    def test_absent_tool_pool_micros_defaults_to_zero(self):
        state = parse_credits_headers(self._no_tool_pool_headers())
        assert state.tool_pool_micros == 0

    def test_absent_tool_pool_gated_off_defaults_to_false(self):
        state = parse_credits_headers(self._no_tool_pool_headers())
        assert state.tool_pool_gated_off is False

    def test_present_malformed_tool_pool_micros_returns_none(self):
        """x-nous-tool-pool-micros present but non-int → parse miss (returns None)."""
        headers = _base_headers(**{"x-nous-tool-pool-micros": "not-a-number"})
        assert parse_credits_headers(headers) is None

    def test_present_negative_tool_pool_micros_returns_none(self):
        """x-nous-tool-pool-micros present but negative → parse miss (returns None)."""
        headers = _base_headers(**{"x-nous-tool-pool-micros": "-1000"})
        assert parse_credits_headers(headers) is None

    def test_only_tool_pool_micros_absent_still_succeeds(self):
        """Only micros absent (gated-off still present) → tool_pool_micros = 0, parse succeeds."""
        h = _base_headers()
        h.pop("x-nous-tool-pool-micros", None)
        state = parse_credits_headers(h)
        assert state is not None
        assert state.tool_pool_micros == 0


# ── Half-pair subscription limit ─────────────────────────────────────────────


class TestHalfPairLimit:
    def test_only_limit_micros_present_both_absent(self):
        """Only -micros present → both None, parse SUCCEEDS."""
        headers = _base_headers(
            **{
                "x-nous-credits-subscription-limit-micros": micros(20.00),
                "x-nous-credits-denominator-kind": "subscription_cap",
            }
        )
        state = parse_credits_headers(headers)
        assert state is not None
        assert state.subscription_limit_micros is None
        assert state.subscription_limit_usd is None

    def test_only_limit_usd_present_both_absent(self):
        """Only -usd present → both None, parse SUCCEEDS."""
        headers = _base_headers(
            **{
                "x-nous-credits-subscription-limit-usd": "20.00",
                "x-nous-credits-denominator-kind": "subscription_cap",
            }
        )
        state = parse_credits_headers(headers)
        assert state is not None
        assert state.subscription_limit_micros is None
        assert state.subscription_limit_usd is None

    def test_half_pair_used_fraction_is_none(self):
        """With no limit pair, used_fraction is None regardless of denominator_kind."""
        headers = _base_headers(
            **{
                "x-nous-credits-subscription-limit-micros": micros(20.00),
                "x-nous-credits-denominator-kind": "subscription_cap",
            }
        )
        state = parse_credits_headers(headers)
        assert state is not None
        assert state.used_fraction is None

    def test_full_pair_present_parsed_correctly(self):
        """Both present → both populated, used_fraction computable."""
        headers = _base_headers(
            **{
                "x-nous-credits-subscription-micros": micros(10.00),
                "x-nous-credits-subscription-usd": "10.00",
                "x-nous-credits-subscription-limit-micros": micros(20.00),
                "x-nous-credits-subscription-limit-usd": "20.00",
                "x-nous-credits-denominator-kind": "subscription_cap",
            }
        )
        state = parse_credits_headers(headers)
        assert state is not None
        assert state.subscription_limit_micros == round(20.00 * 1_000_000)
        assert state.subscription_limit_usd == "20.00"
        assert state.used_fraction == pytest.approx(0.50)


# ── Negative value validation ─────────────────────────────────────────────────


class TestNegativeValues:
    def test_negative_remaining_micros_returns_none(self):
        headers = _base_headers(**{"x-nous-credits-remaining-micros": "-1000"})
        assert parse_credits_headers(headers) is None

    def test_negative_purchased_micros_returns_none(self):
        headers = _base_headers(**{"x-nous-credits-purchased-micros": "-500"})
        assert parse_credits_headers(headers) is None

    def test_negative_rollover_micros_returns_none(self):
        headers = _base_headers(**{"x-nous-credits-rollover-micros": "-100"})
        assert parse_credits_headers(headers) is None

    def test_negative_limit_micros_returns_none(self):
        headers = _base_headers(
            **{
                "x-nous-credits-subscription-limit-micros": "-1000",
                "x-nous-credits-subscription-limit-usd": "-0.00",
                "x-nous-credits-denominator-kind": "subscription_cap",
            }
        )
        assert parse_credits_headers(headers) is None

    def test_negative_subscription_accepted(self):
        """subscription_micros is the ONLY field allowed to be negative."""
        headers = _base_headers(**{"x-nous-credits-subscription-micros": "-5000000",
                                   "x-nous-credits-subscription-usd": "-5.00"})
        state = parse_credits_headers(headers)
        assert state is not None
        assert state.subscription_micros == -5_000_000


# ── USD format validation ─────────────────────────────────────────────────────


class TestUsdValidation:
    def test_valid_usd_format(self):
        headers = _base_headers(**{"x-nous-credits-remaining-usd": "18.00"})
        state = parse_credits_headers(headers)
        assert state is not None
        assert state.remaining_usd == "18.00"

    def test_usd_one_decimal_returns_none(self):
        """'18.0' does not match ^-?\d+\.\d{2}$"""
        headers = _base_headers(**{"x-nous-credits-remaining-usd": "18.0"})
        assert parse_credits_headers(headers) is None

    def test_usd_no_decimal_returns_none(self):
        headers = _base_headers(**{"x-nous-credits-remaining-usd": "18"})
        assert parse_credits_headers(headers) is None

    def test_usd_with_dollar_sign_returns_none(self):
        headers = _base_headers(**{"x-nous-credits-remaining-usd": "$18.00"})
        assert parse_credits_headers(headers) is None

    def test_usd_with_comma_returns_none(self):
        headers = _base_headers(**{"x-nous-credits-remaining-usd": "1,800.00"})
        assert parse_credits_headers(headers) is None

    def test_usd_negative_valid(self):
        """Negative USD string should parse (e.g. subscription debt)."""
        headers = _base_headers(
            **{
                "x-nous-credits-subscription-micros": "-5000000",
                "x-nous-credits-subscription-usd": "-5.00",
            }
        )
        state = parse_credits_headers(headers)
        assert state is not None
        assert state.subscription_usd == "-5.00"


# ── Non-int micros validation ─────────────────────────────────────────────────


class TestMicrosValidation:
    def test_non_int_micros_string_returns_none(self):
        headers = _base_headers(**{"x-nous-credits-remaining-micros": "abc"})
        assert parse_credits_headers(headers) is None

    def test_float_string_micros_returns_none(self):
        """'1.5' is not an integer string — should fail validation."""
        headers = _base_headers(**{"x-nous-credits-remaining-micros": "1.5"})
        assert parse_credits_headers(headers) is None

    def test_non_int_purchased_returns_none(self):
        headers = _base_headers(**{"x-nous-credits-purchased-micros": "abc"})
        assert parse_credits_headers(headers) is None


# ── as_of_ms validation ───────────────────────────────────────────────────────


class TestAsOfMs:
    def test_junk_as_of_ms_returns_none(self):
        headers = _base_headers(**{"x-nous-credits-as-of-ms": "not-a-timestamp"})
        assert parse_credits_headers(headers) is None

    def test_valid_as_of_ms(self):
        headers = _base_headers(**{"x-nous-credits-as-of-ms": "1717000000000"})
        state = parse_credits_headers(headers)
        assert state is not None
        assert state.as_of_ms == 1717000000000


# ── denominator_kind validation ────────────────────────────────────────────────


class TestDenominatorKind:
    def test_subscription_cap_valid(self):
        headers = _base_headers(
            **{
                "x-nous-credits-denominator-kind": "subscription_cap",
                "x-nous-credits-subscription-limit-micros": micros(20.00),
                "x-nous-credits-subscription-limit-usd": "20.00",
            }
        )
        state = parse_credits_headers(headers)
        assert state is not None
        assert state.denominator_kind == "subscription_cap"

    def test_none_valid(self):
        headers = _base_headers(**{"x-nous-credits-denominator-kind": "none"})
        state = parse_credits_headers(headers)
        assert state is not None
        assert state.denominator_kind == "none"

    def test_invalid_denominator_kind_returns_none(self):
        headers = _base_headers(**{"x-nous-credits-denominator-kind": "invalid_kind"})
        assert parse_credits_headers(headers) is None


# ── Zero-division guard ────────────────────────────────────────────────────────


class TestZeroDivisionGuard:
    def test_subscription_limit_zero_used_fraction_is_none(self):
        """subscription_limit_micros='0' + subscription_cap → used_fraction is None (no ZeroDivisionError)."""
        headers = _base_headers(
            **{
                "x-nous-credits-subscription-limit-micros": "0",
                "x-nous-credits-subscription-limit-usd": "0.00",
                "x-nous-credits-denominator-kind": "subscription_cap",
            }
        )
        state = parse_credits_headers(headers)
        assert state is not None
        # limit == 0, so used_fraction must be None (guard prevents division)
        assert state.used_fraction is None


# ── Unknown headers ignored ────────────────────────────────────────────────────


class TestUnknownHeaders:
    def test_unknown_extra_header_ignored(self):
        headers = {
            **_base_headers(),
            "x-nous-credits-future-field": "some-value",
            "x-request-id": "abc123",
        }
        state = parse_credits_headers(headers)
        assert state is not None

    def test_mixed_with_other_providers_headers(self):
        headers = {
            **_base_headers(),
            "x-ratelimit-limit-requests": "800",
            "content-type": "application/json",
        }
        state = parse_credits_headers(headers)
        assert state is not None


# ── Header normalization ──────────────────────────────────────────────────────


class TestHeaderNormalization:
    def test_uppercase_headers_parsed(self):
        headers = {k.upper(): v for k, v in _base_headers().items()}
        state = parse_credits_headers(headers)
        assert state is not None

    def test_mixed_case_headers_parsed(self):
        headers = {
            "X-Nous-Credits-Version": "1",
            "X-Nous-Credits-Remaining-Micros": micros(5.00),
            "X-Nous-Credits-Remaining-Usd": "5.00",
            "X-Nous-Credits-Subscription-Micros": micros(5.00),
            "X-Nous-Credits-Subscription-Usd": "5.00",
            "X-Nous-Credits-Rollover-Micros": "0",
            "X-Nous-Credits-Purchased-Micros": "0",
            "X-Nous-Credits-Purchased-Usd": "0.00",
            "X-Nous-Tool-Pool-Micros": "0",
            "X-Nous-Tool-Pool-Gated-Off": "false",
            "X-Nous-Credits-Denominator-Kind": "none",
            "X-Nous-Credits-Paid-Access": "true",
            "X-Nous-Credits-As-Of-Ms": "1717000000000",
        }
        state = parse_credits_headers(headers)
        assert state is not None
        assert state.remaining_micros == round(5.00 * 1_000_000)


# ── CreditsState dataclass defaults ──────────────────────────────────────────


class TestCreditsStateDefaults:
    def test_default_state(self):
        state = CreditsState()
        assert state.version == 0
        assert state.remaining_micros == 0
        assert state.remaining_usd == ""
        assert state.subscription_micros == 0
        assert state.subscription_usd == ""
        assert state.subscription_limit_micros is None
        assert state.subscription_limit_usd is None
        assert state.rollover_micros == 0
        assert state.purchased_micros == 0
        assert state.purchased_usd == ""
        assert state.tool_pool_micros == 0
        assert state.tool_pool_gated_off is False
        assert state.denominator_kind == "none"
        assert state.paid_access is True
        assert state.disabled_reason is None
        assert state.as_of_ms == 0
        assert state.captured_at == 0.0
        assert state.from_header is False

    def test_has_data_false_when_no_captured_at(self):
        state = CreditsState()
        assert state.has_data is False

    def test_age_seconds_inf_when_no_data(self):
        state = CreditsState()
        assert state.age_seconds == float("inf")

    def test_depleted_false_by_default(self):
        state = CreditsState()
        assert state.depleted is False

    def test_used_fraction_none_by_default(self):
        state = CreditsState()
        assert state.used_fraction is None


# ── depleted property ─────────────────────────────────────────────────────────


class TestDepletedProperty:
    def test_depleted_equals_not_paid_access(self):
        """depleted must be exactly `not paid_access`, never `remaining==0`."""
        state = CreditsState(paid_access=False, remaining_micros=0, captured_at=time.time())
        assert state.depleted is True

    def test_not_depleted_when_paid_access_true(self):
        state = CreditsState(paid_access=True, remaining_micros=0, captured_at=time.time())
        # remaining==0 but paid_access is True → NOT depleted
        assert state.depleted is False

    def test_depleted_independent_of_remaining(self):
        """Even with remaining > 0, if paid_access is False, depleted is True."""
        state = CreditsState(paid_access=False, remaining_micros=1_000_000, captured_at=time.time())
        assert state.depleted is True


# ── used_fraction edge cases ──────────────────────────────────────────────────


class TestUsedFraction:
    def test_none_without_limit(self):
        state = CreditsState(
            denominator_kind="subscription_cap",
            subscription_limit_micros=None,
            captured_at=time.time(),
        )
        assert state.used_fraction is None

    def test_none_when_limit_zero(self):
        state = CreditsState(
            denominator_kind="subscription_cap",
            subscription_limit_micros=0,
            subscription_micros=0,
            captured_at=time.time(),
        )
        assert state.used_fraction is None

    def test_clamped_at_zero(self):
        """If subscription_micros > limit (over-credited), fraction clamps to 0."""
        state = CreditsState(
            denominator_kind="subscription_cap",
            subscription_limit_micros=10_000_000,
            subscription_micros=15_000_000,  # more than limit
            captured_at=time.time(),
        )
        assert state.used_fraction == pytest.approx(0.0)

    def test_clamped_at_one(self):
        """If subscription_micros is very negative (debt), fraction clamps to 1.0."""
        state = CreditsState(
            denominator_kind="subscription_cap",
            subscription_limit_micros=10_000_000,
            subscription_micros=-5_000_000,  # deep debt
            captured_at=time.time(),
        )
        assert state.used_fraction == pytest.approx(1.0)

    def test_guarded_by_limit_field_not_denominator(self):
        """used_fraction depends on subscription_limit_micros being truthy, not denominator_kind."""
        # limit present but denominator_kind="none" — spec says guard on LIMIT FIELD
        state = CreditsState(
            denominator_kind="none",
            subscription_limit_micros=20_000_000,
            subscription_micros=10_000_000,
            captured_at=time.time(),
        )
        # With limit_micros set, fraction should be computable regardless of denominator_kind
        assert state.used_fraction == pytest.approx(0.50)

    def test_none_when_denominator_cap_but_no_limit(self):
        """denominator_kind=subscription_cap but no limit pair → None."""
        state = CreditsState(
            denominator_kind="subscription_cap",
            subscription_limit_micros=None,
            subscription_micros=5_000_000,
            captured_at=time.time(),
        )
        assert state.used_fraction is None
