"""Tests for write helpers — correction, extraction_strategy, pending_ops."""

from __future__ import annotations

import time
from types import SimpleNamespace

from arandu.write.correction import CorrectionResult, is_user_correction
from arandu.write.extraction_strategy import (
    ExtractionMode,
    ExtractionStrategy,
    InputType,
    classify_input,
    select_strategy,
)
from arandu.write.pending_ops import (
    _pending,
    clear_pending,
    get_pending,
    save_pending_selection,
)

# ---------------------------------------------------------------------------
# is_user_correction
# ---------------------------------------------------------------------------


def test_correction_same_key_different_value():
    """Same attribute_key, different value_json → correction."""
    old = SimpleNamespace(attribute_key="user.home.city", value_json={"v": "SP"})
    new = SimpleNamespace(attribute_key="user.home.city", value_json={"v": "RJ"})
    assert is_user_correction(old, new) is True


def test_correction_same_value():
    """Same attribute_key, same value_json → not a correction."""
    old = SimpleNamespace(attribute_key="user.home.city", value_json={"v": "SP"})
    new = SimpleNamespace(attribute_key="user.home.city", value_json={"v": "SP"})
    assert is_user_correction(old, new) is False


def test_correction_different_key():
    """Different attribute_key → not a correction."""
    old = SimpleNamespace(attribute_key="user.home.city", value_json={"v": "SP"})
    new = SimpleNamespace(attribute_key="user.home.state", value_json={"v": "RJ"})
    assert is_user_correction(old, new) is False


def test_correction_result_defaults():
    """CorrectionResult should have sane defaults."""
    r = CorrectionResult()
    assert r.corrections_detected == 0
    assert r.corrected_keys == []
    assert r.facts_corrected_ids == []


# ---------------------------------------------------------------------------
# classify_input
# ---------------------------------------------------------------------------


def test_classify_empty():
    """Empty text → SHORT."""
    c = classify_input("")
    assert c.input_type == InputType.SHORT
    assert c.char_count == 0


def test_classify_short():
    """Short message → SHORT."""
    c = classify_input("Moro em São Paulo")
    assert c.input_type == InputType.SHORT
    assert c.char_count < 500


def test_classify_medium():
    """Medium text → MEDIUM."""
    text = "a " * 600  # 1200 chars
    c = classify_input(text)
    assert c.input_type == InputType.MEDIUM


def test_classify_long():
    """Long unstructured text → LONG."""
    text = "a " * 1500  # 3000 chars, no structure
    c = classify_input(text)
    assert c.input_type == InputType.LONG


def test_classify_structured():
    """Structured text with headers → STRUCTURED."""
    text = "# Header 1\n" + "content " * 100 + "\n\n# Header 2\n" + "more content " * 100
    c = classify_input(text)
    assert c.input_type == InputType.STRUCTURED
    assert c.has_headers is True


# ---------------------------------------------------------------------------
# select_strategy
# ---------------------------------------------------------------------------


def test_strategy_short():
    """Short → SINGLE_SHOT."""
    c = classify_input("hello")
    s = select_strategy(c)
    assert s.mode == ExtractionMode.SINGLE_SHOT
    assert s.estimated_chunks == 1


def test_strategy_long():
    """Long → CHUNKED."""
    text = "a " * 1500
    c = classify_input(text)
    s = select_strategy(c)
    assert s.mode == ExtractionMode.CHUNKED
    assert s.estimated_chunks >= 2


def test_strategy_defaults():
    """ExtractionStrategy should be constructible."""
    s = ExtractionStrategy(
        mode=ExtractionMode.SINGLE_SHOT,
        reason="test",
        max_tokens_per_call=4096,
        estimated_chunks=1,
        chunk_context_hint=None,
    )
    assert s.mode == ExtractionMode.SINGLE_SHOT


# ---------------------------------------------------------------------------
# pending_ops
# ---------------------------------------------------------------------------


def test_pending_save_and_get():
    """Save and retrieve pending selection."""
    _pending.clear()
    save_pending_selection("user1", "delete", [{"id": 1}], "Confirm?")
    op = get_pending("user1")
    assert op is not None
    assert op["type"] == "selection"
    assert op["intent"] == "delete"
    _pending.clear()


def test_pending_expiry():
    """Expired pending ops should return None."""
    _pending.clear()
    save_pending_selection("user2", "edit", [], "Confirm?")
    # Manually set timestamp to expired
    _pending["user2"]["timestamp"] = time.time() - 600
    op = get_pending("user2")
    assert op is None
    _pending.clear()


def test_pending_clear():
    """clear_pending should remove entry."""
    _pending.clear()
    save_pending_selection("user3", "delete", [], "Confirm?")
    clear_pending("user3")
    assert get_pending("user3") is None
    _pending.clear()


def test_pending_nonexistent():
    """get_pending for unknown user → None."""
    _pending.clear()
    assert get_pending("unknown_user") is None
