import os
from types import SimpleNamespace

import pytest
from pytest import fixture

import fbnconfig
from fbnconfig import relational_dataset_definition as rdd
from fbnconfig.datatype import ResourceId
from tests.integration.generate_test_name import gen


@fixture(scope="module")
def lusid_env():
    if os.getenv("FBN_ACCESS_TOKEN") is None or os.getenv("LUSID_ENV") is None:
        raise RuntimeError(
            "FBN_ACCESS_TOKEN and LUSID_ENV variables need to be set to run these tests"
        )

    env = os.environ["LUSID_ENV"]
    token = os.environ["FBN_ACCESS_TOKEN"]
    return SimpleNamespace(env=env, token=token)


@fixture(scope="module")
def client(lusid_env):
    return fbnconfig.create_client(lusid_env.env, lusid_env.token)


@fixture(scope="module")
def deployment_name():
    return gen("relational_datasets")


def resources(deployment_name):
    dataset_def = rdd.RelationalDatasetDefinitionResource(
        id="integrationtest-dataset-definition",
        scope=deployment_name,
        code="FinancialData",
        display_name="Financial Data Dataset",
        description="Dataset for storing financial instrument data",
        applicable_entity_types=[
            rdd.ApplicableEntityType.INSTRUMENT,
            rdd.ApplicableEntityType.PORTFOLIO,
        ],
        field_schema=[
            rdd.FieldSchema(
                field_name="instrumentId",
                display_name="Instrument ID",
                description="The unique identifier of the financial instrument",
                data_type_id=ResourceId(scope="system", code="string"),
                required=True,
                category=rdd.FieldCategory.SERIES_IDENTIFIER,
            ),
            rdd.FieldSchema(
                field_name="price",
                display_name="Price",
                description="The market price of the instrument",
                data_type_id=ResourceId(scope="system", code="number"),
                required=True,
                category=rdd.FieldCategory.VALUE,
            ),
            rdd.FieldSchema(
                field_name="currency",
                display_name="Currency",
                description="The currency of the price",
                data_type_id=ResourceId(scope="system", code="string"),
                required=False,
                category=rdd.FieldCategory.METADATA,
            )
        ],
    )

    return {
        "dataset_def": dataset_def,
    }


@fixture(scope="module")
def deployment(deployment_name, lusid_env):
    res = resources(deployment_name)
    print(f"\nRunning for deployment {deployment_name}...")
    yield fbnconfig.Deployment(deployment_name, list(res.values()))
    fbnconfig.deployex(fbnconfig.Deployment(deployment_name, []), lusid_env.env, lusid_env.token)


def test_teardown(deployment, client, lusid_env):
    # create first
    fbnconfig.deployex(deployment, lusid_env.env, lusid_env.token)
    # then delete
    fbnconfig.deployex(
        fbnconfig.Deployment(deployment.id, []), lusid_env.env, lusid_env.token
    )
    # verify no datasets remain
    result = client.get(
        f"/api/api/relationaldatasetdefinitions?filter=id.scope eq '{deployment.id}'"
    )
    assert len(result.json()["values"]) == 0


def test_create(deployment, client, lusid_env):
    fbnconfig.deployex(deployment, lusid_env.env, lusid_env.token)
    datasets = client.get(
        f"/api/api/relationaldatasetdefinitions?filter=id.scope eq '{deployment.id}'"
    )
    assert len(datasets.json()["values"]) == 1

    # Verify the dataset has the expected properties
    dataset = datasets.json()["values"][0]
    assert dataset["id"]["scope"] == deployment.id
    assert dataset["id"]["code"] == "FinancialData"
    assert dataset["displayName"] == "Financial Data Dataset"
    assert len(dataset["fieldSchema"]) == 3
    assert "Instrument" in dataset["applicableEntityTypes"]
    assert "Portfolio" in dataset["applicableEntityTypes"]


def test_update_nochange(deployment, client, lusid_env):
    # Create the dataset
    fbnconfig.deployex(deployment, lusid_env.env, lusid_env.token)
    # Deploy again without changes
    actions = fbnconfig.deployex(deployment, lusid_env.env, lusid_env.token)
    # Should report no changes
    assert {a.change for a in actions} == {"nochange"}

    # Verify dataset still exists with same properties
    datasets = client.get(
        f"/api/api/relationaldatasetdefinitions?filter=id.scope eq '{deployment.id}'"
    )
    assert len(datasets.json()["values"]) == 1


@pytest.mark.asyncio
async def test_relational_dataset_definition_list_with_filter(deployment, lusid_env):
    # GIVEN RelationalDatasetDefinition is deployed
    deployment_name = deployment.id
    scope = deployment_name
    code = "FinancialData"
    client = fbnconfig.create_client(lusid_env.env, lusid_env.token, is_async=True)
    # WHEN calling list with filter for the deployment name
    result = await rdd.RelationalDatasetDefinitionResource.list(
        client=client,
        query_params={"filter": f"id.code eq '{code}' and id.scope eq '{scope}'"},
        path_params={}
    )
    # THEN the result has correct structure
    expected_result = {
        "values": [
            {
                "keys": {
                    "code": code,
                    "scope": scope
                },
                "value": {
                    "id": f"relation-dataset-definition_{scope}_{code}".lower(),
                    "code": code,
                    "scope": scope,
                    "description": "Dataset for storing financial instrument data",
                    "displayName": "Financial Data Dataset",
                    "applicableEntityTypes": [
                        "Instrument",
                        "Portfolio"
                    ],
                    "fieldSchema": [
                        {
                            "fieldName": "instrumentId",
                            "displayName": "Instrument ID",
                            "description": "The unique identifier of the financial instrument",
                            "dataTypeId": {
                                "scope": "system",
                                "code": "string"
                            },
                            "required": True,
                            "category": "SeriesIdentifier"
                        },
                        {
                            "fieldName": "price",
                            "displayName": "Price",
                            "description": "The market price of the instrument",
                            "dataTypeId": {
                                "scope": "system",
                                "code": "number"
                            },
                            "required": True,
                            "category": "Value"
                        },
                        {
                            "fieldName": "currency",
                            "displayName": "Currency",
                            "description": "The currency of the price",
                            "dataTypeId": {
                                "scope": "system",
                                "code": "string"
                            },
                            "required": False,
                            "category": "Metadata"
                        }
                    ]
                }
            }
        ]
    }
    assert result == expected_result


@pytest.mark.asyncio
async def test_relational_dataset_definition_fetch(deployment, lusid_env):
    # GIVEN RelationalDatasetDefinition is deployed
    deployment_name = deployment.id
    scope = deployment_name
    code = "FinancialData"
    client = fbnconfig.create_client(lusid_env.env, lusid_env.token, is_async=True)
    # WHEN the fetch method is called for that resource
    response = await rdd.RelationalDatasetDefinitionResource.fetch(
        client=client,
        keys={
            "code": code,
            "scope": scope
        }
    )
    # THEN the response is as expected
    expected_response = {
        "id": f"relation-dataset-definition_{scope}_{code}".lower(),
        "code": code,
        "scope": scope,
        "description": "Dataset for storing financial instrument data",
        "displayName": "Financial Data Dataset",
        "applicableEntityTypes": [
            "Instrument",
            "Portfolio"
        ],
        "fieldSchema": [
            {
                "fieldName": "instrumentId",
                "displayName": "Instrument ID",
                "description": "The unique identifier of the financial instrument",
                "dataTypeId": {
                    "scope": "system",
                    "code": "string"
                },
                "required": True,
                "category": "SeriesIdentifier"
            },
            {
                "fieldName": "price",
                "displayName": "Price",
                "description": "The market price of the instrument",
                "dataTypeId": {
                    "scope": "system",
                    "code": "number"
                },
                "required": True,
                "category": "Value"
            },
            {
                "fieldName": "currency",
                "displayName": "Currency",
                "description": "The currency of the price",
                "dataTypeId": {
                    "scope": "system",
                    "code": "string"
                },
                "required": False,
                "category": "Metadata"
            }
        ]
    }
    assert response == expected_response


def test_update_change_fields(deployment, client, lusid_env):
    # Create the initial dataset
    fbnconfig.deployex(deployment, lusid_env.env, lusid_env.token)

    # Modify the deployment - change display name and description
    res = resources(deployment.id)
    res["dataset_def"].display_name = "Updated Financial Data Dataset"
    res["dataset_def"].description = "Updated description for financial data"

    updated_deployment = fbnconfig.Deployment(deployment.id, list(res.values()))
    actions = fbnconfig.deployex(updated_deployment, lusid_env.env, lusid_env.token)

    # Should report an update
    assert "update" in {a.change for a in actions}

    # Verify the changes were applied
    datasets = client.get(
        f"/api/api/relationaldatasetdefinitions?filter=id.scope eq '{deployment.id}'"
    )
    dataset = datasets.json()["values"][0]
    assert dataset["displayName"] == "Updated Financial Data Dataset"
    assert dataset["description"] == "Updated description for financial data"


def test_update_add_field(deployment, client, lusid_env):
    # Create the initial dataset
    fbnconfig.deployex(deployment, lusid_env.env, lusid_env.token)

    # Modify the deployment - add a new field to the schema
    res = resources(deployment.id)
    res["dataset_def"].field_schema.append(
        rdd.FieldSchema(
            field_name="volume",
            display_name="Trading Volume",
            description="The trading volume",
            data_type_id=ResourceId(scope="system", code="number"),
            required=False,
            category=rdd.FieldCategory.VALUE,
        )
    )

    updated_deployment = fbnconfig.Deployment(deployment.id, list(res.values()))
    actions = fbnconfig.deployex(updated_deployment, lusid_env.env, lusid_env.token)

    # Should report an update
    assert "update" in {a.change for a in actions}

    # Verify the new field was added
    datasets = client.get(
        f"/api/api/relationaldatasetdefinitions?filter=id.scope eq '{deployment.id}'"
    )
    dataset = datasets.json()["values"][0]
    assert len(dataset["fieldSchema"]) == 4
    field_names = [field["fieldName"] for field in dataset["fieldSchema"]]
    assert "volume" in field_names
