"""Ideas are ordered by how much users would get out of them (Ken, 2026-09-06: "order by
value"): a 1..5 score the researcher gives each idea, kept in the store, used by the page, the
page, and `scout status`. Older ideas get theirs from `scout score`."""

from __future__ import annotations

import json

from scout.claude_runner import ClaudeResult
from scout.scan import parse_ideas, score_ideas
from scout.store import Store, by_value, clamp_score
from scout.projects import Project


def _project(tmp_path) -> Project:
    return Project(key="terminal", name="Refacto Terminal", folder=tmp_path, topic="", what_it_is="A terminal for prediction markets.",
                   audience="prediction-market traders")


def test_scores_are_whole_numbers_one_to_five_or_nothing():
    assert clamp_score(4) == 4 and clamp_score("3") == 3 and clamp_score(4.6) == 5
    assert clamp_score(0) is None and clamp_score(6) is None
    assert clamp_score(None) is None and clamp_score("high") is None


def test_by_value_puts_high_scores_first_then_newest_then_researcher_order():
    day = 86400 * 20000
    ideas = [
        {"id": 1, "created_at": day, "value_score": 3},
        {"id": 2, "created_at": day + 86400, "value_score": 3},
        {"id": 3, "created_at": day + 86400, "value_score": 3},
        {"id": 4, "created_at": day, "value_score": 5},
        {"id": 5, "created_at": day + 86400, "value_score": None},
        {"id": 6, "created_at": day + 86400, "value_score": 9},   # out of range = unscored
    ]
    assert [i["id"] for i in by_value(ideas)] == [4, 2, 3, 1, 5, 6]


def test_the_store_keeps_the_score_and_an_old_store_gains_the_columns():
    store = Store(":memory:")
    iid = store.add_idea("terminal", "T", "s", evidence=[{"url": "https://e", "note": ""}],
                         value_score=4, value_reason="why")
    row = store.ideas()[0]
    assert row["value_score"] == 4 and row["value_reason"] == "why"
    store.set_value(iid, 2, "on reflection")
    assert store.ideas()[0]["value_score"] == 2
    store.set_value(iid, 7, "bad")           # out of range clears it rather than storing nonsense
    assert store.ideas()[0]["value_score"] is None
    # A store made before the columns existed gets them on open.
    old = Store(":memory:")
    old.conn.execute("ALTER TABLE ideas DROP COLUMN value_score")
    old.conn.execute("ALTER TABLE ideas DROP COLUMN value_reason")
    old._migrate()
    cols = {r[1] for r in old.conn.execute("PRAGMA table_info(ideas)")}
    assert {"value_score", "value_reason"} <= cols


def test_the_researcher_answer_carries_the_score():
    raw = [{"title": "A", "summary": "s", "user_value": "u", "value_score": "4",
            "value_reason": "many", "evidence": [{"url": "https://e", "note": "n"}], "effort": "S"},
           {"title": "B", "summary": "s", "value_score": 11,
            "evidence": [{"url": "https://e2", "note": "n"}]}]
    ideas = parse_ideas(json.dumps(raw))
    assert ideas[0]["value_score"] == 4 and ideas[0]["value_reason"] == "many"
    assert ideas[1]["value_score"] is None and ideas[1]["value_reason"] == ""


def test_the_page_lists_by_value_and_shows_it():
    from scout.review import column_order
    day = 86400 * 20000
    ideas = [
        {"id": 7, "platform": "terminal", "title": "Low", "summary": "s", "evidence": [], "status": "proposed",
         "created_at": day + 86400, "value_score": 2, "value_reason": "few would use it"},
        {"id": 8, "platform": "terminal", "title": "High", "summary": "s", "evidence": [], "status": "proposed",
         "created_at": day, "value_score": 5, "value_reason": "everyone, daily"},
    ]
    assert [i["id"] for i in column_order("backlog", ideas)] == [8, 7]


def test_scout_status_prints_value_first(capsys, monkeypatch):
    from scout import cli
    store = Store(":memory:")
    a = store.add_idea("terminal", "Small one", "s", evidence=[{"url": "https://e", "note": ""}], value_score=1)
    b = store.add_idea("terminal", "Big one", "s", evidence=[{"url": "https://e", "note": ""}], value_score=5)
    c = store.add_idea("terminal", "Unscored", "s", evidence=[{"url": "https://e", "note": ""}])
    monkeypatch.setattr(cli, "known_projects", lambda: {})
    cli.cmd_status(store)
    out = capsys.readouterr().out
    lines = [ln for ln in out.splitlines() if ln.strip().startswith("#")]
    assert [ln.split()[0] for ln in lines] == [f"#{b}", f"#{a}", f"#{c}"]
    assert "5/5" in lines[0] and "-/5" in lines[2]


def test_scoring_old_ideas_fills_in_only_the_unscored_ones(tmp_path):
    store = Store(":memory:")
    p = _project(tmp_path)
    old1 = store.add_idea("terminal", "Old one", "s", user_value="u", evidence=[{"url": "https://e", "note": "n"}])
    old2 = store.add_idea("terminal", "Old two", "s", evidence=[{"url": "https://e", "note": "n"}])
    done = store.add_idea("terminal", "Scored", "s", evidence=[{"url": "https://e", "note": "n"}], value_score=3)
    store.set_status(old2, "shipped")   # not open: never asked about
    seen = {}

    def fake_runner(prompt, **kw):
        seen["prompt"] = prompt
        return ClaudeResult(ok=True, text=json.dumps([
            {"id": old1, "value_score": 4, "value_reason": "many traders"},
            {"id": done, "value_score": 1, "value_reason": "should be ignored"},
            {"id": 999, "value_score": 5, "value_reason": "not ours"},
        ]), cost_usd=0.02)

    r = score_ideas(p, store, runner=fake_runner)
    assert r == {"project": "terminal", "ok": True, "asked": 1, "scored": 1, "unscored_ids": [], "cost_usd": 0.02}
    assert f"id {old1}: Old one" in seen["prompt"] and "Old two" not in seen["prompt"] and "Scored" not in seen["prompt"]
    assert "prediction-market traders" in seen["prompt"] and "$" not in seen["prompt"]
    rows = {i["id"]: i for i in store.ideas()}
    assert rows[old1]["value_score"] == 4 and rows[old1]["value_reason"] == "many traders"
    assert rows[done]["value_score"] == 3, "an already-scored idea is left alone"
    # Nothing left to do: no call is made.
    r2 = score_ideas(p, store, runner=lambda *a, **k: (_ for _ in ()).throw(AssertionError("no call")))
    assert r2["asked"] == 0 and r2["ok"]


def test_scoring_reports_a_failure_and_the_ones_it_missed(tmp_path):
    store = Store(":memory:")
    p = _project(tmp_path)
    a = store.add_idea("terminal", "A", "s", evidence=[{"url": "https://e", "note": "n"}])
    b = store.add_idea("terminal", "B", "s", evidence=[{"url": "https://e", "note": "n"}])
    r = score_ideas(p, store, runner=lambda *a_, **k: ClaudeResult(ok=False, error="boom"))
    assert r["ok"] is False and "boom" in r["error"]
    r = score_ideas(p, store, runner=lambda *a_, **k: ClaudeResult(ok=True, text=json.dumps([{"id": a, "value_score": 2}])))
    assert r["scored"] == 1 and r["unscored_ids"] == [b]
    # An answer with no JSON in it is reported as a failure, and nothing is stored.
    r = score_ideas(p, store, runner=lambda *a_, **k: ClaudeResult(ok=True, text="no json here"))
    assert r["ok"] is False and r["scored"] == 0
    assert {i["id"]: i["value_score"] for i in store.ideas()} == {a: 2, b: None}


def test_the_plan_an_idea_belongs_in_travels_from_the_answer_to_the_page():
    """Ken (2026-09-07): the scout should know a paid tier is coming. With plans set, every
    idea says which plan it belongs in and why, and that shows wherever ideas are listed."""
    raw = [{"title": "Alerts", "summary": "s", "plan": "Pro", "plan_reason": "traders pay for alerts",
            "evidence": [{"url": "https://e", "note": "n"}]},
           {"title": "Basics", "summary": "s", "evidence": [{"url": "https://e", "note": "n"}]}]
    ideas = parse_ideas(json.dumps(raw))
    assert ideas[0]["plan"] == "pro" and ideas[0]["plan_reason"] == "traders pay for alerts"
    assert ideas[1]["plan"] == "" and ideas[1]["plan_reason"] == ""
    store = Store(":memory:")
    iid = store.add_idea("terminal", "Alerts", "s", evidence=[{"url": "https://e", "note": "n"}],
                         plan="Pro", plan_reason="traders pay for alerts")
    row = store.ideas()[0]
    assert row["plan"] == "pro" and row["plan_reason"] == "traders pay for alerts"
    from scout.review import idea_card
    assert "Plan: Pro</b> — traders pay for alerts" in idea_card(row, "tok")
