"""Fast, deterministic unit tests for Jessica's tool logic.

These never touch the real vault (VAULT is monkeypatched to a tmp dir) and never
call an LLM, so they're cheap and safe to run anywhere.
"""

import pytest

import agent
import recipes
import vault


@pytest.fixture
def tmp_vault(tmp_path, monkeypatch):
    monkeypatch.setattr(vault, "VAULT", tmp_path)
    return tmp_path


@pytest.mark.parametrize(
    "item,section",
    [
        ("asparagus", "Produce"),
        ("chicken thighs", "Meat / Protein"),
        ("whole milk", "Dairy"),
        ("olive oil", "Pantry"),
        ("paper towels", "Household"),
        ("frozen peas", "Frozen"),
    ],
)
def test_categorize_grocery(item, section):
    assert vault.categorize_grocery(item) == section


def test_add_grocery_files_under_section(tmp_vault):
    (tmp_vault / "Tasks").mkdir()
    (tmp_vault / "Tasks" / "Grocery List.md").write_text("## Produce\n- [ ] apples\n")
    assert vault.add_grocery("bananas") == "Produce"
    content = (tmp_vault / "Tasks" / "Grocery List.md").read_text()
    assert "- [ ] bananas" in content
    assert content.index("bananas") > content.index("## Produce")


def test_add_grocery_creates_missing_section(tmp_vault):
    vault.add_grocery("frozen waffles")
    content = (tmp_vault / "Tasks" / "Grocery List.md").read_text()
    assert "## Frozen" in content
    assert "- [ ] frozen waffles" in content


def test_capture_creates_inbox_with_tag(tmp_vault):
    vault.capture("NOTE", "call the architect")
    inbox = (tmp_vault / "Jessica Inbox.md").read_text()
    assert "NOTE: call the architect" in inbox


def test_append_under_new_heading(tmp_vault):
    vault.append_under_heading("Tasks/Household.md", "## When I Get To It", "- [ ] fix gate")
    txt = (tmp_vault / "Tasks" / "Household.md").read_text()
    assert "## When I Get To It" in txt
    assert "- [ ] fix gate" in txt


def test_read_today_focus_only_today_column(tmp_vault):
    (tmp_vault / "Tasks").mkdir()
    (tmp_vault / "Tasks" / "Task Board.md").write_text(
        "## This Week\n- [ ] later thing\n"
        "## Today / Focus\n- [ ] call Bob\n- [x] done item\n"
        "## Done\n- [x] old\n"
    )
    items = vault.read_today_focus()
    assert "call Bob" in items
    assert "done item" in items
    assert "later thing" not in items


def test_pantry_add_and_remove(tmp_vault):
    vault.add_pantry("soy sauce")
    assert any("soy sauce" in i for i in vault.read_pantry())
    assert vault.remove_pantry("soy sauce") is True
    assert not any("soy sauce" in i for i in vault.read_pantry())


def test_safe_path_rejects_escape(tmp_vault):
    with pytest.raises(ValueError):
        vault._safe_path("../../etc/passwd")


@pytest.mark.parametrize(
    "value,from_u,to_u,expected",
    [
        (5, "miles", "km", "8.04"),
        (350, "fahrenheit", "celsius", "176"),
        (0, "celsius", "fahrenheit", "32"),
        (2, "cups", "ml", "473"),
        (3, "inches", "cm", "7.62"),
        (100, "celsius", "kelvin", "373"),
    ],
)
def test_convert_measure(value, from_u, to_u, expected):
    out = agent.convert_measure(value, from_u, to_u)
    assert out is not None and expected in out


def test_convert_measure_incompatible():
    assert agent.convert_measure(5, "miles", "kg") is None


def test_recipes_walk_instructions_flattens():
    steps = recipes._walk_instructions(
        [
            {"@type": "HowToStep", "text": "Do thing one"},
            {
                "@type": "HowToSection",
                "itemListElement": [{"@type": "HowToStep", "text": "Sub step"}],
            },
            "Plain string step",
        ]
    )
    assert steps == ["Do thing one", "Sub step", "Plain string step"]


def test_find_recipe_legacy_links(tmp_vault):
    # Legacy flat Recipes.md web links remain reachable as a fallback.
    (tmp_vault / "Recipes.md").write_text(
        "- [Easy Skillet Lemon Chicken](https://example.com/lemon)\n"
        "- [Grilled Caesar Salad](https://example.com/caesar)\n"
    )
    hit = recipes.find_recipe("lemon chicken")
    assert hit is not None and "Lemon Chicken" in hit[0]
    assert hit[1].startswith("http")  # legacy locator is a URL
    assert recipes.find_recipe("beef wellington") is None


def test_structured_recipe_note_parsed(tmp_vault):
    d = tmp_vault / "Recipes" / "Good Things"
    d.mkdir(parents=True)
    (d / "Basil Pesto.md").write_text(
        "---\n"
        "type: recipe\n"
        "title: Basil Pesto\n"
        "tags: [recipe/sauce, pesto]\n"
        "url:\n"
        "---\n\n"
        "A streamlined pesto with generous Parmesan.\n\n"
        "## Ingredients\n"
        "- 1 cup Parmesan\n"
        "- 2 cups basil leaves\n\n"
        "## Steps\n"
        "1. Pulse the Parmesan.\n"
        "2. Add basil and oil.\n\n"
        "## Notes\n"
        "- great on pasta\n"
    )
    hit = recipes.find_recipe("pesto")
    assert hit is not None and hit[0] == "Basil Pesto"
    assert not hit[1].startswith("http")  # structured locator is a note path
    data = recipes.load_recipe(hit[1])
    assert data["name"] == "Basil Pesto"
    assert data["ingredients"] == ["1 cup Parmesan", "2 cups basil leaves"]
    assert data["steps"] == ["Pulse the Parmesan.", "Add basil and oil."]


def test_iso_duration_to_human():
    assert recipes._iso_duration_to_human("PT45M") == "45 minutes"
    assert recipes._iso_duration_to_human("PT1H30M") == "1 hour 30 minutes"
    assert recipes._iso_duration_to_human("PT2H") == "2 hours"
    assert recipes._iso_duration_to_human(None) == ""


def test_import_web_recipe_writes_structured_note(tmp_vault, monkeypatch):
    monkeypatch.setattr(
        recipes,
        "_fetch_web",
        lambda url: {
            "name": "Test Lemon Chicken",
            "ingredients": ["2 lemons", "4 chicken breasts"],
            "steps": ["Season the chicken.", "Sear and finish in the oven."],
            "url": url,
            "author": "A Cook",
            "servings": "4",
            "total_time": "30 minutes",
            "source": "example.com",
        },
    )
    rel = recipes.import_web_recipe("https://example.com/lemon")
    assert rel == "Recipes/Web/Test Lemon Chicken.md"
    note = (tmp_vault / rel).read_text()
    assert "title: Test Lemon Chicken" in note
    assert "author: A Cook" in note
    assert "- 2 lemons" in note
    assert "1. Season the chicken." in note
    # discoverable + loadable through the normal library API
    hit = recipes.find_recipe("lemon chicken")
    assert hit is not None and hit[1] == rel
    assert recipes.load_recipe(rel)["steps"][0] == "Season the chicken."


def test_list_skips_readme_and_inbox(tmp_vault):
    r = tmp_vault / "Recipes"
    (r / "Cookbook").mkdir(parents=True)
    (r / "Cookbook" / "Real.md").write_text("---\ntitle: Real\n---\n## Steps\n1. go\n")
    (r / "README.md").write_text("# readme")
    (r / "_inbox").mkdir()
    (r / "_inbox" / "scan.md").write_text("---\ntitle: Scan\n---\n")
    titles = [t for t, _ in recipes.list_saved_recipes()]
    assert "Real" in titles
    assert "README" not in titles
    assert "Scan" not in titles
