import json
from types import SimpleNamespace

import httpx
import pytest

from fbnconfig import datatype
from fbnconfig import relational_dataset_definition as rdd

TEST_BASE = "https://foo.lusid.com"


@pytest.mark.respx(base_url=TEST_BASE)
class DescribeRelationalDatasetDefinitionRef:
    base_url = TEST_BASE
    dataset_def_url = "/api/api/relationaldatasetdefinitions"
    client = httpx.Client(
        base_url=TEST_BASE, event_hooks={"response": [lambda x: x.raise_for_status()]}
    )

    def test_attach_when_present(self, respx_mock):
        # given that the remote definition exists
        respx_mock.get(f"{self.dataset_def_url}/default/sample-dataset").mock(
            side_effect=[
                httpx.Response(
                    200, json={"id": {"scope": "default", "code": "sample-dataset"}}
                )
            ]
        )

        sut = rdd.RelationalDatasetDefinitionRef(
            id="one", scope="default", code="sample-dataset"
        )
        # when we call attach
        sut.attach(self.client)
        # then a get request was made
        req = respx_mock.calls.last.request
        assert req.url == f"{self.base_url + self.dataset_def_url}/default/sample-dataset"

    def test_attach_when_missing(self, respx_mock):
        # given the remote does not exist
        respx_mock.get(f"{self.dataset_def_url}/default/missing-dataset").mock(
            side_effect=[
                httpx.Response(
                    404, json={"name": "RelationalDatasetDefinitionNotFound"}
                )
            ]
        )
        sut = rdd.RelationalDatasetDefinitionRef(
            id="two", scope="default", code="missing-dataset"
        )

        # when we call attach an exception is raised
        with pytest.raises(RuntimeError) as ex:
            sut.attach(self.client)

        assert (
            "Relational dataset definition default/missing-dataset does not exist"
            in str(ex.value)
        )

    def test_attach_when_http_error(self, respx_mock):
        # given the server returns an error
        respx_mock.get(f"{self.dataset_def_url}/default/error-dataset").mock(
            side_effect=[httpx.Response(400, json={})]
        )
        sut = rdd.RelationalDatasetDefinitionRef(
            id="three", scope="default", code="error-dataset"
        )
        # when we call attach an exception is raised
        with pytest.raises(httpx.HTTPError):
            sut.attach(self.client)


@pytest.mark.respx(base_url=TEST_BASE)
class DescribeRelationalDatasetDefinitionResource:
    base_url = TEST_BASE
    dataset_def_url = "/api/api/relationaldatasetdefinitions"
    client = httpx.Client(
        base_url=TEST_BASE, event_hooks={"response": [lambda x: x.raise_for_status()]}
    )

    def test_create_dataset_definition(self, respx_mock):
        # GIVEN Resource does not exist
        respx_mock.post(self.dataset_def_url).mock(
            side_effect=[
                httpx.Response(
                    status_code=201,
                    json={
                        "id": {"scope": "default", "code": "sample-dataset"},
                        "displayName": "Sample Financial Dataset",
                        "description": "A sample relational dataset definition",
                        "applicableEntityTypes": ["Instrument", "Portfolio"],
                        "fieldSchema": [
                            {
                                "fieldName": "instrumentId",
                                "displayName": "Instrument ID",
                                "description": "The unique identifier",
                                "dataTypeId": {"scope": "system", "code": "string"},
                                "required": True,
                                "category": "SeriesIdentifier",
                            }
                        ],
                        "href": "https://example.com/api/relationaldatasetdefinitions/default/sample-dataset",
                        "version": {
                            "effectiveFrom": "2019-01-01T12:00:00.0000000+00:00",
                            "asAtDate": "2019-01-01T12:00:00.0100000+00:00",
                        },
                    },
                )
            ]
        )

        # WHEN Dataset Definition Resource is created
        dataset_def = rdd.RelationalDatasetDefinitionResource(
            id="sample-dataset",
            scope="default",
            code="sample-dataset",
            display_name="Sample Financial Dataset",
            description="A sample relational dataset definition",
            applicable_entity_types=[
                rdd.ApplicableEntityType.INSTRUMENT,
                rdd.ApplicableEntityType.PORTFOLIO
            ],
            field_schema=[
                rdd.FieldSchema(
                    field_name="instrumentId",
                    display_name="Instrument ID",
                    description="The unique identifier",
                    data_type_id=datatype.ResourceId(scope="system", code="string"),
                    required=True,
                    category=rdd.FieldCategory.SERIES_IDENTIFIER,
                )
            ],
        )

        new_state = dataset_def.create(self.client)

        # THEN State returned as expected
        assert new_state == {
            "scope": "default",
            "code": "sample-dataset",
        }

        # and the expected post request was sent
        request: httpx.Request = respx_mock.calls.last.request
        assert request.method == "POST"
        assert request.url.path == self.dataset_def_url
        assert f"https://{request.url.host}" == self.base_url
        assert json.loads(request.content) == {
            "id": {"scope": "default", "code": "sample-dataset"},
            "displayName": "Sample Financial Dataset",
            "description": "A sample relational dataset definition",
            "applicableEntityTypes": ["Instrument", "Portfolio"],
            "fieldSchema": [
                {
                    "fieldName": "instrumentId",
                    "displayName": "Instrument ID",
                    "description": "The unique identifier",
                    "dataTypeId": {"scope": "system", "code": "string"},
                    "required": True,
                    "category": "SeriesIdentifier",
                }
            ],
        }

    def test_create_dataset_definition_with_datatypekey(self, respx_mock):

        system_string_datatype = datatype.DataTypeRef(id="systemstring", scope="system", code="string")

        # GIVEN Resource does not exist
        respx_mock.post(self.dataset_def_url).mock(
            side_effect=[
                httpx.Response(
                    status_code=201,
                    json={
                        "id": {"scope": "default", "code": "sample-dataset"},
                        "displayName": "Sample Financial Dataset",
                        "description": "A sample relational dataset definition",
                        "applicableEntityTypes": ["Instrument", "Portfolio"],
                        "fieldSchema": [
                            {
                                "fieldName": "instrumentId",
                                "displayName": "Instrument ID",
                                "description": "The unique identifier",
                                "dataTypeId": {"scope": "system", "code": "string"},
                                "required": True,
                                "category": "SeriesIdentifier",
                            }
                        ],
                        "href": "https://example.com/api/relationaldatasetdefinitions/default/sample-dataset",
                        "version": {
                            "effectiveFrom": "2019-01-01T12:00:00.0000000+00:00",
                            "asAtDate": "2019-01-01T12:00:00.0100000+00:00",
                        },
                    },
                )
            ]
        )

        # WHEN Dataset Definition Resource is created
        dataset_def = rdd.RelationalDatasetDefinitionResource(
            id="sample-dataset",
            scope="default",
            code="sample-dataset",
            display_name="Sample Financial Dataset",
            description="A sample relational dataset definition",
            applicable_entity_types=[
                rdd.ApplicableEntityType.INSTRUMENT,
                rdd.ApplicableEntityType.PORTFOLIO
            ],
            field_schema=[
                rdd.FieldSchema(
                    field_name="instrumentId",
                    display_name="Instrument ID",
                    description="The unique identifier",
                    data_type_id=system_string_datatype,
                    required=True,
                    category=rdd.FieldCategory.SERIES_IDENTIFIER,
                )
            ],
        )

        new_state = dataset_def.create(self.client)

        # THEN State returned as expected
        assert new_state == {
            "scope": "default",
            "code": "sample-dataset",
        }

        # and the expected post request was sent
        request: httpx.Request = respx_mock.calls.last.request
        assert request.method == "POST"
        assert request.url.path == self.dataset_def_url
        assert f"https://{request.url.host}" == self.base_url
        assert json.loads(request.content) == {
            "id": {"scope": "default", "code": "sample-dataset"},
            "displayName": "Sample Financial Dataset",
            "description": "A sample relational dataset definition",
            "applicableEntityTypes": ["Instrument", "Portfolio"],
            "fieldSchema": [
                {
                    "fieldName": "instrumentId",
                    "displayName": "Instrument ID",
                    "description": "The unique identifier",
                    "dataTypeId": {"scope": "system", "code": "string"},
                    "required": True,
                    "category": "SeriesIdentifier",
                }
            ],
        }

    def test_update_no_change(self, respx_mock):

        # WHEN Dataset Definition Resource is created
        dataset_def = rdd.RelationalDatasetDefinitionResource(
            id="sample-dataset",
            scope="default",
            code="sample-dataset-no-change",
            display_name="Sample Financial Dataset",
            description="A sample relational dataset definition",
            applicable_entity_types=[
                rdd.ApplicableEntityType.INSTRUMENT,
                rdd.ApplicableEntityType.PORTFOLIO
            ],
            field_schema=[
                rdd.FieldSchema(
                    field_name="instrumentId",
                    display_name="Instrument ID",
                    description="The unique identifier",
                    data_type_id=datatype.ResourceId(scope="system", code="string"),
                    required=True,
                    category=rdd.FieldCategory.SERIES_IDENTIFIER,
                )
            ],
        )

        respx_mock.get(
            f"{self.dataset_def_url}/{dataset_def.scope}/{dataset_def.code}"
        ).mock(
            side_effect=[
                httpx.Response(
                    200,
                    json={
                        "id": {"scope": "default", "code": "sample-dataset-no-change"},
                        "displayName": dataset_def.display_name,
                        "description": dataset_def.description,
                        "applicableEntityTypes": dataset_def.applicable_entity_types,
                        "fieldSchema": [
                            {
                                "fieldName": field.field_name,
                                "displayName": field.display_name,
                                "description": field.description,
                                "dataTypeId": {
                                    "scope": field.data_type_id.scope,
                                    "code": field.data_type_id.code
                                },
                                "required": field.required,
                                "category": field.category,
                            }
                            for field in dataset_def.field_schema
                        ],
                        "version": {
                            "effectiveFrom": "2019-01-01T12:00:00.0000000+00:00",
                            "asAtDate": "2019-01-01T12:00:00.0100000+00:00",
                        },
                        "href": "https://example.com/api/relationaldatasetdefinitions/default/sample-dataset-no-change",
                    },
                )
            ]
        )

        old_state = SimpleNamespace(
            id=dataset_def.id, scope=dataset_def.scope, code=dataset_def.code
        )

        new_state = dataset_def.update(self.client, old_state)
        # THEN no update is made
        assert new_state is None

    def test_update_no_change_null_description(self, respx_mock):

        # WHEN Dataset Definition Resource is created
        dataset_def = rdd.RelationalDatasetDefinitionResource(
            id="sample-dataset",
            scope="default",
            code="sample-dataset-no-change",
            display_name="Sample Financial Dataset",
            description="A sample relational dataset definition",
            applicable_entity_types=[
                rdd.ApplicableEntityType.INSTRUMENT,
                rdd.ApplicableEntityType.PORTFOLIO
            ],
            field_schema=[
                rdd.FieldSchema(
                    field_name="instrumentId",
                    display_name="Instrument ID",
                    description="The unique identifier",
                    data_type_id=datatype.ResourceId(scope="system", code="string"),
                    required=True,
                    category=rdd.FieldCategory.SERIES_IDENTIFIER,
                )
            ],
        )

        # AND we set the description to None
        dataset_def.description = None

        respx_mock.get(
            f"{self.dataset_def_url}/{dataset_def.scope}/{dataset_def.code}"
        ).mock(
            side_effect=[
                httpx.Response(
                    200,
                    json={
                        "id": {"scope": "default", "code": "sample-dataset-no-change"},
                        "displayName": dataset_def.display_name,
                        "description": dataset_def.description,
                        "applicableEntityTypes": dataset_def.applicable_entity_types,
                        "fieldSchema": [
                            {
                                "fieldName": field.field_name,
                                "displayName": field.display_name,
                                "description": field.description,
                                "dataTypeId": {
                                    "scope": field.data_type_id.scope,
                                    "code": field.data_type_id.code
                                },
                                "required": field.required,
                                "category": field.category,
                            }
                            for field in dataset_def.field_schema
                        ],
                        "version": {
                            "effectiveFrom": "2019-01-01T12:00:00.0000000+00:00",
                            "asAtDate": "2019-01-01T12:00:00.0100000+00:00",
                        },
                        "href": "https://example.com/api/relationaldatasetdefinitions/default/sample-dataset-no-change",
                    },
                )
            ]
        )

        old_state = SimpleNamespace(
            id=dataset_def.id, scope=dataset_def.scope, code=dataset_def.code
        )

        new_state = dataset_def.update(self.client, old_state)
        # THEN no update is made
        assert new_state is None

    def test_update_identifier(self, respx_mock):
        # GIVEN Dataset definition exists
        # WHEN updating scope or code
        # THEN Dataset is deleted and recreated
        old_state = SimpleNamespace(id="dd1", scope="default", code="old-dataset")

        respx_mock.delete(
            f"{self.dataset_def_url}/{old_state.scope}/{old_state.code}"
        ).mock(side_effect=[httpx.Response(200, json={"asAt": "asAt deleted"})])

        respx_mock.post(self.dataset_def_url).mock(
            side_effect=[
                httpx.Response(
                    status_code=201,
                    json={
                        "id": {"scope": "new-scope", "code": "new-dataset"},
                        "version": {"asAtDate": "2019-01-01T12:00:00.0100000+00:00"},
                    },
                )
            ]
        )

        dataset_def = rdd.RelationalDatasetDefinitionResource(
            id="dd1",
            scope="new-scope",
            code="new-dataset",
            display_name="Sample Financial Dataset",
            description="A sample relational dataset definition",
            applicable_entity_types=[
                rdd.ApplicableEntityType.INSTRUMENT,
                rdd.ApplicableEntityType.PORTFOLIO
            ],
            field_schema=[
                rdd.FieldSchema(
                    field_name="instrumentId",
                    display_name="Instrument ID",
                    description="The unique identifier",
                    data_type_id=datatype.ResourceId(scope="system", code="string"),
                    required=True,
                    category=rdd.FieldCategory.SERIES_IDENTIFIER,
                )
            ],
        )

        new_state = dataset_def.update(self.client, old_state)
        assert new_state == {
            "scope": "new-scope",
            "code": "new-dataset",
        }

        # and the post is made to create the dataset
        request = respx_mock.calls.last.request
        assert request.method == "POST"

    def test_update_display_name(self, respx_mock):
        # GIVEN Dataset definition exists
        dataset_def = rdd.RelationalDatasetDefinitionResource(
            id="dd1",
            scope="default",
            code="sample-dataset",
            display_name="Original Name",
            description="A sample relational dataset definition",
            applicable_entity_types=[
                rdd.ApplicableEntityType.INSTRUMENT,
                rdd.ApplicableEntityType.PORTFOLIO
            ],
            field_schema=[
                rdd.FieldSchema(
                    field_name="instrumentId",
                    display_name="Instrument ID",
                    description="The unique identifier",
                    data_type_id=datatype.ResourceId(scope="system", code="string"),
                    required=True,
                    category=rdd.FieldCategory.SERIES_IDENTIFIER,
                )
            ],
        )

        respx_mock.get(
            f"{self.dataset_def_url}/{dataset_def.scope}/{dataset_def.code}"
        ).mock(
            side_effect=[
                httpx.Response(
                    200,
                    json={
                        "id": {"scope": "default", "code": "sample-dataset"},
                        "displayName": "Original Name",
                        "description": dataset_def.description,
                        "applicableEntityTypes": dataset_def.applicable_entity_types,
                        "fieldSchema": [
                            {
                                "fieldName": field.field_name,
                                "displayName": field.display_name,
                                "description": field.description,
                                "dataTypeId": {"scope": "system", "code": "string"},
                                "required": field.required,
                                "category": field.category,
                            }
                            for field in dataset_def.field_schema
                        ],
                        "version": {"asAtDate": "2019-01-01T12:00:00.0100000+00:00"},
                        "href": "https://example.com",
                    },
                )
            ]
        )

        respx_mock.put(
            f"{self.dataset_def_url}/{dataset_def.scope}/{dataset_def.code}"
        ).mock(
            side_effect=[
                httpx.Response(
                    status_code=200,
                    json={
                        "displayName": "Updated Name",
                        "version": {"asAtDate": "2019-01-01T13:00:00.0100000+00:00"},
                    },
                )
            ]
        )

        old_state = SimpleNamespace(
            id="dd1", scope="default", code="sample-dataset"
        )

        # WHEN display name changes
        dataset_def.display_name = "Updated Name"
        new_state = dataset_def.update(self.client, old_state)

        # THEN update is made
        assert new_state == {
            "scope": "default",
            "code": "sample-dataset",
        }

        request = respx_mock.calls.last.request
        assert request.method == "PUT"

    def test_delete(self, respx_mock):
        respx_mock.delete(f"{self.dataset_def_url}/default/sample-dataset").mock(
            side_effect=[httpx.Response(200, json={"asAt": "asAt deleted"})]
        )

        old_state = SimpleNamespace(
            id="dd1", scope="default", code="sample-dataset"
        )

        dataset_def = rdd.RelationalDatasetDefinitionResource(
            id="dd1",
            scope="default",
            code="sample-dataset",
            display_name="Original Name",
            description="A sample relational dataset definition",
            applicable_entity_types=[
                rdd.ApplicableEntityType.INSTRUMENT,
                rdd.ApplicableEntityType.PORTFOLIO
            ],
            field_schema=[
                rdd.FieldSchema(
                    field_name="instrumentId",
                    display_name="Instrument ID",
                    description="The unique identifier",
                    data_type_id=datatype.ResourceId(scope="system", code="string"),
                    required=True,
                    category=rdd.FieldCategory.SERIES_IDENTIFIER,
                )
            ],
        )

        assert dataset_def.delete(self.client, old_state) is None

        request = respx_mock.calls.last.request
        assert request.method == "DELETE"

    @staticmethod
    def test_deps_with_datatyperef():
        # given a dataset definition with a system/string dataTypeId
        system_string_datatype = datatype.DataTypeRef(id="systemstring", scope="system", code="string")
        dataset_def = rdd.RelationalDatasetDefinitionResource(
            id="dd1",
            scope="default",
            code="dataset-with-system-types",
            display_name="Dataset with System Types",
            applicable_entity_types=[rdd.ApplicableEntityType.INSTRUMENT],
            field_schema=[
                rdd.FieldSchema(
                    field_name="stringField",
                    display_name="String Field",
                    data_type_id=system_string_datatype,
                    required=True,
                    category=rdd.FieldCategory.VALUE,
                )
            ],
        )

        # when we get dependencies
        deps = dataset_def.deps()

        # then we get back the system string data type ID
        assert deps == [system_string_datatype]

    @staticmethod
    def test_deps_with_datatype_resource():
        # given a dataset definition with a custom DataTypeResource
        custom_type = datatype.DataTypeResource(
            id="dt",
            scope="dtscope",
            code="dtcode",
            type_value_range=datatype.TypeValueRange.CLOSED,
            display_name="Priority Test",
            description="A test datatype for Priority",
            value_type=datatype.ValueType.STRING,
            acceptable_values=["High", "Medium", "Low"],
        )

        dataset_def = rdd.RelationalDatasetDefinitionResource(
            id="dd1",
            scope="default",
            code="dataset-with-custom-type",
            display_name="Dataset with Custom Type",
            applicable_entity_types=[rdd.ApplicableEntityType.INSTRUMENT],
            field_schema=[
                rdd.FieldSchema(
                    field_name="customField",
                    display_name="Custom Field",
                    data_type_id=custom_type,
                    required=True,
                    category=rdd.FieldCategory.VALUE,
                )
            ],
        )

        # when we get dependencies
        deps = dataset_def.deps()

        # then it includes the custom data type
        assert deps == [custom_type]

    @staticmethod
    def test_deps_with_resource_id():
        # given a dataset definition with ResourceId (system types)
        dataset_def = rdd.RelationalDatasetDefinitionResource(
            id="dd1",
            scope="default",
            code="dataset-with-system-types",
            display_name="Dataset with System Types",
            applicable_entity_types=[rdd.ApplicableEntityType.INSTRUMENT],
            field_schema=[
                rdd.FieldSchema(
                    field_name="stringField",
                    display_name="String Field",
                    data_type_id=datatype.ResourceId(scope="system", code="string"),
                    required=True,
                    category=rdd.FieldCategory.VALUE,
                )
            ],
        )

        # when we get dependencies
        deps = dataset_def.deps()

        # then no dependencies (ResourceId is not a Resource or Ref)
        assert deps == []

    @staticmethod
    def test_deps_with_multiple_fields():
        # given a dataset definition with multiple fields and mixed data types
        custom_type = datatype.DataTypeResource(
            id="dt",
            scope="dtscope",
            code="dtcode",
            type_value_range=datatype.TypeValueRange.CLOSED,
            display_name="Priority Test",
            description="A test datatype for Priority",
            value_type=datatype.ValueType.STRING,
            acceptable_values=["High", "Medium", "Low"],
        )

        type_ref = datatype.DataTypeRef(
            id="type-ref", scope="system", code="decimal"
        )

        dataset_def = rdd.RelationalDatasetDefinitionResource(
            id="dd1",
            scope="default",
            code="multi-field-dataset",
            display_name="Multi-field Dataset",
            applicable_entity_types=[
                rdd.ApplicableEntityType.INSTRUMENT,
                rdd.ApplicableEntityType.PORTFOLIO
            ],
            field_schema=[
                rdd.FieldSchema(
                    field_name="field1",
                    display_name="Field 1",
                    data_type_id=custom_type,
                    required=True,
                    category=rdd.FieldCategory.VALUE,
                ),
                rdd.FieldSchema(
                    field_name="field2",
                    display_name="Field 2",
                    data_type_id=datatype.ResourceId(scope="system", code="string"),
                    required=False,
                    category=rdd.FieldCategory.SERIES_IDENTIFIER,
                ),
                rdd.FieldSchema(
                    field_name="field3",
                    display_name="Field 3",
                    data_type_id=type_ref,
                    required=True,
                    category=rdd.FieldCategory.METADATA,
                ),
            ],
        )

        # when we get dependencies
        deps = dataset_def.deps()

        # then it includes both the resource and ref, but not the ResourceId
        assert deps == [custom_type, type_ref]

    def test_dump_simple_dataset_definition(self):
        # given a simple dataset definition resource
        sut = rdd.RelationalDatasetDefinitionResource(
            id="dump-dataset",
            scope="test-scope",
            code="DumpDataset",
            display_name="Test Dataset",
            description="A test dataset for dumping",
            applicable_entity_types=[rdd.ApplicableEntityType.INSTRUMENT],
            field_schema=[
                rdd.FieldSchema(
                    field_name="price",
                    display_name="Price",
                    data_type_id=datatype.ResourceId(scope="system", code="number"),
                    required=True,
                    category=rdd.FieldCategory.VALUE,
                )
            ],
        )
        # when we dump it
        result = sut.model_dump(
            mode="json",
            by_alias=True,
            round_trip=True,
            exclude_none=True,
            context={"style": "dump"},
        )
        # then all fields are included (no excludes)
        expected = {
            "scope": "test-scope",
            "code": "DumpDataset",
            "displayName": "Test Dataset",
            "description": "A test dataset for dumping",
            "applicableEntityTypes": ["Instrument"],
            "fieldSchema": [
                {
                    "fieldName": "price",
                    "displayName": "Price",
                    "dataTypeId": {"scope": "system", "code": "number"},
                    "required": True,
                    "category": "Value",
                }
            ],
        }
        assert result == expected

    def test_undump_simple_dataset_definition(self):
        # given dumped dataset definition data with id context
        data = {
            "scope": "test-scope",
            "code": "DumpDataset",
            "displayName": "Test Dataset",
            "description": "A test dataset for undumping",
            "applicableEntityTypes": ["Portfolio"],
            "fieldSchema": [
                {
                    "fieldName": "nav",
                    "displayName": "NAV",
                    "dataTypeId": {"scope": "system", "code": "number"},
                    "required": False,
                    "category": "Value",
                }
            ],
        }
        # when we undump it with id context
        result = rdd.RelationalDatasetDefinitionResource.model_validate(
            data, context={"id": "undump-dataset"}
        )
        # then the resource is properly constructed
        assert result.id == "undump-dataset"
        assert result.scope == "test-scope"
        assert result.code == "DumpDataset"
        assert result.display_name == "Test Dataset"
        assert result.description == "A test dataset for undumping"
        assert result.applicable_entity_types == ["Portfolio"]
        assert len(result.field_schema) == 1
        assert result.field_schema[0].field_name == "nav"
        assert result.field_schema[0].required is False

    def test_from_api(self, testdata_loader):
        input_data = testdata_loader(
            "relational_dataset_definitions",
            "list_mock_data"
        )["values"][0]
        expected_result = testdata_loader(
            "relational_dataset_definitions",
            "list_expected_result"
        )["values"][0]["value"]
        result_data = rdd.RelationalDatasetDefinitionResource.from_api(input_data)
        assert result_data == expected_result

    @pytest.mark.asyncio
    async def test_list(self, respx_mock, testdata_loader):
        input_data = testdata_loader(
            "relational_dataset_definitions",
            "list_mock_data"
        )
        expected_result = testdata_loader(
            "relational_dataset_definitions",
            "list_expected_result"
        )
        respx_mock.get(rdd.RelationalDatasetDefinitionResource.list_endpoint()).mock(
            return_value=httpx.Response(200, json=input_data)
        )
        async with httpx.AsyncClient(base_url=self.base_url) as client:
            result = await rdd.RelationalDatasetDefinitionResource.list(client, query_params={})
            assert result == expected_result

    @pytest.mark.asyncio
    async def test_fetch(self, respx_mock, testdata_loader):
        input_data = testdata_loader(
            "relational_dataset_definitions",
            "list_mock_data"
        )["values"][0]
        expected_result = testdata_loader(
            "relational_dataset_definitions",
            "list_expected_result"
        )["values"][0]["value"]
        keys = {"scope": "scope", "code": "code"}
        respx_mock.get(rdd.RelationalDatasetDefinitionResource.fetch_endpoint().format(**keys)).mock(
            return_value=httpx.Response(200, json=input_data)
        )
        async with httpx.AsyncClient(base_url=self.base_url) as client:
            result = await rdd.RelationalDatasetDefinitionResource.fetch(client, keys=keys)
        assert result == expected_result


class DescribeFieldSchema:
    @staticmethod
    def test_field_schema_serialization():
        # given a field schema
        field = rdd.FieldSchema(
            field_name="instrumentId",
            display_name="Instrument ID",
            description="The unique identifier",
            data_type_id=datatype.DataTypeRef(id="dt", scope="system", code="string"),
            required=True,
            category=rdd.FieldCategory.SERIES_IDENTIFIER,
        )

        # when we serialize it
        result = field.model_dump(mode="json", by_alias=True, exclude_none=True)

        # then it matches the expected format
        assert result == {
            "fieldName": "instrumentId",
            "displayName": "Instrument ID",
            "description": "The unique identifier",
            "dataTypeId": {"scope": "system", "code": "string"},
            "required": True,
            "category": "SeriesIdentifier",
        }

    @staticmethod
    def test_field_schema_with_optional_fields():
        # given a minimal field schema
        field = rdd.FieldSchema(
            field_name="optionalField",
            display_name="Optional Field",
            data_type_id=datatype.ResourceId(scope="system", code="string"),
            category=rdd.FieldCategory.METADATA,
        )

        # when we serialize it
        result = field.model_dump(mode="json", by_alias=True, exclude_none=True)

        # then optional fields are excluded and required is automatically set to True
        assert result == {
            "fieldName": "optionalField",
            "displayName": "Optional Field",
            "dataTypeId": {"scope": "system", "code": "string"},
            "required": True,
            "category": "Metadata",
        }
