"""Datasource fields: a Control (how the field is edited, among those that
fit its type) and validation rules — the same rules a Form field takes,
enforced on every write (app/crud/validation.py)."""

import uuid

import pytest
from pydantic import ValidationError

from app.schemas.master import MasterFieldDef, controls_for


def test_controls_fit_the_type():
    assert controls_for("text")[0] == "textarea"
    assert controls_for("reference", multi=True) == ["checkboxes", "multiselect"]
    MasterFieldDef(name="email", type="varchar", control="email")
    MasterFieldDef(name="share", type="numeric", control="slider")
    with pytest.raises(ValidationError):
        MasterFieldDef(name="email", type="date", control="email")
    with pytest.raises(ValidationError):
        MasterFieldDef(name="n", type="integer", control="radio")
    with pytest.raises(ValidationError):
        MasterFieldDef(name="total", type="formula", formula_expression="1 + 1", validation_rules=[{"type": "min_value", "value": 1}])


async def _master(client, headers) -> str:
    resp = await client.post(
        "/masters",
        headers=headers,
        json={
            "name": f"Contacts {uuid.uuid4().hex[:6]}",
            "field_schema": [
                {"name": "name", "alias": "Full name", "type": "text", "control": "text", "required": True, "required_message": "Please give a name."},
                {"name": "email", "type": "varchar", "control": "email", "validation_rules": [{"type": "email"}]},
                {"name": "pincode", "alias": "PIN code", "type": "varchar", "validation_rules": [{"type": "pattern", "value": "^[0-9]{6}$", "message": "{label} must be 6 digits."}]},
                {"name": "share", "type": "integer", "control": "slider", "validation_rules": [{"type": "min_value", "value": 0}, {"type": "max_value", "value": 100}]},
            ],
        },
    )
    assert resp.status_code == 201, resp.text
    return resp.json()["id"]


async def test_rules_are_enforced_on_every_write(client, auth_headers):
    master_id = await _master(client, auth_headers)
    ok = await client.post(f"/masters/{master_id}/records", headers=auth_headers, json={"field_values": {"name": "Asha", "email": "a@x.io", "pincode": "695001", "share": 40}})
    assert ok.status_code == 201, ok.text

    bad = await client.post(f"/masters/{master_id}/records", headers=auth_headers, json={"field_values": {"name": "Asha", "email": "nope", "pincode": "69", "share": 140}})
    assert bad.status_code == 422
    detail = str(bad.json()["detail"])
    assert "email" in detail.lower()
    assert "PIN code must be 6 digits." in detail
    assert "share" in detail

    blank = await client.post(f"/masters/{master_id}/records", headers=auth_headers, json={"field_values": {"email": "a@x.io"}})
    assert blank.status_code == 422
    assert "Please give a name." in str(blank.json()["detail"])

    record_id = ok.json()["id"]
    edited = await client.put(f"/masters/{master_id}/records/{record_id}", headers=auth_headers, json={"field_values": {"name": "Asha", "pincode": "12"}})
    if edited.status_code == 405:
        edited = await client.patch(f"/masters/{master_id}/records/{record_id}", headers=auth_headers, json={"field_values": {"name": "Asha", "pincode": "12"}})
    assert edited.status_code == 422
    assert "PIN code must be 6 digits." in str(edited.json()["detail"])


async def test_controls_and_rules_are_saved(client, auth_headers):
    master_id = await _master(client, auth_headers)
    master = (await client.get(f"/masters/{master_id}", headers=auth_headers)).json()
    by_name = {f["name"]: f for f in master["field_schema"]}
    assert by_name["share"]["control"] == "slider"
    assert by_name["pincode"]["validation_rules"][0]["type"] == "pattern"
    assert by_name["name"]["required_message"] == "Please give a name."
