"""Tests for Phase 4 — schema registry, filter DSL, schema endpoints."""

from __future__ import annotations

import pytest
from django.urls import reverse

from simorgh.apps.schema.filters import FilterError, parse_filter
from simorgh.apps.schema.registry import (
    ActionSpec,
    EntitySchema,
    FieldSpec,
    SchemaError,
    ViewSpec,
    get_entity,
    list_entities,
    register_entity,
    reset_registry_for_tests,
)


@pytest.fixture
def _schema_registry():
    reset_registry_for_tests()
    yield
    reset_registry_for_tests()


@pytest.fixture
def contact_schema(_schema_registry):
    return register_entity(
        EntitySchema(
            name="crm.contact",
            label_key="crm.contact.label",
            plural_label_key="crm.contact.plural",
            fields=(
                FieldSpec(name="id", type="int", label_key="crm.contact.id", read_only=True),
                FieldSpec(
                    name="full_name",
                    type="string",
                    label_key="crm.contact.full_name",
                    required=True,
                    max_length=200,
                    semantic_type="person_name",
                ),
                FieldSpec(
                    name="email",
                    type="string",
                    label_key="crm.contact.email",
                    semantic_type="email",
                ),
                FieldSpec(
                    name="kind",
                    type="enum",
                    label_key="crm.contact.kind",
                    enum=("lead", "customer"),
                ),
                FieldSpec(name="score", type="int", label_key="crm.contact.score"),
                FieldSpec(name="archived", type="bool", label_key="crm.contact.archived"),
            ),
            views=(
                ViewSpec(kind="list", fields=("full_name", "email", "kind")),
                ViewSpec(kind="form", fields=("full_name", "email", "kind", "score")),
            ),
            actions=(
                ActionSpec(
                    name="archive",
                    label_key="crm.contact.archive",
                    endpoint="/api/v1/crm/contacts/{id}/archive/",
                    permission="crm.contact.update",
                ),
            ),
            list_endpoint="/api/v1/crm/contacts/",
            detail_endpoint="/api/v1/crm/contacts/{id}/",
            view_permission="crm.contact.view",
            filterable_fields=("full_name", "email", "kind", "score", "archived"),
            sortable_fields=("full_name", "score"),
        )
    )


# ---------------------------------------------------------------------------
# Registry
# ---------------------------------------------------------------------------


def test_register_and_lookup(contact_schema):
    fetched = get_entity("crm.contact")
    assert fetched is contact_schema
    assert any(e.name == "crm.contact" for e in list_entities())


def test_register_rejects_invalid_name(_schema_registry):
    with pytest.raises(SchemaError):
        register_entity(
            EntitySchema(
                name="badname",
                label_key="x",
                plural_label_key="x",
                fields=(FieldSpec(name="id", type="int", label_key="x"),),
            )
        )


def test_register_idempotent_same_spec(contact_schema):
    again = register_entity(contact_schema)
    assert again is contact_schema


def test_register_rejects_conflicting_redefine(contact_schema):
    with pytest.raises(SchemaError):
        register_entity(
            EntitySchema(
                name="crm.contact",
                label_key="other",
                plural_label_key="other",
                fields=(FieldSpec(name="id", type="int", label_key="x"),),
            )
        )


def test_view_referencing_unknown_field_rejected(_schema_registry):
    with pytest.raises(SchemaError):
        register_entity(
            EntitySchema(
                name="crm.thing",
                label_key="x",
                plural_label_key="x",
                fields=(FieldSpec(name="id", type="int", label_key="x"),),
                views=(ViewSpec(kind="list", fields=("ghost",)),),
            )
        )


def test_enum_field_requires_values(_schema_registry):
    with pytest.raises(SchemaError):
        FieldSpec(name="kind", type="enum", label_key="x")


def test_serialize_round_trip(contact_schema):
    data = contact_schema.serialize()
    assert data["name"] == "crm.contact"
    assert data["permissions"]["view"] == "crm.contact.view"
    assert {f["name"] for f in data["fields"]} >= {"full_name", "email", "kind"}
    assert data["actions"][0]["name"] == "archive"


# ---------------------------------------------------------------------------
# Filter DSL
# ---------------------------------------------------------------------------


def test_filter_simple_equality(contact_schema):
    q = parse_filter({"field": "kind", "op": "eq", "value": "lead"}, entity=contact_schema)
    assert ("kind__exact", "lead") in q.children


def test_filter_negation(contact_schema):
    q = parse_filter({"field": "kind", "op": "ne", "value": "lead"}, entity=contact_schema)
    assert q.negated is True


def test_filter_and_combination(contact_schema):
    q = parse_filter(
        {
            "and": [
                {"field": "kind", "op": "eq", "value": "lead"},
                {"field": "score", "op": "gte", "value": 50},
            ]
        },
        entity=contact_schema,
    )
    assert q.connector == "AND"
    assert len(q.children) == 2


def test_filter_or_combination(contact_schema):
    q = parse_filter(
        {
            "or": [
                {"field": "score", "op": "lt", "value": 10},
                {"field": "archived", "op": "eq", "value": True},
            ]
        },
        entity=contact_schema,
    )
    assert q.connector == "OR"


def test_filter_in_with_limit(contact_schema):
    q = parse_filter(
        {"field": "kind", "op": "in", "value": ["lead", "customer"]},
        entity=contact_schema,
    )
    assert ("kind__in", ["lead", "customer"]) in q.children


def test_filter_rejects_unknown_field(contact_schema):
    with pytest.raises(FilterError):
        parse_filter({"field": "secret", "op": "eq", "value": "x"}, entity=contact_schema)


def test_filter_rejects_non_filterable_field(_schema_registry):
    schema = register_entity(
        EntitySchema(
            name="crm.note",
            label_key="x",
            plural_label_key="x",
            fields=(
                FieldSpec(name="id", type="int", label_key="x"),
                FieldSpec(name="body", type="text", label_key="x"),
            ),
            filterable_fields=("id",),  # body not filterable
        )
    )
    with pytest.raises(FilterError):
        parse_filter({"field": "body", "op": "icontains", "value": "x"}, entity=schema)


def test_filter_rejects_wrong_op_for_type(contact_schema):
    with pytest.raises(FilterError):
        parse_filter(
            {"field": "archived", "op": "icontains", "value": "x"},
            entity=contact_schema,
        )


def test_filter_rejects_bad_value_type(contact_schema):
    with pytest.raises(FilterError):
        parse_filter({"field": "score", "op": "eq", "value": "not-an-int"}, entity=contact_schema)


def test_filter_rejects_enum_outside_choices(contact_schema):
    with pytest.raises(FilterError):
        parse_filter({"field": "kind", "op": "eq", "value": "ghost"}, entity=contact_schema)


def test_filter_rejects_excessive_depth(contact_schema):
    node = {"field": "score", "op": "eq", "value": 1}
    for _ in range(10):
        node = {"and": [node]}
    with pytest.raises(FilterError):
        parse_filter(node, entity=contact_schema)


# ---------------------------------------------------------------------------
# HTTP endpoints
# ---------------------------------------------------------------------------


@pytest.mark.django_db
def test_schema_list_requires_auth(api_client, contact_schema):
    resp = api_client.get(reverse("platform_schema:list"))
    assert resp.status_code in (401, 403)


@pytest.mark.django_db
def test_schema_detail_returns_entity(
    api_client, contact_schema, alice, alice_membership, tenant_acme
):
    api_client.force_login(alice)
    resp = api_client.get(
        reverse("platform_schema:detail", kwargs={"name": "crm.contact"}),
        HTTP_X_TENANT=tenant_acme.slug,
    )
    # Endpoint is reachable but gated by view permission alice doesn't hold.
    assert resp.status_code in (200, 403)


@pytest.mark.django_db
def test_schema_detail_404_for_unknown(api_client, _schema_registry, alice, tenant_acme):
    alice.is_superuser = True
    alice.save()
    api_client.force_login(alice)
    resp = api_client.get(
        reverse("platform_schema:detail", kwargs={"name": "no.such"}),
        HTTP_X_TENANT=tenant_acme.slug,
    )
    assert resp.status_code == 404
