"""TW-254 / TW-261 — Free indexed-repo cap and the upgrade-ready error payload."""

from __future__ import annotations

from app.middleware.trusted_context import parse_trusted_context
from app.services.plan_limits import (
    indexed_repo_limit_for_plan,
    repo_limit_error_payload,
    resolve_indexed_repo_limit,
)

BASE_HEADERS = {
    "X-Request-Id": "req1",
    "X-Org-Id": "org1",
    "X-User-Id": "user1",
}


class TestPlanLimits:
    def test_free_is_capped_at_thirty(self):
        assert indexed_repo_limit_for_plan("free") == 30

    def test_paid_plans_are_unlimited(self):
        assert indexed_repo_limit_for_plan("pro") is None
        assert indexed_repo_limit_for_plan("team") is None

    def test_plan_code_is_case_insensitive(self):
        assert indexed_repo_limit_for_plan("PRO") is None
        assert indexed_repo_limit_for_plan("Free") == 30

    def test_unknown_plan_gets_the_free_cap_not_unlimited(self):
        # Failing open here would hand every unknown plan unlimited indexing.
        assert indexed_repo_limit_for_plan("enterprise-legacy") == 30

    def test_missing_plan_gets_the_free_cap(self):
        assert indexed_repo_limit_for_plan(None) == 30


class TestResolveLimit:
    def test_gateway_value_wins_when_header_present(self):
        assert resolve_indexed_repo_limit("free", 50, header_present=True) == 50

    def test_gateway_unlimited_is_honoured(self):
        assert resolve_indexed_repo_limit("free", None, header_present=True) is None

    def test_absent_header_falls_back_to_plan_table(self):
        # The critical case: no header must not read as "unlimited" for a Free seat.
        assert resolve_indexed_repo_limit("free", None, header_present=False) == 30

    def test_absent_header_still_unlimited_for_pro(self):
        assert resolve_indexed_repo_limit("pro", None, header_present=False) is None


class TestTrustedContextParsing:
    def test_parses_numeric_cap(self):
        ctx = parse_trusted_context({**BASE_HEADERS, "X-Max-Indexed-Repos": "30"})
        assert ctx.max_indexed_repos == 30
        assert ctx.max_indexed_repos_present is True

    def test_parses_unlimited_sentinel(self):
        ctx = parse_trusted_context({**BASE_HEADERS, "X-Max-Indexed-Repos": "unlimited"})
        assert ctx.max_indexed_repos is None
        assert ctx.max_indexed_repos_present is True

    def test_absent_header_marks_not_present(self):
        ctx = parse_trusted_context(BASE_HEADERS)
        assert ctx.max_indexed_repos_present is False

    def test_malformed_value_is_treated_as_absent_not_unlimited(self):
        ctx = parse_trusted_context({**BASE_HEADERS, "X-Max-Indexed-Repos": "banana"})
        assert ctx.max_indexed_repos_present is False
        assert resolve_indexed_repo_limit(
            "free", ctx.max_indexed_repos, header_present=ctx.max_indexed_repos_present
        ) == 30

    def test_negative_value_is_rejected(self):
        ctx = parse_trusted_context({**BASE_HEADERS, "X-Max-Indexed-Repos": "-1"})
        assert ctx.max_indexed_repos_present is False

    def test_seat_id_is_parsed(self):
        ctx = parse_trusted_context({**BASE_HEADERS, "X-Seat-Id": "seat1"})
        assert ctx.seat_id == "seat1"


class TestErrorPayload:
    def test_carries_everything_the_cta_needs(self):
        payload = repo_limit_error_payload("free", 30, 30)
        assert payload == {
            "code": "REPO_LIMIT_EXCEEDED",
            "message": "Indexed repository limit reached",
            "plan_code": "free",
            "max_indexed_repos": 30,
            "current_count": 30,
        }

    def test_includes_requested_count_for_batch_imports(self):
        payload = repo_limit_error_payload("free", 30, 28, requested_count=5)
        assert payload["requested_count"] == 5

    def test_missing_plan_code_defaults_to_free(self):
        assert repo_limit_error_payload(None, 30, 30)["plan_code"] == "free"
