"""Tests for the Affinity CRM tool."""

import pytest
import httpx
from pytest_httpx import HTTPXMock

from ecs_mcp.tools.affinity import (
    _get_api_key,
    _find_list_by_name,
    _find_org_in_list,
    _normalize,
    _names_match,
    _create_organization,
    _create_list_entry,
    _create_opportunity,
    _find_person_by_email,
    _create_person,
    _create_note,
    _search_opportunities_in_affinity,
    create_deal,
    search_opportunities,
    AFFINITY_BASE_URL,
    LIST_TYPE_OPPORTUNITY,
)

# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------

@pytest.fixture(autouse=True)
def set_api_key(monkeypatch):
    monkeypatch.setenv("AFFINITY_API_KEY", "test-key")


@pytest.fixture
def async_client():
    return httpx.AsyncClient(base_url=AFFINITY_BASE_URL, auth=("", "test-key"))


# ---------------------------------------------------------------------------
# _get_api_key
# ---------------------------------------------------------------------------

def test_get_api_key_returns_key(monkeypatch):
    monkeypatch.setenv("AFFINITY_API_KEY", "my-secret-key")
    assert _get_api_key() == "my-secret-key"


def test_get_api_key_strips_whitespace(monkeypatch):
    monkeypatch.setenv("AFFINITY_API_KEY", "  my-key  ")
    assert _get_api_key() == "my-key"


def test_get_api_key_raises_when_missing(monkeypatch):
    monkeypatch.delenv("AFFINITY_API_KEY", raising=False)
    with pytest.raises(ValueError, match="AFFINITY_API_KEY"):
        _get_api_key()


def test_get_api_key_raises_when_empty(monkeypatch):
    monkeypatch.setenv("AFFINITY_API_KEY", "   ")
    with pytest.raises(ValueError, match="AFFINITY_API_KEY"):
        _get_api_key()


# ---------------------------------------------------------------------------
# _find_list_by_name
# ---------------------------------------------------------------------------

async def test_find_list_by_name_found(async_client, httpx_mock: HTTPXMock):
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/lists",
        json=[
            {"id": 1, "name": "Other List"},
            {"id": 42, "name": "ALL DEALS ECS"},
        ],
    )
    result = await _find_list_by_name(async_client, "ALL DEALS ECS")
    assert result == {"id": 42, "name": "ALL DEALS ECS"}


async def test_find_list_by_name_case_insensitive(async_client, httpx_mock: HTTPXMock):
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/lists",
        json=[{"id": 42, "name": "ALL DEALS ECS"}],
    )
    result = await _find_list_by_name(async_client, "all deals ecs")
    assert result["id"] == 42


async def test_find_list_by_name_not_found(async_client, httpx_mock: HTTPXMock):
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/lists",
        json=[{"id": 1, "name": "Other List"}],
    )
    result = await _find_list_by_name(async_client, "ALL DEALS ECS")
    assert result is None


async def test_find_list_by_name_empty(async_client, httpx_mock: HTTPXMock):
    httpx_mock.add_response(url=f"{AFFINITY_BASE_URL}/lists", json=[])
    result = await _find_list_by_name(async_client, "ALL DEALS ECS")
    assert result is None


# ---------------------------------------------------------------------------
# _normalize / _names_match
# ---------------------------------------------------------------------------

def test_normalize_strips_punctuation():
    assert _normalize("Mertz-Taggart") == "mertz taggart"
    assert _normalize("A & B Co.") == "a b co"


def test_names_match_exact():
    assert _names_match("Acme Corp", "acme corp")


def test_names_match_punctuation_difference():
    assert _names_match("Mertz-Taggart", "Mertz Taggart")
    assert _names_match("A&B Co", "A B Co")


def test_names_match_substring():
    assert _names_match("Acme", "Acme Corporation")


def test_names_match_no_match():
    assert not _names_match("Acme", "Widgets Inc")


# ---------------------------------------------------------------------------
# _find_org_in_list
# ---------------------------------------------------------------------------

async def test_find_org_in_list_exact_match_opportunity_list(async_client, httpx_mock: HTTPXMock):
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/lists/42/list-entries?page_size=500",
        json=[{"entity_id": 99, "entity": {"id": 1, "organization_id": 99}}],
    )
    httpx_mock.add_response(url=f"{AFFINITY_BASE_URL}/organizations/99", json={"id": 99, "name": "Acme Corp"})
    org, warnings = await _find_org_in_list(async_client, list_id=42, list_type=LIST_TYPE_OPPORTUNITY, org_name="Acme Corp")
    assert org["id"] == 99
    assert warnings == []


async def test_find_org_in_list_near_match(async_client, httpx_mock: HTTPXMock):
    # "Mertz-Taggart" in list, searching for "Mertz Taggart" → should match
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/lists/42/list-entries?page_size=500",
        json=[{"entity_id": 99, "entity": {"id": 1, "organization_id": 99}}],
    )
    httpx_mock.add_response(url=f"{AFFINITY_BASE_URL}/organizations/99", json={"id": 99, "name": "Mertz-Taggart"})
    org, warnings = await _find_org_in_list(async_client, list_id=42, list_type=LIST_TYPE_OPPORTUNITY, org_name="Mertz Taggart")
    assert org["id"] == 99


async def test_find_org_in_list_no_match(async_client, httpx_mock: HTTPXMock):
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/lists/42/list-entries?page_size=500",
        json=[{"entity_id": 99, "entity": {"id": 1, "organization_id": 99}}],
    )
    httpx_mock.add_response(url=f"{AFFINITY_BASE_URL}/organizations/99", json={"id": 99, "name": "Widgets Inc"})
    org, warnings = await _find_org_in_list(async_client, list_id=42, list_type=LIST_TYPE_OPPORTUNITY, org_name="Acme Corp")
    assert org is None
    assert warnings == []


async def test_find_org_in_list_empty_list(async_client, httpx_mock: HTTPXMock):
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/lists/42/list-entries?page_size=500",
        json=[],
    )
    org, warnings = await _find_org_in_list(async_client, list_id=42, list_type=LIST_TYPE_OPPORTUNITY, org_name="Acme Corp")
    assert org is None
    assert warnings == []


async def test_find_org_in_list_pagination(async_client, httpx_mock: HTTPXMock):
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/lists/42/list-entries?page_size=500",
        json={
            "list_entries": [{"entity_id": 1, "entity": {"organization_id": 1}}],
            "next_page_token": "tok123",
        },
    )
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/lists/42/list-entries?page_size=500&page_token=tok123",
        json={
            "list_entries": [{"entity_id": 99, "entity": {"organization_id": 99}}],
            "next_page_token": None,
        },
    )
    httpx_mock.add_response(url=f"{AFFINITY_BASE_URL}/organizations/1", json={"id": 1, "name": "Other Co"})
    httpx_mock.add_response(url=f"{AFFINITY_BASE_URL}/organizations/99", json={"id": 99, "name": "Acme Corp"})
    org, _ = await _find_org_in_list(async_client, list_id=42, list_type=LIST_TYPE_OPPORTUNITY, org_name="Acme Corp")
    assert org["id"] == 99


async def test_find_org_in_list_non_opportunity_list_uses_entity_id(async_client, httpx_mock: HTTPXMock):
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/lists/42/list-entries?page_size=500",
        json=[{"entity_id": 99, "entity": {"id": 99, "name": "Acme Corp"}}],
    )
    httpx_mock.add_response(url=f"{AFFINITY_BASE_URL}/organizations/99", json={"id": 99, "name": "Acme Corp"})
    org, _ = await _find_org_in_list(async_client, list_id=42, list_type=1, org_name="Acme Corp")
    assert org["id"] == 99


# ---------------------------------------------------------------------------
# _create_organization
# ---------------------------------------------------------------------------

async def test_create_organization_with_domain(async_client, httpx_mock: HTTPXMock):
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/organizations",
        method="POST",
        json={"id": 99, "name": "Acme Corp", "domain": "acme.com"},
    )
    result = await _create_organization(async_client, "Acme Corp", domain="acme.com")
    assert result["id"] == 99
    request = httpx_mock.get_requests()[0]
    import json
    body = json.loads(request.content)
    assert body["domain"] == "acme.com"


async def test_create_organization_derives_domain_when_not_provided(async_client, httpx_mock: HTTPXMock):
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/organizations",
        method="POST",
        json={"id": 99, "name": "Acme Corp", "domain": "acmecorp.com"},
    )
    await _create_organization(async_client, "Acme Corp")
    import json
    body = json.loads(httpx_mock.get_requests()[0].content)
    assert body["domain"] == "acmecorp.com"
    assert body["name"] == "Acme Corp"


async def test_create_organization_strips_special_chars_from_derived_domain(async_client, httpx_mock: HTTPXMock):
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/organizations",
        method="POST",
        json={"id": 99, "name": "A & B Co."},
    )
    await _create_organization(async_client, "A & B Co.")
    import json
    body = json.loads(httpx_mock.get_requests()[0].content)
    assert body["domain"] == "abco.com"


# ---------------------------------------------------------------------------
# _create_list_entry
# ---------------------------------------------------------------------------

async def test_create_list_entry(async_client, httpx_mock: HTTPXMock):
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/lists/42/list-entries",
        method="POST",
        json={"id": 500, "list_id": 42, "entity_id": 99},
    )
    result = await _create_list_entry(async_client, list_id=42, entity_id=99)
    assert result["id"] == 500
    import json
    body = json.loads(httpx_mock.get_requests()[0].content)
    assert body == {"entity_id": 99}


# ---------------------------------------------------------------------------
# _find_person_by_email
# ---------------------------------------------------------------------------

async def test_find_person_by_email_found(async_client, httpx_mock: HTTPXMock):
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/persons?term=jane%40acme.com",
        json={"persons": [
            {"id": 20, "first_name": "Jane", "last_name": "Doe", "emails": ["jane@acme.com"]},
        ]},
    )
    result = await _find_person_by_email(async_client, "jane@acme.com")
    assert result["id"] == 20


async def test_find_person_by_email_case_insensitive(async_client, httpx_mock: HTTPXMock):
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/persons?term=Jane%40Acme.com",
        json={"persons": [
            {"id": 20, "first_name": "Jane", "emails": ["jane@acme.com"]},
        ]},
    )
    result = await _find_person_by_email(async_client, "Jane@Acme.com")
    assert result["id"] == 20


async def test_find_person_by_email_not_found(async_client, httpx_mock: HTTPXMock):
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/persons?term=nobody%40acme.com",
        json={"persons": []},
    )
    result = await _find_person_by_email(async_client, "nobody@acme.com")
    assert result is None


async def test_find_person_by_email_no_email_match(async_client, httpx_mock: HTTPXMock):
    # Search returns results but none have the requested email
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/persons?term=jane%40acme.com",
        json={"persons": [
            {"id": 20, "first_name": "Jane", "emails": ["jane@other.com"]},
        ]},
    )
    result = await _find_person_by_email(async_client, "jane@acme.com")
    assert result is None


# ---------------------------------------------------------------------------
# _create_person
# ---------------------------------------------------------------------------

async def test_create_person_full_name_and_email(async_client, httpx_mock: HTTPXMock):
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/persons",
        method="POST",
        json={"id": 30, "first_name": "Jane", "last_name": "Doe"},
    )
    result = await _create_person(async_client, "Jane Doe", "jane@acme.com", org_id=99)
    assert result["id"] == 30
    import json
    body = json.loads(httpx_mock.get_requests()[0].content)
    assert body["first_name"] == "Jane"
    assert body["last_name"] == "Doe"
    assert body["emails"] == ["jane@acme.com"]
    assert body["organization_ids"] == [99]


async def test_create_person_single_name(async_client, httpx_mock: HTTPXMock):
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/persons",
        method="POST",
        json={"id": 31, "first_name": "Jane", "last_name": ""},
    )
    await _create_person(async_client, "Jane", "", org_id=99)
    import json
    body = json.loads(httpx_mock.get_requests()[0].content)
    assert body["first_name"] == "Jane"
    assert body["last_name"] == ""
    assert "emails" not in body


async def test_create_person_no_email_omits_field(async_client, httpx_mock: HTTPXMock):
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/persons",
        method="POST",
        json={"id": 32, "first_name": "Bob", "last_name": "Smith"},
    )
    await _create_person(async_client, "Bob Smith", "", org_id=5)
    import json
    body = json.loads(httpx_mock.get_requests()[0].content)
    assert "emails" not in body


# ---------------------------------------------------------------------------
# _create_opportunity
# ---------------------------------------------------------------------------

async def test_create_opportunity_with_person(async_client, httpx_mock: HTTPXMock):
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/opportunities",
        method="POST",
        json={"id": 200, "name": "Acme Corp", "list_id": 42},
    )
    result = await _create_opportunity(async_client, "Acme Corp", list_id=42, org_id=99, person_ids=[20])
    assert result["id"] == 200
    import json
    body = json.loads(httpx_mock.get_requests()[0].content)
    assert body == {"name": "Acme Corp", "list_id": 42, "organization_id": 99, "person_ids": [20]}


async def test_create_opportunity_without_person(async_client, httpx_mock: HTTPXMock):
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/opportunities",
        method="POST",
        json={"id": 201, "name": "Acme Corp", "list_id": 42},
    )
    await _create_opportunity(async_client, "Acme Corp", list_id=42, org_id=99)
    import json
    body = json.loads(httpx_mock.get_requests()[0].content)
    assert "person_ids" not in body


# ---------------------------------------------------------------------------
# _create_note
# ---------------------------------------------------------------------------

async def test_create_note_with_person(async_client, httpx_mock: HTTPXMock):
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/notes",
        method="POST",
        json={"id": 77},
    )
    result = await _create_note(async_client, "Great lead from email", org_id=99, person_id=20)
    assert result["id"] == 77
    import json
    body = json.loads(httpx_mock.get_requests()[0].content)
    assert body["content"] == "Great lead from email"
    assert body["organization_ids"] == [99]
    assert body["person_ids"] == [20]


async def test_create_note_without_person(async_client, httpx_mock: HTTPXMock):
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/notes",
        method="POST",
        json={"id": 78},
    )
    await _create_note(async_client, "Note content", org_id=99)
    import json
    body = json.loads(httpx_mock.get_requests()[0].content)
    assert "person_ids" not in body


# ---------------------------------------------------------------------------
# _search_opportunities_in_affinity / search_opportunities
# ---------------------------------------------------------------------------

async def test_search_opportunities_no_list_filter(async_client, httpx_mock: HTTPXMock):
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/opportunities?term=Atlas&page_size=500",
        json={"opportunities": [{"id": 10, "name": "Project Atlas", "organization_id": 99}]},
    )
    results = await _search_opportunities_in_affinity(async_client, "Atlas", list_id=None)
    assert len(results) == 1
    assert results[0]["name"] == "Project Atlas"


async def test_search_opportunities_no_results(async_client, httpx_mock: HTTPXMock):
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/opportunities?term=Unknown&page_size=500",
        json={"opportunities": []},
    )
    results = await _search_opportunities_in_affinity(async_client, "Unknown", list_id=None)
    assert results == []


async def test_search_opportunities_filters_by_list_id(async_client, httpx_mock: HTTPXMock):
    # Two results from global search; only one is in list 339920
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/opportunities?term=Acme&page_size=500",
        json={"opportunities": [
            {"id": 10, "name": "Acme Deal", "organization_id": 99},
            {"id": 20, "name": "Acme Other", "organization_id": 88},
        ]},
    )
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/opportunities/10",
        json={"id": 10, "name": "Acme Deal", "organization_id": 99,
              "list_entries": [{"id": 1, "list_id": 339920}]},
    )
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/opportunities/20",
        json={"id": 20, "name": "Acme Other", "organization_id": 88,
              "list_entries": [{"id": 2, "list_id": 999}]},
    )
    results = await _search_opportunities_in_affinity(async_client, "Acme", list_id=339920)
    assert len(results) == 1
    assert results[0]["id"] == 10


async def test_search_opportunities_list_filter_no_match(async_client, httpx_mock: HTTPXMock):
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/opportunities?term=Atlas&page_size=500",
        json={"opportunities": [{"id": 10, "name": "Project Atlas", "organization_id": 99}]},
    )
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/opportunities/10",
        json={"id": 10, "name": "Project Atlas", "organization_id": 99,
              "list_entries": [{"id": 1, "list_id": 999}]},
    )
    results = await _search_opportunities_in_affinity(async_client, "Atlas", list_id=339920)
    assert results == []


async def test_search_opportunities_tool_formats_output(httpx_mock: HTTPXMock):
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/opportunities?term=Atlas&page_size=500",
        json={"opportunities": [{"id": 10, "name": "Project Atlas", "organization_id": 99}]},
    )
    result = await search_opportunities(term="Atlas")
    assert "Project Atlas" in result
    assert "id=10" in result


async def test_search_opportunities_tool_no_results_message(httpx_mock: HTTPXMock):
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/opportunities?term=XYZ&page_size=500",
        json={"opportunities": []},
    )
    result = await search_opportunities(term="XYZ", list_id=339920)
    assert "No opportunities found" in result
    assert "339920" in result


# ---------------------------------------------------------------------------
# create_deal (integration-style, all HTTP mocked)
# ---------------------------------------------------------------------------

def _mock_list_entries(httpx_mock: HTTPXMock, list_id: int, list_type: int, orgs: list[dict]):
    """Mock GET /lists/{list_id}/list-entries and the subsequent org fetches."""
    if list_type == LIST_TYPE_OPPORTUNITY:
        entries = [{"entity_id": o["id"], "entity": {"organization_id": o["id"]}} for o in orgs]
    else:
        entries = [{"entity_id": o["id"], "entity": {"id": o["id"]}} for o in orgs]
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/lists/{list_id}/list-entries?page_size=500",
        json=entries,
    )
    for o in orgs:
        httpx_mock.add_response(url=f"{AFFINITY_BASE_URL}/organizations/{o['id']}", json=o)


def _mock_deal_requests(httpx_mock: HTTPXMock, *, existing_org=False, existing_person=False, list_type=1):
    """Register all Affinity HTTP responses for a create_deal call."""
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/lists",
        json=[{"id": 42, "name": "ALL DEALS ECS", "type": list_type}],
    )
    if existing_org:
        _mock_list_entries(httpx_mock, 42, list_type, [{"id": 99, "name": "Acme Corp"}])
    else:
        _mock_list_entries(httpx_mock, 42, list_type, [])
        httpx_mock.add_response(
            url=f"{AFFINITY_BASE_URL}/organizations",
            method="POST",
            json={"id": 99, "name": "Acme Corp", "domain": "acme.com"},
        )
    if list_type == LIST_TYPE_OPPORTUNITY:
        httpx_mock.add_response(
            url=f"{AFFINITY_BASE_URL}/opportunities",
            method="POST",
            json={"id": 500, "name": "Acme Corp", "list_id": 42},
        )
    else:
        httpx_mock.add_response(
            url=f"{AFFINITY_BASE_URL}/lists/42/list-entries",
            method="POST",
            json={"id": 500},
        )
    if existing_person:
        httpx_mock.add_response(
            url=f"{AFFINITY_BASE_URL}/persons?term=jane%40acme.com",
            json={"persons": [{"id": 20, "first_name": "Jane", "last_name": "Doe", "emails": ["jane@acme.com"]}]},
        )
    else:
        httpx_mock.add_response(
            url=f"{AFFINITY_BASE_URL}/persons?term=jane%40acme.com",
            json={"persons": []},
        )
        httpx_mock.add_response(
            url=f"{AFFINITY_BASE_URL}/persons",
            method="POST",
            json={"id": 30, "first_name": "Jane", "last_name": "Doe"},
        )
    httpx_mock.add_response(
        url=f"{AFFINITY_BASE_URL}/notes",
        method="POST",
        json={"id": 77},
    )


async def test_create_deal_minimal(httpx_mock: HTTPXMock):
    httpx_mock.add_response(url=f"{AFFINITY_BASE_URL}/lists", json=[{"id": 42, "name": "ALL DEALS ECS"}])
    _mock_list_entries(httpx_mock, 42, 0, [])
    httpx_mock.add_response(url=f"{AFFINITY_BASE_URL}/organizations", method="POST", json={"id": 99, "name": "Acme Corp", "domain": "acmecorp.com"})
    httpx_mock.add_response(url=f"{AFFINITY_BASE_URL}/lists/42/list-entries", method="POST", json={"id": 500})

    result = await create_deal(list_name="ALL DEALS ECS", organization_name="Acme Corp")

    assert "Successfully added" in result
    assert "Acme Corp" in result
    assert "ALL DEALS ECS" in result
    assert "created new organization" in result


async def test_create_deal_existing_org_reused(httpx_mock: HTTPXMock):
    httpx_mock.add_response(url=f"{AFFINITY_BASE_URL}/lists", json=[{"id": 42, "name": "ALL DEALS ECS"}])
    _mock_list_entries(httpx_mock, 42, 0, [{"id": 99, "name": "Acme Corp"}])
    httpx_mock.add_response(url=f"{AFFINITY_BASE_URL}/lists/42/list-entries", method="POST", json={"id": 500})

    result = await create_deal(list_name="ALL DEALS ECS", organization_name="Acme Corp")

    assert "found existing organization" in result
    post_org_calls = [r for r in httpx_mock.get_requests() if r.method == "POST" and "/organizations" in str(r.url)]
    assert len(post_org_calls) == 0


async def test_create_deal_list_not_found(httpx_mock: HTTPXMock):
    httpx_mock.add_response(url=f"{AFFINITY_BASE_URL}/lists", json=[{"id": 1, "name": "Other List"}])

    result = await create_deal(list_name="ALL DEALS ECS", organization_name="Acme Corp")

    assert "Error" in result
    assert "ALL DEALS ECS" in result


async def test_create_deal_full_details_new_everything(httpx_mock: HTTPXMock):
    _mock_deal_requests(httpx_mock, existing_org=False, existing_person=False)

    result = await create_deal(
        list_name="ALL DEALS ECS",
        organization_name="Acme Corp",
        domain="acme.com",
        contact_name="Jane Doe",
        contact_email="jane@acme.com",
        note="Met at conference, interested in our platform",
    )

    assert "Successfully added" in result
    assert "created new organization" in result
    assert "created new contact 'Jane Doe'" in result
    assert "note added" in result


async def test_create_deal_existing_contact_reused(httpx_mock: HTTPXMock):
    _mock_deal_requests(httpx_mock, existing_org=False, existing_person=True)

    result = await create_deal(
        list_name="ALL DEALS ECS",
        organization_name="Acme Corp",
        contact_name="Jane Doe",
        contact_email="jane@acme.com",
        note="Follow-up opportunity",
    )

    assert "found existing contact" in result
    post_person_calls = [r for r in httpx_mock.get_requests() if r.method == "POST" and "/persons" in str(r.url)]
    assert len(post_person_calls) == 0


async def test_create_deal_note_truncated_in_status(httpx_mock: HTTPXMock):
    httpx_mock.add_response(url=f"{AFFINITY_BASE_URL}/lists", json=[{"id": 42, "name": "ALL DEALS ECS"}])
    _mock_list_entries(httpx_mock, 42, 0, [{"id": 99, "name": "Acme Corp"}])
    httpx_mock.add_response(url=f"{AFFINITY_BASE_URL}/lists/42/list-entries", method="POST", json={"id": 500})
    httpx_mock.add_response(url=f"{AFFINITY_BASE_URL}/notes", method="POST", json={"id": 77})

    long_note = "A" * 200
    result = await create_deal(list_name="ALL DEALS ECS", organization_name="Acme Corp", note=long_note)

    assert "..." in result
    import json
    note_request = next(r for r in httpx_mock.get_requests() if r.method == "POST" and "/notes" in str(r.url))
    assert json.loads(note_request.content)["content"] == long_note


async def test_create_deal_no_contact_skips_person_and_note(httpx_mock: HTTPXMock):
    httpx_mock.add_response(url=f"{AFFINITY_BASE_URL}/lists", json=[{"id": 42, "name": "ALL DEALS ECS"}])
    _mock_list_entries(httpx_mock, 42, 0, [])
    httpx_mock.add_response(url=f"{AFFINITY_BASE_URL}/organizations", method="POST", json={"id": 99, "name": "Acme Corp", "domain": "acmecorp.com"})
    httpx_mock.add_response(url=f"{AFFINITY_BASE_URL}/lists/42/list-entries", method="POST", json={"id": 500})

    result = await create_deal(list_name="ALL DEALS ECS", organization_name="Acme Corp")

    assert "Contact" not in result
    assert "Note" not in result
    person_calls = [r for r in httpx_mock.get_requests() if "/persons" in str(r.url)]
    note_calls = [r for r in httpx_mock.get_requests() if "/notes" in str(r.url)]
    assert len(person_calls) == 0
    assert len(note_calls) == 0


async def test_create_deal_near_match_org_reused(httpx_mock: HTTPXMock):
    # "Mertz-Taggart" in list, searching for "Mertz Taggart" → reuse, no new org
    httpx_mock.add_response(url=f"{AFFINITY_BASE_URL}/lists", json=[{"id": 42, "name": "ALL DEALS ECS"}])
    _mock_list_entries(httpx_mock, 42, 0, [{"id": 99, "name": "Mertz-Taggart"}])
    httpx_mock.add_response(url=f"{AFFINITY_BASE_URL}/lists/42/list-entries", method="POST", json={"id": 500})

    result = await create_deal(list_name="ALL DEALS ECS", organization_name="Mertz Taggart")

    assert "found existing organization" in result
    post_org_calls = [r for r in httpx_mock.get_requests() if r.method == "POST" and "/organizations" in str(r.url)]
    assert len(post_org_calls) == 0


async def test_create_deal_missing_api_key_raises(monkeypatch):
    monkeypatch.delenv("AFFINITY_API_KEY", raising=False)
    with pytest.raises(ValueError, match="AFFINITY_API_KEY"):
        await create_deal(list_name="ALL DEALS ECS", organization_name="Acme Corp")


# ---------------------------------------------------------------------------
# create_deal with opportunity-type list (type=8)
# ---------------------------------------------------------------------------

async def test_create_deal_opportunity_list_uses_opportunities_endpoint(httpx_mock: HTTPXMock):
    _mock_deal_requests(httpx_mock, existing_org=False, existing_person=False, list_type=LIST_TYPE_OPPORTUNITY)

    result = await create_deal(
        list_name="ALL DEALS ECS",
        organization_name="Acme Corp",
        contact_name="Jane Doe",
        contact_email="jane@acme.com",
        note="Great lead",
    )

    assert "Successfully added" in result
    opp_calls = [r for r in httpx_mock.get_requests() if r.method == "POST" and "/opportunities" in str(r.url)]
    assert len(opp_calls) == 1
    entry_calls = [r for r in httpx_mock.get_requests() if r.method == "POST" and "/list-entries" in str(r.url)]
    assert len(entry_calls) == 0


async def test_create_deal_opportunity_list_includes_person_id(httpx_mock: HTTPXMock):
    httpx_mock.add_response(url=f"{AFFINITY_BASE_URL}/lists", json=[{"id": 42, "name": "ALL DEALS ECS", "type": LIST_TYPE_OPPORTUNITY}])
    _mock_list_entries(httpx_mock, 42, LIST_TYPE_OPPORTUNITY, [{"id": 99, "name": "Acme Corp"}])
    httpx_mock.add_response(url=f"{AFFINITY_BASE_URL}/persons?term=jane%40acme.com", json={"persons": []})
    httpx_mock.add_response(url=f"{AFFINITY_BASE_URL}/persons", method="POST", json={"id": 30, "first_name": "Jane", "last_name": "Doe"})
    httpx_mock.add_response(url=f"{AFFINITY_BASE_URL}/opportunities", method="POST", json={"id": 200, "name": "Acme Corp", "list_id": 42})

    await create_deal(
        list_name="ALL DEALS ECS",
        organization_name="Acme Corp",
        contact_name="Jane Doe",
        contact_email="jane@acme.com",
    )

    import json
    opp_request = next(r for r in httpx_mock.get_requests() if r.method == "POST" and "/opportunities" in str(r.url))
    body = json.loads(opp_request.content)
    assert "person_ids" in body
    assert body["organization_id"] == 99
    assert body["list_id"] == 42


async def test_create_deal_non_opportunity_list_uses_list_entries(httpx_mock: HTTPXMock):
    httpx_mock.add_response(url=f"{AFFINITY_BASE_URL}/lists", json=[{"id": 42, "name": "ALL DEALS ECS", "type": 1}])
    _mock_list_entries(httpx_mock, 42, 1, [])
    httpx_mock.add_response(url=f"{AFFINITY_BASE_URL}/organizations", method="POST", json={"id": 99, "name": "Acme Corp", "domain": "acmecorp.com"})
    httpx_mock.add_response(url=f"{AFFINITY_BASE_URL}/lists/42/list-entries", method="POST", json={"id": 500})

    await create_deal(list_name="ALL DEALS ECS", organization_name="Acme Corp")

    entry_calls = [r for r in httpx_mock.get_requests() if r.method == "POST" and "/list-entries" in str(r.url)]
    assert len(entry_calls) == 1
    opp_calls = [r for r in httpx_mock.get_requests() if "/opportunities" in str(r.url)]
    assert len(opp_calls) == 0
