"""Tests for the CRM client's payload builder (pure logic, no network)."""
import os
import sys

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

import crm_client  # noqa: E402


def test_new_investor_payload():
    p = {"intent": "new_investor", "investor_name": "Acme Capital",
         "contact_name": "Jane Doe", "contact_email": "jane@acme.com",
         "contact_title": "GP", "note": "met at conf"}
    out = crm_client.build_commit_payload(p)
    assert out["investor_name"] == "Acme Capital"
    assert out["create_investor_if_missing"] is True
    assert "row_id" not in out
    assert out["contact"] == {"name": "Jane Doe", "email": "jane@acme.com", "title": "GP",
                              "city": "", "linkedin_url": "", "phone": "", "mobile": ""}
    assert out["body"] == "met at conf"
    assert out["source"] == "matrix_intake"


def test_contact_carries_card_fields_when_present():
    p = {"intent": "new_investor", "investor_name": "Acme Capital", "contact_name": "Jane Doe",
         "contact_email": "jane@acme.com", "city": "New York",
         "linkedin_url": "linkedin.com/in/janedoe", "phone": "212-555-0100",
         "mobile": "917-555-0199", "note": "met at conf"}
    out = crm_client.build_commit_payload(p)
    assert out["contact"]["city"] == "New York"
    assert out["contact"]["linkedin_url"] == "linkedin.com/in/janedoe"
    assert out["contact"]["phone"] == "212-555-0100"   # office/main line
    assert out["contact"]["mobile"] == "917-555-0199"  # cell


def test_existing_investor_uses_row_id_not_create():
    p = {"intent": "meeting_note", "investor_name": "Acme Capital",
         "contact_name": "Jane Doe", "contact_email": None, "note": "wants Q3 deck",
         "_match_id": "rowAcme"}
    out = crm_client.build_commit_payload(p)
    assert out["row_id"] == "rowAcme"
    assert "create_investor_if_missing" not in out
    assert "investor_name" not in out  # targeted by row id, never re-matched by name
    assert out["body"] == "wants Q3 deck"


def test_contact_falls_back_to_investor_name_when_no_person():
    p = {"intent": "new_investor", "investor_name": "Delta Fund",
         "contact_name": None, "contact_email": None, "note": None}
    out = crm_client.build_commit_payload(p)
    assert out["contact"]["name"] == "Delta Fund"
    assert out["body"] == ""


def test_no_email_sends_empty_string_not_none():
    p = {"intent": "new_investor", "investor_name": "Gamma", "contact_name": "Bob",
         "contact_email": None, "note": "x"}
    out = crm_client.build_commit_payload(p)
    assert out["contact"]["email"] == ""


def test_subject_blank_when_note_present_else_provenance_label():
    # The CRM's grid note line uses subject-or-body, so a blank subject lets the note text show.
    with_note = crm_client.build_commit_payload(
        {"intent": "meeting_note", "investor_name": "Acme", "note": "sent the deck", "_match_id": "r1"})
    assert with_note["subject"] == ""
    assert with_note["body"] == "sent the deck"
    # no note text → fall back to a provenance label so the grid line isn't empty
    no_note = crm_client.build_commit_payload(
        {"intent": "new_investor", "investor_name": "Beta", "contact_name": "X", "note": None})
    assert no_note["subject"] == "Intake (Matrix)"


def test_source_defaults_to_intake_and_card_overrides():
    # Provenance: a typed note tags source="matrix_intake"; a scanned card rides in on
    # _source="matrix_card" (set by the bot's image handler) so the audit log distinguishes them.
    typed = crm_client.build_commit_payload(
        {"intent": "new_investor", "investor_name": "Acme", "note": "x"})
    assert typed["source"] == "matrix_intake"
    card = crm_client.build_commit_payload(
        {"intent": "new_investor", "investor_name": "Acme", "note": "x", "_source": "matrix_card"})
    assert card["source"] == "matrix_card"


def _with_stub_authed(reply, capture=None):
    """Swap crm_client._authed for a canned (status, data); return a restorer."""
    orig = crm_client._authed

    def fake(method, path, body=None):
        if capture is not None:
            capture["path"] = path
        return reply

    crm_client._authed = fake
    return orig


def test_match_parses_exact_match():
    cap = {}
    orig = _with_stub_authed((200, {"data": {
        "match": {"id": "rowAcme", "investor_name": "Acme Capital", "matched_on": "name"},
        "candidates": [],
    }}), cap)
    try:
        res = crm_client.match({"investor_name": "Acme Capital", "contact_email": ""})
    finally:
        crm_client._authed = orig
    assert res["match"] == {"id": "rowAcme", "name": "Acme Capital"}
    assert res["candidates"] == []
    assert "q=Acme" in cap["path"]  # the query was forwarded


def test_match_returns_ranked_candidates_when_no_exact():
    orig = _with_stub_authed((200, {"data": {"match": None, "candidates": [
        {"id": "rowCharlie", "investor_name": "Charlie Brown", "score": 0.92, "matched_on": "name"},
        {"id": "rowBeta", "investor_name": "Beta Capital LLC", "score": 0.86, "matched_on": "name"},
    ]}}))
    try:
        res = crm_client.match({"investor_name": "Charles Brown"})
    finally:
        crm_client._authed = orig
    assert res["match"] is None
    assert [c["id"] for c in res["candidates"]] == ["rowCharlie", "rowBeta"]
    assert res["candidates"][0]["name"] == "Charlie Brown"
    assert res["candidates"][0]["matched_on"] == "name"


def test_match_no_query_skips_network():
    def boom(*a, **k):
        raise AssertionError("should not hit the network when there's nothing to match on")
    orig = crm_client._authed
    crm_client._authed = boom
    try:
        res = crm_client.match({"investor_name": None, "contact_name": None, "contact_email": None})
    finally:
        crm_client._authed = orig
    assert res == {"match": None, "candidates": []}


def test_nl_query_returns_endpoint_data():
    cap = {}
    orig = _with_stub_authed(
        (200, {"data": {"intent": "top_investors_committed", "rows": [], "summary": "ok"}}), cap)
    try:
        res = crm_client.nl_query("top investors")
    finally:
        crm_client._authed = orig
    assert res["intent"] == "top_investors_committed"
    assert cap["path"] == "/api/query/nl"


def test_nl_query_passes_through_soft_503():
    # Model-down still carries a structured body (the endpoint 503s with the error in `data`) —
    # return it for the renderer to surface, don't raise.
    orig = _with_stub_authed((503, {"data": {"error": "model_unavailable"}}))
    try:
        res = crm_client.nl_query("anything")
    finally:
        crm_client._authed = orig
    assert res["error"] == "model_unavailable"


def test_nl_query_raises_on_auth_failure():
    orig = _with_stub_authed((403, {"error": "Bot or admin required"}))
    raised = False
    try:
        crm_client.nl_query("x")
    except RuntimeError:
        raised = True
    finally:
        crm_client._authed = orig
    assert raised


if __name__ == "__main__":
    fns = [v for k, v in sorted(globals().items()) if k.startswith("test_") and callable(v)]
    for fn in fns:
        fn()
        print(f"ok  {fn.__name__}")
    print(f"\n{len(fns)} passed")
