from src.domain.app_blocks.canonical import CANONICAL_BLOCK_IDS
from src.domain.app_blocks.catalogs import default_known_packs, default_primitives
from src.domain.app_blocks.compiler import compile_install_plan
from src.domain.app_blocks.registry import load_default_registry
from src.domain.app_blocks.resolver import (
    OPTIONAL_REQUIRES_CONSUMPTION,
    observe_optional_requires,
    resolve_block,
)
from src.domain.app_blocks.schemas import DependencyRef, PlannedBlock
from src.domain.projects.discovery_schemas import (
    Actor,
    AuthenticationSpec,
    DiscoverySpec,
    Entity,
    ProductDefinition,
    TenancySpec,
    Workflow,
)


def _contact_spec() -> DiscoverySpec:
    return DiscoverySpec(
        product=ProductDefinition(
            purpose="Allow prospective customers to contact the agency.",
            first_build_goal="Capture website enquiries and let staff review them.",
        ),
        archetypes=["marketing_site"],
        actors=[
            Actor(id="visitor", name="Website visitor"),
            Actor(id="staff", name="Staff"),
        ],
        workflows=[
            Workflow(
                id="submit_enquiry",
                name="Submit enquiry",
                description="Visitor sends a message",
            )
        ],
        entities=[Entity(id="enquiry", name="Enquiry", description="A website enquiry")],
        tenancy=TenancySpec(model="single_workspace"),
        authentication=AuthenticationSpec(required=False),
    )


def _contact_plan(**kwargs):
    return compile_install_plan(_contact_spec(), **kwargs)


def _contact_block(plan):
    return next(block for block in plan.blocks if block.id == "forms.contact")


def _optional_map(block):
    return {item.id: item for item in block.optional_requirements}


def test_optional_requires_consumption_flags():
    assert OPTIONAL_REQUIRES_CONSUMPTION["status"] == "complete"
    assert OPTIONAL_REQUIRES_CONSUMPTION["planner_optional_auto_install"] is False
    assert OPTIONAL_REQUIRES_CONSUMPTION["plan_json_includes_optional_requirements"] is False


def test_required_missing_makes_plan_unsatisfiable():
    primitives = default_primitives() - {"data.entity"}
    plan = _contact_plan(primitives=primitives)
    assert plan.unsatisfiable
    assert any(
        gap.type == "primitive_missing" and gap.id == "data.entity" for gap in plan.gaps
    )


def test_required_present_keeps_plan_satisfiable():
    plan = _contact_plan()
    assert not any(gap.type == "primitive_missing" for gap in plan.gaps)
    assert "form.public_create" in plan.required_primitives
    assert "contact-capture" in plan.required_feature_packs


def test_optional_missing_does_not_fail_the_plan():
    primitives = default_primitives() - {"email.send"}
    plan = _contact_plan(primitives=primitives)
    assert not any(
        gap.type == "primitive_missing" and gap.id == "email.send" for gap in plan.gaps
    )
    contact = _contact_block(plan)
    assert _optional_map(contact)["email.send"].status == "unavailable"
    assert "email.send" not in plan.required_primitives
    assert "contact-capture" in plan.required_feature_packs


def test_optional_present_is_recognized():
    plan = _contact_plan()
    contact = _contact_block(plan)
    assert _optional_map(contact)["email.send"].status == "satisfied"
    assert "email.send" not in plan.required_primitives


def test_partial_optional_satisfaction():
    primitives = default_primitives() | {"email.send"}
    assert "crm.contact.upsert" not in primitives
    plan = _contact_plan(primitives=primitives)
    statuses = _optional_map(_contact_block(plan))
    assert statuses["email.send"].status == "satisfied"
    assert statuses["crm.contact.upsert"].status == "unavailable"
    assert all(
        gap.id != "crm.contact.upsert" or gap.severity != "build_blocking"
        for gap in plan.gaps
    )


def test_email_transactional_alias_normalizes_and_dedupes():
    registry = load_default_registry()
    contact = registry.require("forms.contact")
    duplicate = contact.model_copy(
        update={
            "optional_requires": [
                *contact.optional_requires,
                DependencyRef(kind="primitive", id="email.transactional"),
            ]
        }
    )
    observed = observe_optional_requires(
        duplicate,
        registry,
        primitives=default_primitives(),
        known_packs=default_known_packs(),
    )
    email = [item for item in observed if item.id == "email.send"]
    assert len(email) == 1
    assert email[0].status == "satisfied"
    assert email[0].kind == "primitive"


def test_optional_does_not_force_pack_install_or_change_identities():
    without_email = _contact_plan(primitives=default_primitives() - {"email.send"})
    with_email = _contact_plan()
    assert without_email.required_feature_packs == with_email.required_feature_packs
    contact = _contact_block(with_email)
    assert contact.id == "forms.contact"
    runtime = load_default_registry().canonical.require("forms.contact").runtime
    assert runtime.identity == "contact"
    assert runtime.identity != contact.id
    dumped = contact.model_dump()
    assert "optional_requirements" not in dumped
    assert "optional_requirements" not in PlannedBlock(
        id="forms.contact", version="1", reason="x"
    ).model_dump()


def test_crm_pipeline_is_a_canonical_block_and_contact_capture_owners_stay_forms():
    registry = load_default_registry()
    assert registry.require("crm.pipeline").id == "crm.pipeline"
    assert registry.require("crm.customer").id == "crm.customer"
    assert registry.canonical.ids == CANONICAL_BLOCK_IDS
    owners = registry.canonical.blocks_for_feature_pack("contact-capture")
    assert set(owners) == {"forms.contact", "forms.quote_request"}


def test_optional_app_block_is_not_nested_installed_from_optional_list():
    registry = load_default_registry()
    contact = registry.require("forms.contact")
    packs, primitives, nested, gaps, _configs, optionals = resolve_block(
        contact,
        registry,
        primitives=default_primitives(),
        known_packs=default_known_packs(),
    )
    assert "admin.record_table" not in nested
    assert "contact-capture" in packs
    by_id = {item.id: item for item in optionals}
    assert by_id["admin.record_table"].status == "satisfied"
    assert not any(
        gap.type == "block_missing" and gap.id == "admin.record_table" for gap in gaps
    )
    assert "email.send" not in primitives
