from src.domain.app_blocks.compiler import compile_install_plan
from src.domain.app_blocks.errors import InvalidAppBlockManifestError, UnknownAppBlockError
from src.domain.app_blocks.registry import AppBlockRegistry, load_default_registry, load_manifests
from src.domain.app_blocks.validation import validate_manifest
from src.domain.projects.discovery_schemas import (
    Actor,
    AuthenticationSpec,
    DiscoverySpec,
    Entity,
    ProductDefinition,
    TenancySpec,
    Workflow,
)


def _spec(**overrides) -> DiscoverySpec:
    data = {
        "product": ProductDefinition(
            name="Agency Site",
            name_status="working",
            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",
            ),
            Workflow(
                id="review_enquiry",
                name="Review enquiry",
                description="Staff review submissions",
            ),
        ],
        "entities": [Entity(id="enquiry", name="Enquiry", description="A website enquiry")],
        "tenancy": TenancySpec(model="single_workspace"),
        "authentication": AuthenticationSpec(required=False),
    }
    data.update(overrides)
    return DiscoverySpec(**data)


def test_default_registry_loads_implemented_contact_block():
    registry = load_default_registry()
    contact = registry.require("forms.contact")
    assert contact.implemented
    assert registry.providers_for("public_contact_submission")[0].id == "forms.contact"
    assert registry.list_family("forms")
    assert registry.require("crm.lead").implemented


def test_manifest_requires_provides():
    try:
        validate_manifest(
            {
                "id": "x.bad",
                "version": "1",
                "family": "forms",
                "title": "Bad",
                "description": "Missing provides",
            }
        )
        raise AssertionError("expected InvalidAppBlockManifestError")
    except InvalidAppBlockManifestError:
        pass


def test_unknown_block_raises():
    registry = AppBlockRegistry([])
    try:
        registry.require("does.not.exist")
        raise AssertionError("expected UnknownAppBlockError")
    except UnknownAppBlockError:
        pass


def test_contact_plan_uses_existing_contact_capture_pack():
    plan = compile_install_plan(_spec())
    ids = [block.id for block in plan.blocks]
    assert "forms.contact" in ids
    assert "admin.shell" in ids
    assert "admin.record_table" in ids
    assert "contact-capture" in plan.required_feature_packs
    assert plan.adoption == "advisory"
    assert "form.public_create" in plan.required_primitives
    contact = next(block for block in plan.blocks if block.id == "forms.contact")
    assert "persistence" in contact.protected_bindings
    assert contact.slots["route"] == "/contact"
    assert "form.standard" in plan.starter_contracts
    assert "record.table" in plan.starter_contracts
    assert "layout.admin" in plan.starter_contracts
    assert all(gap.type != "primitive_missing" for gap in plan.gaps)
    email_gaps = [gap for gap in plan.gaps if gap.id == "email.send"]
    assert email_gaps
    assert all(gap.severity != "build_blocking" for gap in email_gaps)
    crm_gaps = [gap for gap in plan.gaps if gap.id == "crm.contact.upsert"]
    assert crm_gaps
    assert all(gap.severity == "optional" for gap in crm_gaps)
    assert any(item.capability == "crm.contact.upsert" for item in plan.configuration_requirements)


def test_quote_request_roofing_prompt_does_not_also_select_contact():
    spec = _spec(
        product=ProductDefinition(
            purpose="Website for a roofing company where visitors request a quote.",
            first_build_goal="Capture quote requests and let staff mark them New to Lost.",
        ),
        workflows=[
            Workflow(
                id="request_quote",
                name="Request a quote",
                description="Visitor requests a quote",
            ),
            Workflow(
                id="review_quote",
                name="Review quote requests",
                description="Staff review quotes",
            ),
        ],
        entities=[Entity(id="quote", name="Quote request", description="A roofing quote")],
    )
    plan = compile_install_plan(spec)
    ids = [block.id for block in plan.blocks]
    assert "forms.quote_request" in ids
    assert "forms.contact" not in ids
    assert "workflow.transition" in plan.required_primitives
    assert "quote_request_submission" in plan.requested_capabilities


def test_no_show_calculator_is_not_treated_as_negated_calculator():
    spec = _spec(
        product=ProductDefinition(
            purpose="No-show impact calculator",
            first_build_goal="Estimate lost revenue from missed appointments.",
        ),
        actors=[Actor(id="staff", name="Staff")],
        workflows=[
            Workflow(
                id="calculate",
                name="No-show impact",
                description="Calculate expected no-shows and lost revenue.",
            )
        ],
    )
    plan = compile_install_plan(spec)
    assert "calculation.calculator" in {block.id for block in plan.blocks}


def test_crm_lead_installs_lifecycle_pack():
    spec = _spec(
        product=ProductDefinition(
            purpose="Internal CRM for managing sales leads.",
            first_build_goal="Track leads through a pipeline.",
        ),
        archetypes=["crm"],
        actors=[Actor(id="agent", name="Sales agent")],
        workflows=[
            Workflow(
                id="pipeline",
                name="Lead pipeline",
                description="Move leads through stages",
            )
        ],
        entities=[Entity(id="lead", name="Lead", description="A sales lead")],
    )
    plan = compile_install_plan(spec)
    ids = [block.id for block in plan.blocks]
    assert "crm.lead" in ids
    assert "admin.shell" in ids
    assert "lead-lifecycle" in plan.required_feature_packs
    assert "workflow.transition" in plan.required_primitives
    assert not [gap for gap in plan.gaps if gap.id == "crm.lead"]


def test_lead_capture_contact_site_does_not_select_crm():
    spec = _spec(
        product=ProductDefinition(
            purpose="Marketing site with lead capture.",
            first_build_goal="Capture website enquiries.",
        ),
        archetypes=["marketing_site"],
    )
    plan = compile_install_plan(spec)
    ids = [block.id for block in plan.blocks]
    assert "forms.contact" in ids
    assert "crm.lead" not in ids
    assert not [gap for gap in plan.gaps if gap.id == "crm.lead"]


def test_case_management_installs_lifecycle_pack():
    spec = _spec(
        product=ProductDefinition(
            purpose="School office that tracks student enrollments.",
            first_build_goal="Track enrollment cases from open to resolved.",
        ),
        archetypes=["operations"],
        actors=[Actor(id="staff", name="Office staff")],
        workflows=[
            Workflow(
                id="enroll",
                name="Enrollment tracking",
                description="Staff move applications through review",
            )
        ],
        entities=[Entity(id="enrollment", name="Enrollment", description="A student application")],
    )
    plan = compile_install_plan(spec)
    ids = [block.id for block in plan.blocks]
    assert "case.management" in ids
    assert "admin.shell" in ids
    assert "case-lifecycle" in plan.required_feature_packs
    assert not [gap for gap in plan.gaps if gap.id == "case.management"]


def test_use_case_marketing_copy_does_not_select_case_management():
    spec = _spec(
        product=ProductDefinition(
            purpose="Marketing site with customer use case studies.",
            first_build_goal="Publish case studies on the website.",
        ),
        archetypes=["marketing_site"],
    )
    plan = compile_install_plan(spec)
    ids = [block.id for block in plan.blocks]
    assert "case.management" not in ids
    assert not [gap for gap in plan.gaps if gap.id == "case.management"]


def test_auth_blocks_when_authentication_required():
    spec = _spec(authentication=AuthenticationSpec(required=True))
    plan = compile_install_plan(spec)
    ids = [block.id for block in plan.blocks]
    assert "auth.sign_in" in ids
    assert "auth.profile" in ids


def test_load_manifests_are_valid():
    manifests = load_manifests()
    assert {item.id for item in manifests} >= {
        "forms.contact",
        "forms.quote_request",
        "admin.shell",
        "admin.record_table",
        "admin.record_detail",
        "auth.sign_in",
        "auth.profile",
        "crm.lead",
        "case.management",
    }


def test_unknown_starter_contract_on_slot_is_invalid():
    try:
        validate_manifest(
            {
                "id": "x.bad",
                "version": "1",
                "family": "forms",
                "title": "Bad",
                "description": "Unknown UI contract",
                "provides": [{"id": "x"}],
                "slots": [
                    {
                        "id": "form",
                        "type": "ui_slot",
                        "description": "Bad",
                        "component_contract": "ContactForm.vue",
                    }
                ],
            }
        )
        raise AssertionError("expected InvalidAppBlockManifestError")
    except InvalidAppBlockManifestError:
        pass


def test_host_map_lists_contact_public_form_and_preserves_implementation():
    from src.domain.app_blocks.hosts import host_map_payload, merge_host_map

    payload = host_map_payload()
    keys = {(item["block"], item["slot"]) for item in payload["hosts"]}
    assert ("forms.contact", "public_form") in keys
    assert ("admin.record_table", "table") in keys
    assert all(item["implementation"] is None for item in payload["hosts"])
    preserved = merge_host_map(
        {
            "hosts": [
                {
                    "block": "forms.contact",
                    "slot": "public_form",
                    "implementation": "components/starter/blocks/ContactPublicForm.vue",
                }
            ]
        },
        payload,
    )
    mapped = {
        (item["block"], item["slot"]): item["implementation"] for item in preserved["hosts"]
    }
    assert mapped[("forms.contact", "public_form")].endswith("ContactPublicForm.vue")
    assert mapped[("admin.shell", "layout")] is None
