from src.domain.codegen.provision_manifest import ProvisionField
from src.domain.projects.entity_requirement_sync import looks_entity_field_related
from src.domain.projects.entity_requirements import (
    EntityFieldRequirement,
    EntityFieldRequirementDelta,
    apply_entity_field_requirement_delta,
    resolved_entity_fields_config,
)


def test_apply_delta_adds_new_field_preserving_order():
    existing = (
        EntityFieldRequirement(
            entity_key="lead",
            fields=(ProvisionField(key="name", label="Name", type="text", required=True),),
        ),
    )
    delta = EntityFieldRequirementDelta(
        entity_key="lead",
        add_fields=(ProvisionField(key="status", label="Status", type="text"),),
    )
    merged = apply_entity_field_requirement_delta(existing, delta)
    assert [f.key for f in merged[0].fields] == ["name", "status"]


def test_apply_delta_update_replaces_in_place():
    existing = (
        EntityFieldRequirement(
            entity_key="lead",
            fields=(
                ProvisionField(key="name", label="Name", type="text", required=True),
                ProvisionField(key="status", label="Status", type="text", required=False),
            ),
        ),
    )
    delta = EntityFieldRequirementDelta(
        entity_key="lead",
        update_fields=(ProvisionField(key="status", label="Status", type="text", required=True),),
    )
    merged = apply_entity_field_requirement_delta(existing, delta)
    fields = merged[0].fields
    assert [f.key for f in fields] == ["name", "status"]
    assert fields[1].required is True


def test_apply_delta_remove_wins_over_same_delta_add():
    existing = (EntityFieldRequirement(entity_key="lead", fields=()),)
    delta = EntityFieldRequirementDelta(
        entity_key="lead",
        add_fields=(ProvisionField(key="status", label="Status", type="text"),),
        remove_fields=("status",),
    )
    merged = apply_entity_field_requirement_delta(existing, delta)
    assert merged[0].fields == ()


def test_apply_delta_does_not_touch_other_entities():
    existing = (
        EntityFieldRequirement(
            entity_key="lead",
            fields=(ProvisionField(key="status", label="Status", type="text"),),
        ),
        EntityFieldRequirement(
            entity_key="order",
            fields=(ProvisionField(key="total", label="Total", type="number"),),
        ),
    )
    delta = EntityFieldRequirementDelta(
        entity_key="order",
        add_fields=(ProvisionField(key="currency", label="Currency", type="text"),),
    )
    merged = apply_entity_field_requirement_delta(existing, delta)
    by_key = {r.entity_key: r for r in merged}
    assert [f.key for f in by_key["lead"].fields] == ["status"]
    assert [f.key for f in by_key["order"].fields] == ["total", "currency"]


def test_select_field_requires_options():
    import pytest

    with pytest.raises(Exception):
        ProvisionField(key="budget", label="Budget", type="select")


def test_resolved_entity_fields_config_omits_empty_requirement():
    requirements = [
        EntityFieldRequirement(entity_key="lead", fields=()),
        EntityFieldRequirement(
            entity_key="order",
            fields=(ProvisionField(key="total", label="Total", type="number"),),
        ),
    ]
    config = resolved_entity_fields_config(requirements)
    assert "lead" not in config
    assert config["order"][0]["key"] == "total"


def test_looks_entity_field_related_generic_hints():
    assert looks_entity_field_related("Add a status field to the lead entity", frozenset())
    assert looks_entity_field_related("Add a currency column to orders", frozenset())
    assert not looks_entity_field_related("Make the hero banner blue", frozenset())
    assert not looks_entity_field_related("", frozenset())


def test_looks_entity_field_related_known_entity_key():
    assert looks_entity_field_related("drop the extra column on lead", frozenset({"lead"}))


def test_extract_delta_passes_discovery_entity_names_to_extractor():
    """Regression: a project with no Core-resolved entity fields yet (every
    project's day-one state for every entity) must still pass Discovery's
    conceptual entity list to the extractor. Reproduces a live incident where
    an empty `known_entities`/no grounding at all made the extractor return
    applies=false for an unambiguous 'add a field to the lead entity' prompt."""
    import asyncio
    from unittest.mock import patch

    from src.domain.projects.entity_requirement_sync import extract_entity_delta_for_prompt
    from src.domain.projects.entity_requirements import EntityFieldRequirementExtractionResult

    captured = {}

    async def fake_extract(payload, settings, *, trace=None):
        captured.update(payload)
        return EntityFieldRequirementExtractionResult(applies=False)

    with patch(
        "src.integrations.project_intake_client.extract_entity_field_requirement_delta",
        new=fake_extract,
    ):
        asyncio.run(
            extract_entity_delta_for_prompt(
                "Add a status field to the lead entity",
                (),
                object(),
                entity_names=(("lead", "Lead"),),
            )
        )
    assert captured["known_entity_names"] == [{"id": "lead", "name": "Lead"}]
