"""Tests for Task 5.6 — Dynamic Forms Engine.

Covers:
  * FormDefinition CRUD services
  * validate_form_data — required fields, type checking
  * submit_form — happy path, inactive form, validation errors
  * Selectors: list_form_definitions, list_form_submissions
  * Events emitted on create / update / delete / submit
  * API endpoints: forms_list, form_detail, form_submit, form_submissions
"""

from __future__ import annotations

import json

import pytest

from simorgh.apps.platform_core.models import FormDefinition, FormSubmission
from simorgh.apps.platform_core.services import (
    PlatformCoreError,
    create_form_definition,
    delete_form_definition,
    get_form_definition_by_slug,
    submit_form,
    update_form_definition,
    validate_form_data,
)
from simorgh.apps.platform_core.selectors import (
    list_form_definitions,
    list_form_submissions,
)


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

_SIMPLE_FIELDS = [
    {"id": "name", "type": "text", "label": "Full Name", "required": True},
    {"id": "age", "type": "number", "label": "Age", "required": False},
    {"id": "email", "type": "email", "label": "Email", "required": True},
]

_URL = "/api/v1/"


def _form_url(path: str = "") -> str:
    return f"{_URL}forms/definitions/{path}"


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


@pytest.fixture
def org_node(tenant_acme, acme_tree):
    return acme_tree["root"]


@pytest.fixture
def form(tenant_acme, org_node):
    return create_form_definition(
        tenant_id=tenant_acme.pk,
        organization_node_id=org_node.pk,
        title="Contact Us",
        fields=_SIMPLE_FIELDS,
    )


@pytest.fixture
def staff_user(db):
    from simorgh.apps.accounts.models import User

    u = User.objects.create_user("+989001234567", password="x", email="staff@example.com")
    u.is_staff = True
    u.save()
    return u


@pytest.fixture
def staff_membership(staff_user, tenant_acme, acme_tree, role_admin):
    from simorgh.apps.memberships.models import Membership

    m = Membership.objects.create(
        tenant=tenant_acme,
        organization_node=acme_tree["root"],
        role=role_admin,
    )
    m.users.add(staff_user)
    return m


# ---------------------------------------------------------------------------
# Service — create_form_definition
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_create_form_definition_basic(tenant_acme, org_node):
    form = create_form_definition(
        tenant_id=tenant_acme.pk,
        organization_node_id=org_node.pk,
        title="Feedback",
        fields=[],
    )
    assert form.pk is not None
    assert form.title == "Feedback"
    assert form.tenant_id == tenant_acme.pk
    assert form.is_active is True
    assert form.submit_action == "notify_only"


@pytest.mark.django_db
def test_create_form_definition_slug_uniqueness(tenant_acme, tenant_globex, org_node, acme_tree):
    """Same slug is allowed in different tenants but rejected within the same tenant."""
    create_form_definition(
        tenant_id=tenant_acme.pk,
        organization_node_id=org_node.pk,
        title="Form A",
        public_url_slug="contact",
        fields=[],
    )
    # Same slug in another tenant — OK
    globex_root = pytest.importorskip("simorgh.apps.organizations.services").create_node(
        tenant_id=tenant_globex.pk, name="Globex HQ"
    )
    create_form_definition(
        tenant_id=tenant_globex.pk,
        organization_node_id=globex_root.pk,
        title="Form B",
        public_url_slug="contact",
        fields=[],
    )
    # Duplicate within same tenant — must raise
    with pytest.raises(PlatformCoreError):
        create_form_definition(
            tenant_id=tenant_acme.pk,
            organization_node_id=org_node.pk,
            title="Form C",
            public_url_slug="contact",
            fields=[],
        )


# ---------------------------------------------------------------------------
# Service — update_form_definition
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_update_form_definition_partial(form):
    updated = update_form_definition(form, title="Updated Title")
    assert updated.title == "Updated Title"
    # Other fields unchanged
    assert updated.is_active is True


@pytest.mark.django_db
def test_update_form_definition_deactivate(form):
    updated = update_form_definition(form, is_active=False)
    assert updated.is_active is False


# ---------------------------------------------------------------------------
# Service — delete_form_definition
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_delete_form_definition_removes_record(form):
    form_id = form.pk
    delete_form_definition(form)
    assert not FormDefinition.objects.filter(pk=form_id).exists()


# ---------------------------------------------------------------------------
# Service — get_form_definition_by_slug
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_get_form_definition_by_slug_found(tenant_acme, org_node):
    form = create_form_definition(
        tenant_id=tenant_acme.pk,
        organization_node_id=org_node.pk,
        title="Slug Form",
        public_url_slug="my-form",
        fields=[],
    )
    found = get_form_definition_by_slug("my-form", tenant_id=tenant_acme.pk)
    assert found.pk == form.pk


@pytest.mark.django_db
def test_get_form_definition_by_slug_not_found(tenant_acme):
    with pytest.raises(PlatformCoreError):
        get_form_definition_by_slug("does-not-exist", tenant_id=tenant_acme.pk)


# ---------------------------------------------------------------------------
# Service — validate_form_data
# ---------------------------------------------------------------------------


def test_validate_form_data_no_errors():
    fields = [
        {"id": "name", "type": "text", "label": "Name", "required": True},
        {"id": "age", "type": "number", "label": "Age", "required": False},
    ]

    class _Fake:
        pass

    form = _Fake()
    form.fields = fields
    form.is_active = True

    errors = validate_form_data(form, {"name": "Alice", "age": 30})
    assert errors == {}


def test_validate_form_data_missing_required():
    fields = [
        {"id": "name", "type": "text", "label": "Name", "required": True},
    ]

    class _Fake:
        pass

    form = _Fake()
    form.fields = fields
    form.is_active = True

    errors = validate_form_data(form, {})
    assert "name" in errors


def test_validate_form_data_invalid_email():
    fields = [
        {"id": "email", "type": "email", "label": "Email", "required": True},
    ]

    class _Fake:
        pass

    form = _Fake()
    form.fields = fields
    form.is_active = True

    errors = validate_form_data(form, {"email": "not-an-email"})
    assert "email" in errors


def test_validate_form_data_valid_email():
    fields = [
        {"id": "email", "type": "email", "label": "Email", "required": True},
    ]

    class _Fake:
        pass

    form = _Fake()
    form.fields = fields
    form.is_active = True

    errors = validate_form_data(form, {"email": "user@example.com"})
    assert errors == {}


# ---------------------------------------------------------------------------
# Service — submit_form
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_submit_form_happy_path(form, alice):
    sub = submit_form(form, {"name": "Alice", "email": "alice@example.com"}, user=alice)
    assert sub.pk is not None
    assert sub.form_id == form.pk
    assert sub.submitted_by_id == alice.pk
    assert sub.data["name"] == "Alice"


@pytest.mark.django_db
def test_submit_form_anonymous(form):
    """submitted_by may be None for public / anonymous submissions."""
    sub = submit_form(form, {"name": "Anon", "email": "anon@example.com"}, user=None)
    assert sub.submitted_by_id is None


@pytest.mark.django_db
def test_submit_form_validation_error(form):
    """Missing required field must raise PlatformCoreError."""
    with pytest.raises(PlatformCoreError):
        submit_form(form, {}, user=None)  # 'name' and 'email' required


@pytest.mark.django_db
def test_submit_form_inactive_form(tenant_acme, org_node):
    inactive = create_form_definition(
        tenant_id=tenant_acme.pk,
        organization_node_id=org_node.pk,
        title="Old Form",
        is_active=False,
        fields=[],
    )
    with pytest.raises(PlatformCoreError):
        submit_form(inactive, {}, user=None)


# ---------------------------------------------------------------------------
# Selector — list_form_definitions
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_list_form_definitions_all(tenant_acme, org_node):
    create_form_definition(
        tenant_id=tenant_acme.pk, organization_node_id=org_node.pk, title="F1", fields=[]
    )
    create_form_definition(
        tenant_id=tenant_acme.pk,
        organization_node_id=org_node.pk,
        title="F2",
        fields=[],
        is_active=False,
    )
    qs = list_form_definitions(tenant_acme.pk)
    assert qs.count() == 2


@pytest.mark.django_db
def test_list_form_definitions_filter_active(tenant_acme, org_node):
    create_form_definition(
        tenant_id=tenant_acme.pk, organization_node_id=org_node.pk, title="Active", fields=[]
    )
    create_form_definition(
        tenant_id=tenant_acme.pk,
        organization_node_id=org_node.pk,
        title="Inactive",
        fields=[],
        is_active=False,
    )
    active_qs = list_form_definitions(tenant_acme.pk, is_active=True)
    inactive_qs = list_form_definitions(tenant_acme.pk, is_active=False)
    assert all(f.is_active for f in active_qs)
    assert all(not f.is_active for f in inactive_qs)


@pytest.mark.django_db
def test_list_form_definitions_cross_tenant_isolation(tenant_acme, tenant_globex, org_node):
    create_form_definition(
        tenant_id=tenant_acme.pk, organization_node_id=org_node.pk, title="Acme Form", fields=[]
    )
    # Globex should see nothing
    qs = list_form_definitions(tenant_globex.pk)
    assert qs.count() == 0


# ---------------------------------------------------------------------------
# Selector — list_form_submissions
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_list_form_submissions(form, alice):
    submit_form(form, {"name": "A", "email": "a@x.com"}, user=alice)
    submit_form(form, {"name": "B", "email": "b@x.com"}, user=alice)
    qs = list_form_submissions(form)
    assert qs.count() == 2


@pytest.mark.django_db
def test_list_form_submissions_limit(form, alice):
    for i in range(5):
        submit_form(form, {"name": f"U{i}", "email": f"u{i}@x.com"}, user=alice)
    qs = list_form_submissions(form, limit=3)
    assert qs.count() == 3


# ---------------------------------------------------------------------------
# API — GET /api/v1/platform-core/forms/definitions/  (list)
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_forms_list_api_authenticated(client, alice, alice_membership, tenant_acme, form):
    client.force_login(alice)
    resp = client.get(_form_url(), HTTP_X_TENANT=tenant_acme.slug)
    assert resp.status_code == 200
    data = resp.json()
    assert isinstance(data, list)
    assert any(d["title"] == "Contact Us" for d in data)


@pytest.mark.django_db
def test_forms_list_api_requires_auth(client, tenant_acme):
    resp = client.get(_form_url(), HTTP_X_TENANT=tenant_acme.slug)
    assert resp.status_code == 401


@pytest.mark.django_db
def test_forms_list_api_filter_active(client, alice, alice_membership, tenant_acme, org_node):
    create_form_definition(
        tenant_id=tenant_acme.pk,
        organization_node_id=org_node.pk,
        title="Active Form",
        fields=[],
        is_active=True,
    )
    create_form_definition(
        tenant_id=tenant_acme.pk,
        organization_node_id=org_node.pk,
        title="Inactive Form",
        fields=[],
        is_active=False,
    )
    client.force_login(alice)
    resp = client.get(_form_url() + "?is_active=true", HTTP_X_TENANT=tenant_acme.slug)
    assert resp.status_code == 200
    data = resp.json()
    assert all(d["is_active"] for d in data)


# ---------------------------------------------------------------------------
# API — POST /api/v1/platform-core/forms/definitions/ (create — staff only)
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_forms_create_api_staff(client, staff_user, staff_membership, tenant_acme):
    client.force_login(staff_user)
    payload = {
        "title": "Staff Form",
        "fields": [{"id": "q", "type": "text", "label": "Q", "required": False}],
    }
    resp = client.post(
        _form_url(),
        data=json.dumps(payload),
        content_type="application/json",
        HTTP_X_TENANT=tenant_acme.slug,
    )
    assert resp.status_code == 201
    assert resp.json()["title"] == "Staff Form"


@pytest.mark.django_db
def test_forms_create_api_non_staff_forbidden(client, alice, alice_membership, tenant_acme):
    client.force_login(alice)
    payload = {"title": "Sneaky Form", "fields": []}
    resp = client.post(
        _form_url(),
        data=json.dumps(payload),
        content_type="application/json",
        HTTP_X_TENANT=tenant_acme.slug,
    )
    assert resp.status_code == 403


# ---------------------------------------------------------------------------
# API — GET / PATCH / DELETE  /forms/definitions/{id}/
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_form_detail_get(client, alice, alice_membership, tenant_acme, form):
    client.force_login(alice)
    resp = client.get(_form_url(f"{form.pk}/"), HTTP_X_TENANT=tenant_acme.slug)
    assert resp.status_code == 200
    assert resp.json()["id"] == form.pk


@pytest.mark.django_db
def test_form_detail_patch_staff(client, staff_user, staff_membership, tenant_acme, form):
    client.force_login(staff_user)
    resp = client.patch(
        _form_url(f"{form.pk}/"),
        data=json.dumps({"title": "Patched Title"}),
        content_type="application/json",
        HTTP_X_TENANT=tenant_acme.slug,
    )
    assert resp.status_code == 200
    assert resp.json()["title"] == "Patched Title"


@pytest.mark.django_db
def test_form_detail_patch_non_staff_forbidden(client, alice, alice_membership, tenant_acme, form):
    client.force_login(alice)
    resp = client.patch(
        _form_url(f"{form.pk}/"),
        data=json.dumps({"title": "Hacked"}),
        content_type="application/json",
        HTTP_X_TENANT=tenant_acme.slug,
    )
    assert resp.status_code == 403


@pytest.mark.django_db
def test_form_detail_delete_staff(client, staff_user, staff_membership, tenant_acme, form):
    client.force_login(staff_user)
    resp = client.delete(_form_url(f"{form.pk}/"), HTTP_X_TENANT=tenant_acme.slug)
    assert resp.status_code == 204
    assert not FormDefinition.objects.filter(pk=form.pk).exists()


@pytest.mark.django_db
def test_form_detail_not_found(client, alice, alice_membership, tenant_acme):
    client.force_login(alice)
    resp = client.get(_form_url("9999999/"), HTTP_X_TENANT=tenant_acme.slug)
    assert resp.status_code == 404


# ---------------------------------------------------------------------------
# API — POST /forms/definitions/{id}/submit/
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_form_submit_api_success(client, alice, alice_membership, tenant_acme, form):
    client.force_login(alice)
    payload = {"data": {"name": "Alice", "email": "alice@example.com"}}
    resp = client.post(
        _form_url(f"{form.pk}/submit/"),
        data=json.dumps(payload),
        content_type="application/json",
        HTTP_X_TENANT=tenant_acme.slug,
    )
    assert resp.status_code == 201
    assert FormSubmission.objects.filter(form=form).count() == 1


@pytest.mark.django_db
def test_form_submit_api_validation_error(client, alice, alice_membership, tenant_acme, form):
    client.force_login(alice)
    payload = {"data": {}}  # missing required fields
    resp = client.post(
        _form_url(f"{form.pk}/submit/"),
        data=json.dumps(payload),
        content_type="application/json",
        HTTP_X_TENANT=tenant_acme.slug,
    )
    assert resp.status_code == 400


@pytest.mark.django_db
def test_form_submit_api_requires_auth(client, tenant_acme, form):
    payload = {"data": {"name": "X", "email": "x@x.com"}}
    resp = client.post(
        _form_url(f"{form.pk}/submit/"),
        data=json.dumps(payload),
        content_type="application/json",
        HTTP_X_TENANT=tenant_acme.slug,
    )
    assert resp.status_code == 401


# ---------------------------------------------------------------------------
# API — GET /forms/definitions/{id}/submissions/  (staff only)
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_form_submissions_api_staff(
    client, staff_user, staff_membership, tenant_acme, form, alice
):
    submit_form(form, {"name": "A", "email": "a@x.com"}, user=alice)
    submit_form(form, {"name": "B", "email": "b@x.com"}, user=alice)
    client.force_login(staff_user)
    resp = client.get(_form_url(f"{form.pk}/submissions/"), HTTP_X_TENANT=tenant_acme.slug)
    assert resp.status_code == 200
    data = resp.json()
    assert len(data) == 2


@pytest.mark.django_db
def test_form_submissions_api_non_staff_forbidden(
    client, alice, alice_membership, tenant_acme, form
):
    client.force_login(alice)
    resp = client.get(_form_url(f"{form.pk}/submissions/"), HTTP_X_TENANT=tenant_acme.slug)
    assert resp.status_code == 403
