"""Unit tests for libs.parser.event_type_normalizer.

Covers:
- Each mapped Oracle value resolves to the expected strategy value.
- Unmapped values pass through unchanged (honest fail mode for engine filters).
- Empty / None / whitespace input -> "unknown".
- Case-insensitive + separator-tolerant matching.
- item_number-preferred path (uses rule_parser._ITEM_TO_EVENT_TYPE).
"""
from __future__ import annotations

import pytest

from libs.parser.event_type_normalizer import (
    normalize_oracle_event,
    normalize_oracle_event_type,
)


# ---------------------------------------------------------------------------
# String-only normalizer
# ---------------------------------------------------------------------------


@pytest.mark.parametrize(
    "raw,expected",
    [
        # Identity (already aligned with strategy vocab)
        ("earnings_release", "earnings_release"),
        ("guidance_update", "guidance_update"),
        ("material_contract", "material_contract"),
        ("management_change", "management_change"),
        ("other_material_event", "other_material_event"),
        # Earnings synonyms — the headline mapping (412 live rows)
        ("earnings_result", "earnings_release"),
        ("earnings_announcement", "earnings_release"),
        ("earnings", "earnings_release"),
        # Guidance synonyms
        ("guidance_revision", "guidance_update"),
        ("guidance_change", "guidance_update"),
        # Reg FD -> guidance_update (matches rule_parser Item 7.01 mapping)
        ("regulation_fd", "guidance_update"),
        ("regulation_fd_disclosure", "guidance_update"),
        # Material contract synonyms (Item 1.01 wording)
        ("material_definitive_agreement", "material_contract"),
        ("definitive_agreement", "material_contract"),
        # other_material_event aggregations
        ("shareholder_vote", "other_material_event"),
        ("acquisition_disposition", "other_material_event"),
        ("acquisition", "other_material_event"),
        ("disposition", "other_material_event"),
        ("bankruptcy", "other_material_event"),
        ("other_events", "other_material_event"),
        ("other", "other_material_event"),
    ],
)
def test_mapped_values_normalize_correctly(raw: str, expected: str) -> None:
    assert normalize_oracle_event_type(raw) == expected


@pytest.mark.parametrize(
    "raw",
    [
        # Items intentionally left unmapped — strategy intent unclear; let the
        # engine filter drop them visibly rather than silently re-tagging.
        "financial_obligation",  # 225 live rows
        "articles_amendment",    # 81 live rows
        "contract_termination",
        "unregistered_equity_sale",
        "rights_modification",
        "accountant_change",
        "exit_activity",
        "mine_safety",
        "triggering_event",
        "material_impairment",
        "delisting_notice",
        "bylaws_amendment",
        "shareholder_nomination",
        "control_change",
        "completely_made_up_value",
    ],
)
def test_unmapped_values_pass_through_unchanged(raw: str) -> None:
    """Honest fail mode: don't silently re-tag ambiguous values."""
    assert normalize_oracle_event_type(raw) == raw


@pytest.mark.parametrize(
    "raw,expected",
    [
        ("EARNINGS_RESULT", "earnings_release"),
        ("Earnings Result", "earnings_release"),
        ("earnings-result", "earnings_release"),
        ("Earnings  Result", "earnings_release"),  # double-space
        ("  earnings_result  ", "earnings_release"),  # padding
        ("REGULATION_FD", "guidance_update"),
        ("Material Definitive Agreement", "material_contract"),
        ("Shareholder/Vote", "other_material_event"),  # alt separator
    ],
)
def test_case_and_separator_insensitive(raw: str, expected: str) -> None:
    assert normalize_oracle_event_type(raw) == expected


@pytest.mark.parametrize("bad", [None, "", "   ", "\n", "\t"])
def test_empty_and_none_become_unknown(bad: object) -> None:
    assert normalize_oracle_event_type(bad) == "unknown"  # type: ignore[arg-type]


def test_non_string_becomes_unknown() -> None:
    # Defensive: pydantic's typed model should prevent this, but be safe.
    assert normalize_oracle_event_type(123) == "unknown"  # type: ignore[arg-type]
    assert normalize_oracle_event_type([]) == "unknown"  # type: ignore[arg-type]


def test_pass_through_preserves_original_casing() -> None:
    """Unmapped values are returned verbatim (not lowercased) so logs match
    Oracle's wire format and the DB column reflects exactly what was upstream."""
    assert normalize_oracle_event_type("Financial_Obligation") == "Financial_Obligation"


# ---------------------------------------------------------------------------
# item_number-preferred path
# ---------------------------------------------------------------------------


@pytest.mark.parametrize(
    "item_number,expected",
    [
        ("2.02", "earnings_release"),  # Results of Operations and Financial Condition
        ("7.01", "guidance_update"),   # Regulation FD Disclosure
        ("5.02", "management_change"),
        ("1.01", "material_contract"),
        ("8.01", "other_material_event"),
        ("1.03", "other_material_event"),
    ],
)
def test_item_number_takes_precedence(item_number: str, expected: str) -> None:
    """A wildly mislabeled Oracle string is overridden by a valid item_number.

    Rationale: the rule-parser already trusts item codes more than text-derived
    classifications; the Oracle-fallback path should follow the same convention.
    """
    assert (
        normalize_oracle_event(
            "completely_bogus_oracle_string",
            item_number=item_number,
        )
        == expected
    )


def test_item_number_unknown_falls_back_to_string_map() -> None:
    """Item codes outside _ITEM_TO_EVENT_TYPE shouldn't block string mapping."""
    # 9.99 isn't a real 8-K item; classifier returns "unknown", so we rely on string.
    assert (
        normalize_oracle_event("earnings_result", item_number="9.99")
        == "earnings_release"
    )


def test_no_item_number_uses_string_map() -> None:
    assert (
        normalize_oracle_event("earnings_result", item_number=None)
        == "earnings_release"
    )
    assert (
        normalize_oracle_event("earnings_result", item_number="")
        == "earnings_release"
    )


def test_no_item_number_no_match_passes_through() -> None:
    assert (
        normalize_oracle_event("financial_obligation", item_number=None)
        == "financial_obligation"
    )
