from app.nlp.record_positions import apply_complete_record_intent
from app.planning.models import RawQueryPlan, SortSpec
from app.planning.operators import Operation


def _raw(**kwargs) -> RawQueryPlan:
    defaults = dict(operation=Operation.LIST, entity="account_transactions", fields=[])
    defaults.update(kwargs)
    return RawQueryPlan(**defaults)


# --- Ordinal phrasing is parsed into 1-indexed positions -----------------------

def test_numeric_positions_with_commas_and_and():
    plan = apply_complete_record_intent(
        "Show me the 2, 3 and 4 record from account_transactions", _raw()
    )
    assert plan.record_positions == [2, 3, 4]
    assert plan.select_all is True
    assert plan.fields == []


def test_regression_reported_bug_wording_exact():
    """
    Regression test for the exact bug report:
    "Show me the 2, 3 and 4 record from account_transactions" used to 422
    because the LLM invented a `sort` field (`zoho_account_transaction_id`)
    that doesn't exist on the table. The deterministic intent layer must
    strip any LLM-provided `sort` for ordinal requests so ordering always
    comes from the table's real, schema-verified primary key instead.
    """
    llm_raw = RawQueryPlan(
        operation=Operation.LIST,
        entity="account_transactions",
        fields=[],
        sort=SortSpec(field="zoho_account_transaction_id", direction="ASC"),
    )
    plan = apply_complete_record_intent(
        "Show me the 2, 3 and 4 record from account_transactions", llm_raw
    )
    assert plan.sort is None
    assert plan.select_all is True
    assert plan.fields == []
    assert plan.record_positions == [2, 3, 4]


def test_ordinal_words_are_parsed():
    plan = apply_complete_record_intent(
        "show me the second, third and fourth records", _raw()
    )
    assert plan.record_positions == [2, 3, 4]


def test_ordinal_suffixes_are_parsed():
    plan = apply_complete_record_intent("show the 2nd record", _raw())
    assert plan.record_positions == [2]


def test_first_n_records_phrasing_means_top_n_not_position_n():
    """
    "the first 5 records" must mean records 1-5 (a count), not the single
    ordinal position 5 - i.e. record_positions must be [1, 2, 3, 4, 5], not
    [5]. This is distinct from "the 5th record" (a single ordinal position).
    """
    plan = apply_complete_record_intent("show the first 5 records", _raw())
    assert plan.record_positions == [1, 2, 3, 4, 5]
    assert plan.select_all is True


def test_top_n_records_phrasing():
    plan = apply_complete_record_intent("show the top 3 records", _raw())
    assert plan.record_positions == [1, 2, 3]


def test_single_ordinal_record_is_distinct_from_first_n():
    plan = apply_complete_record_intent("show me the 5th record", _raw())
    assert plan.record_positions == [5]


def test_show_all_records_selects_all_without_positions():
    plan = apply_complete_record_intent("show all records", _raw())
    assert plan.select_all is True
    assert plan.record_positions == []
    assert plan.fields == []


def test_show_records_plural_selects_all():
    plan = apply_complete_record_intent("show records", _raw())
    assert plan.select_all is True


def test_non_record_question_is_left_untouched():
    raw = _raw(fields=["amount", "status"])
    plan = apply_complete_record_intent("what is the total amount for account X", raw)
    assert plan.select_all is False
    assert plan.fields == ["amount", "status"]
    assert plan.record_positions == []


def test_llm_invented_fields_are_discarded_for_ordinal_requests():
    """
    Even if the LLM also hallucinates entries in `fields` (not just `sort`),
    a recognized ordinal request must discard them - the real columns are
    resolved later, from live schema metadata, never from LLM output.
    """
    llm_raw = RawQueryPlan(
        operation=Operation.LIST,
        entity="account_transactions",
        fields=["zoho_account_transaction_id", "made_up_column"],
    )
    plan = apply_complete_record_intent(
        "show me the 2, 3 and 4 record from account_transactions", llm_raw
    )
    assert plan.fields == []
    assert plan.select_all is True


def test_works_generically_for_arbitrary_table_names():
    """Not special-cased to account_transactions - any entity name works identically."""
    for entity in ("employees", "invoices", "widgets", "zz_top_secret_table"):
        plan = apply_complete_record_intent(
            f"show me the 2, 3 and 4 record from {entity}", _raw(entity=entity)
        )
        assert plan.record_positions == [2, 3, 4]
        assert plan.select_all is True
