"""The app model: one document of an app's entities (datasources, forms,
queries in one field vocabulary), the relations between them, the views
and logic that use them, security, and references that no longer resolve."""

import uuid

from app.crud import field_indexes


async def _setup(client, headers):
    customers = (await client.post("/masters", headers=headers, json={"name": "Customers", "field_schema": [
        {"name": "name", "type": "text", "required": True},
        {"name": "credit", "type": "numeric", "indexed": True},
    ]})).json()
    regions = (await client.post("/masters", headers=headers, json={"name": "Regions", "field_schema": [{"name": "name", "type": "text"}]})).json()
    # Customers links to Regions — added after both exist.
    await client.patch(f"/masters/{customers['id']}", headers=headers, json={"field_schema": [
        {"name": "name", "type": "text", "required": True},
        {"name": "credit", "type": "numeric", "indexed": True},
        {"name": "region", "type": "reference", "reference_master_id": regions["id"], "reference_display_field": "name"},
    ]})
    order = (await client.post("/forms", headers=headers, json={"name": "Order", "field_definitions": [
        {"name": "details", "type": "section", "label": "Details"},
        {"name": "customer", "type": "dropdown", "label": "Customer", "master_id": customers["id"], "master_display_field": "name", "required": True},
        {"name": "qty", "type": "number", "label": "Quantity"},
        {"name": "notes", "type": "textarea", "label": "Notes"},
        {"name": "go", "type": "button", "label": "Go", "button_kind": "action_only", "button_action": {"action_type": "show_message", "message": "Hi"}},
    ]}))
    assert order.status_code == 201, order.text
    order = order.json()
    page = (await client.post("/pages", headers=headers, json={"name": "Orders board", "blocks": [
        {"id": "b1", "type": "data_list", "data_source": f"form:{order['form_id']}", "columns": ["customer", "qty"]},
        {"id": "b2", "type": "data_list", "data_source": f"master:{uuid.uuid4()}"},
    ]})).json()
    task = (await client.post("/tasks", headers=headers, json={"name": "Credit check", "nodes": [
        {"key": "start", "type": "start", "label": "Start", "next_nodes": [{"to": "get"}]},
        {"key": "get", "type": "data_get", "label": "Get customer", "datasource_type": "master", "master_id": customers["id"], "match_field": "name"},
    ]})).json()
    return customers, regions, order, page, task


async def test_tenant_model_unifies_entities_views_and_logic(client, auth_headers):
    customers, regions, order, page, task = await _setup(client, auth_headers)
    model = (await client.get("/app-model", headers=auth_headers)).json()

    entities = {e["name"]: e for e in model["entities"]}
    assert entities["Customers"]["kind"] == "datasource" and entities["Order"]["kind"] == "form"
    # One vocabulary: a datasource's numeric and a form's number are both "number";
    # a form's choice backed by a datasource is a reference to it.
    assert [(f["name"], f["type"]) for f in entities["Customers"]["fields"]] == [("name", "text"), ("credit", "number"), ("region", "reference")]
    assert [(f["name"], f["type"]) for f in entities["Order"]["fields"]] == [("customer", "reference"), ("qty", "number"), ("notes", "long_text")]
    assert entities["Order"]["fields"][0]["reference"]["entity_id"] == customers["id"]

    rels = {(r["from"], r["field"], r["to"]) for r in model["relations"]}
    assert (customers["id"], "region", regions["id"]) in rels
    assert (order["form_id"], "customer", customers["id"]) in rels

    views = {v["name"]: v for v in model["views"]}
    assert views["Order"]["entity_ids"][0] == order["form_id"]  # a form is a view of its own entity
    assert views["Orders board"]["entity_ids"] == [order["form_id"]]
    logic = {g["name"]: g for g in model["logic"]}
    assert logic["Credit check"]["entity_ids"] == [customers["id"]]
    assert {u["name"] for u in entities["Customers"]["used_by"]} == {"Order", "Credit check"}

    # The page's list over a datasource that's gone is reported.
    assert [i["item_name"] for i in model["issues"]] == ["Orders board"]
    assert model["summary"]["entities"] >= 3 and len(model["version"]) == 12


async def test_version_follows_changes(client, auth_headers):
    customers, *_ = await _setup(client, auth_headers)
    first = (await client.get("/app-model", headers=auth_headers)).json()["version"]
    assert (await client.get("/app-model", headers=auth_headers)).json()["version"] == first
    await client.patch(f"/masters/{customers['id']}", headers=auth_headers, json={"name": "Clients"})
    assert (await client.get("/app-model", headers=auth_headers)).json()["version"] != first


async def test_app_model_takes_the_app_and_what_it_needs(client, auth_headers):
    customers, regions, order, page, task = await _setup(client, auth_headers)
    app = (await client.post("/apps", headers=auth_headers, json={"name": "Sales", "items": [{"type": "page", "id": page["id"]}, {"type": "form", "id": order["form_id"]}]})).json()
    model = (await client.get(f"/apps/{app['id']}/model", headers=auth_headers)).json()
    assert model["scope"] == {"kind": "app", "id": app["id"], "name": "Sales"}
    assert {v["name"] for v in model["views"]} == {"Order", "Orders board"}
    assert model["logic"] == []
    # Customers and Regions come along (the form links to one, it to the other) — outside the app.
    in_app = {e["name"]: e["in_app"] for e in model["entities"]}
    assert in_app == {"Order": True, "Customers": False, "Regions": False}


async def test_storage_tier_shows_in_the_model(client, auth_headers):
    customers, regions, *_ = await _setup(client, auth_headers)
    await field_indexes.wait_for_pending()
    assert (await client.post(f"/masters/{regions['id']}/storage", headers=auth_headers, json={"tier": "table"})).status_code == 200
    assert (await client.post(f"/masters/{customers['id']}/storage", headers=auth_headers, json={"tier": "table"})).status_code == 200
    model = (await client.get("/app-model", headers=auth_headers)).json()
    entities = {e["name"]: e for e in model["entities"]}
    assert entities["Customers"]["storage"]["tier"] == "table"
    assert entities["Customers"]["storage"]["table"].startswith("entity_data.customers__")
    [rel] = [r for r in model["relations"] if r["field"] == "region"]
    assert rel["enforced"] is True
    assert model["summary"]["tables"] == 2
