"""Task variables (Task Designer › Variables): set before the first node,
from a fixed value/template, a value the caller passes, a session value or
the signed-in user — worked out on the server — and readable by every node;
a Set variable node changes them mid-run."""

import uuid

from app.models.identity import Tenant
from tests.conftest import AsyncSessionLocal


def _start(next_key: str | None = None) -> dict:
    return {"key": "start", "type": "start", "label": "Start", "next_nodes": [{"to": next_key}] if next_key else []}


async def _run(client, headers, variables, nodes, context):
    made = await client.post("/tasks", headers=headers, json={"name": f"Vars {uuid.uuid4().hex[:6]}", "nodes": nodes, "variables": variables})
    assert made.status_code == 201, made.text
    resp = await client.post(f"/tasks/{made.json()['id']}/run", headers=headers, json={"context": context, "allow_draft": True})
    assert resp.status_code == 200, resp.text
    return made.json(), resp.json()


async def test_variables_are_set_from_every_source_and_readable_by_later_nodes(client, auth_headers, test_tenant):
    async with AsyncSessionLocal() as db:
        async with db.begin():
            tenant = await db.get(Tenant, test_tenant["tenant_id"])
            tenant.login_session_settings = {"values": [{"key": "office_email", "source": "user", "user_field": "email"}]}
    variables = [
        {"name": "office", "type": "string", "source": "input", "value": "officeId"},
        {"name": "me", "type": "string", "source": "user", "value": "email"},
        {"name": "office_email", "type": "string", "source": "session", "value": "office_email"},
        {"name": "fee", "type": "number", "source": "value", "value": "250"},
        {"name": "label", "type": "string", "source": "value", "value": "{{office}}-{{recordId}}"},
    ]
    nodes = [
        _start("bump"),
        {
            "key": "bump", "type": "set_variable", "label": "Raise the fee",
            "input_params": [{"name": "fee", "type": "number", "value": "300"}, {"name": "status", "type": "string", "value": "ready for {{office}}"}],
        },
    ]
    task, result = await _run(client, auth_headers, variables, nodes, {"officeId": "ADR", "recordId": "R1", "current_user.email": "spoof@example.com"})
    assert [v["source"] for v in task["variables"]] == ["input", "user", "session", "value", "value"]
    assert result["ok"] is True, result
    ctx = result["context"]
    me = ctx["current_user.email"]
    assert me != "spoof@example.com"  # the caller can't claim to be someone else
    assert ctx["office"] == "ADR" and ctx["me"] == me and ctx["office_email"] == me
    assert ctx["label"] == "ADR-R1"
    assert ctx["fee"] == 300 and ctx["status"] == "ready for ADR"
    assert result["trace"][0]["detail"].startswith("variables: office = 'ADR'")


async def test_a_value_that_doesnt_fit_its_type_stops_the_run(client, auth_headers):
    _, result = await _run(client, auth_headers, [{"name": "fee", "type": "int", "source": "input", "value": "fee"}], [_start()], {"fee": "ten"})
    assert result["ok"] is False
    assert "variable 'fee' should be int" in result["trace"][0]["detail"]


async def test_variable_names_are_checked(client, auth_headers):
    for bad in (
        [{"name": "secret", "value": "x"}],
        [{"name": "a", "value": "1"}, {"name": "a", "value": "2"}],
        [{"name": "who", "source": "user", "value": "password"}],
        [{"name": "1st", "value": "x"}],
    ):
        resp = await client.post("/tasks", headers=auth_headers, json={"name": "Bad vars", "nodes": [], "variables": bad})
        assert resp.status_code == 422, bad


async def test_set_variable_saves_to_the_session_or_hands_values_back(client, auth_headers):
    nodes = [
        _start("task"),
        {"key": "task", "type": "set_variable", "label": "Task", "input_params": [{"name": "step", "type": "string", "value": "one"}], "next_nodes": [{"to": "sess"}]},
        {
            "key": "sess", "type": "set_variable", "label": "Session", "variable_scope": "session",
            "input_params": [{"name": "office_id", "type": "string", "value": "ADR-{{step}}"}], "next_nodes": [{"to": "ctx"}],
        },
        {
            "key": "ctx", "type": "set_variable", "label": "Context", "variable_scope": "context",
            "input_params": [{"name": "applicationNo", "type": "string", "value": "RTI/{{session.office_id}}"}, {"name": "fee", "type": "number", "value": "10"}],
        },
    ]
    _, result = await _run(client, auth_headers, [], nodes, {})
    assert result["ok"] is True, result
    assert result["session_data"] == {"office_id": "ADR-one"}
    assert result["params"] == {"applicationNo": "RTI/ADR-one", "fee": 10}
    # Every later node could read all of them by name.
    assert result["context"]["step"] == "one" and result["context"]["office_id"] == "ADR-one" and result["context"]["session.office_id"] == "ADR-one"


async def test_a_failed_run_hands_nothing_back(client, auth_headers):
    nodes = [
        _start("sess"),
        {"key": "sess", "type": "set_variable", "label": "Session", "variable_scope": "session", "input_params": [{"name": "x", "type": "string", "value": "1"}], "next_nodes": [{"to": "bad"}]},
        {"key": "bad", "type": "set_variable", "label": "Bad", "input_params": [{"name": "n", "type": "int", "value": "not a number"}]},
    ]
    _, result = await _run(client, auth_headers, [], nodes, {})
    assert result["ok"] is False
    assert result["session_data"] == {} and result["params"] == {}


async def test_each_row_says_where_its_value_goes(client, auth_headers):
    """session.<key> sets a session value (existing or new), param.<name> a
    context parameter for the caller, anything else a task variable — and a
    row can use a static value or another variable."""
    nodes = [
        _start("set"),
        {
            "key": "set", "type": "set_variable", "label": "Set",
            "input_params": [
                {"name": "step", "type": "string", "value": "one", "required": False},
                {"name": "session.office_id", "type": "string", "value": "ADR-{{step}}", "required": False},
                {"name": "param.recordId", "type": "string", "source_field": "newId", "required": False},
                {"name": "param.fee", "type": "number", "value": "10", "required": False},
            ],
        },
    ]
    _, result = await _run(client, auth_headers, [], nodes, {"recordId": "app-1", "newId": "pay-9", "session.office_id": "OLD"})
    assert result["ok"] is True, result
    assert result["session_data"] == {"office_id": "ADR-one"}
    assert result["params"] == {"recordId": "pay-9", "fee": 10}
    ctx = result["context"]
    assert ctx["session.office_id"] == "ADR-one"  # the existing session value, replaced
    assert ctx["recordId"] == "pay-9" and ctx["step"] == "one"
