import os

import pytest
from pytest import fixture

import fbnconfig
from fbnconfig import customentity, datatype, property
from tests.integration.generate_test_name import gen

if os.getenv("FBN_ACCESS_TOKEN") is None or os.getenv("LUSID_ENV") is None:
    raise (
        RuntimeError("Both FBN_ACCESS_TOKEN and LUSID_ENV variables need to be set to run these tests")
    )

lusid_env = os.environ["LUSID_ENV"]
token = os.environ["FBN_ACCESS_TOKEN"]
host_vars = {}
client = fbnconfig.create_client(lusid_env, token)


@fixture(scope="module")
def unique_name():
    deployment_name = gen("ce")
    print(f"\nRunning for deployment {deployment_name}...")
    return deployment_name


@fixture(scope="module")
def entity_type(unique_name):
    ce_type = fbnconfig.customentity.EntityTypeResource(
        id="ce2",
        entity_type_name=unique_name,
        display_name="Takeaway menu",
        description="A menu",
        field_schema=[
            fbnconfig.customentity.FieldDefinition(
                name="venueId",
                lifetime=fbnconfig.customentity.LifeTime.PERPETUAL,
                type=fbnconfig.customentity.FieldType.STRING,
                collection_type=fbnconfig.customentity.CollectionType.SINGLE,
                required=True,
            ),
            fbnconfig.customentity.FieldDefinition(
                name="venueOwner",
                lifetime=fbnconfig.customentity.LifeTime.TIMEVARIANT,
                type=fbnconfig.customentity.FieldType.STRING,
                required=True,
            ),
        ],
    )
    return ce_type


@fixture(scope="module")
def identifier_type(unique_name):
    identifier_type = property.DefinitionResource(
        id="exid",
        domain=property.Domain.CustomEntity,
        scope=f"ce-example-{unique_name}",
        code="ceidtype",
        display_name="Example Custom Entity ID",
        data_type_id=datatype.DataTypeRef(
            id="systemstring",
            scope="system",
            code="string",
        ),
        life_time=property.LifeTime.Perpetual,
        constraint_style=property.ConstraintStyle.Identifier,
    )
    return identifier_type


@fixture(scope="module")
def entity_instance(identifier_type, entity_type):
    ce_instance = customentity.EntityResource(
        id="ce1-instance",
        entity_type=entity_type,
        description="An example custom entity instance",
        display_name="Example Instance",
        identifiers=[
            customentity.EntityIdentifier(
                identifier_type=identifier_type,
                identifier_value="ce-example",
            )
        ],
        fields=[
            customentity.EntityField(
                name="venueId",
                value="Example Venue 1",
            ),
            customentity.EntityField(
                name="venueOwner",
                value="Example owner 1",
            )
        ]
    )
    return ce_instance


@fixture(scope="module")
def deployment(unique_name, entity_type, entity_instance):
    dep = fbnconfig.Deployment(unique_name, [entity_type, entity_instance])
    yield dep
    print("\nTearing down deployment but stranding entity type")
    fbnconfig.deployex(fbnconfig.Deployment(unique_name, [entity_type]), lusid_env, token)


def test_create(deployment, identifier_type):
    fbnconfig.deployex(deployment, lusid_env, token)
    res = client.get(f"/api/api/customentities/entitytypes/~{deployment.id}").json()
    assert res is not None
    res = client.get(f"/api/api/customentities/~{deployment.id}/{identifier_type.code}/ce-example",
        params={"identifierScope": identifier_type.scope}).json()
    assert res["fields"][0]["value"] == "Example Venue 1"


def test_nochange(deployment):
    fbnconfig.deployex(deployment, lusid_env, token)
    update = fbnconfig.deploy(deployment, lusid_env, token)
    assert [a.change for a in update] == ["nochange", "attach", "nochange", "nochange"]


def test_update_entity(deployment, entity_type, identifier_type):
    # given an existing deployment
    fbnconfig.deployex(deployment, lusid_env, token)
    # and an instance with some mnodified fields
    ce_instance = customentity.EntityResource(
        id="ce1-instance",
        entity_type=entity_type,
        description="An example custom entity instance",
        display_name="Example Instance",
        identifiers=[
            customentity.EntityIdentifier(
                identifier_type=identifier_type,
                identifier_value="ce-example",
            )
        ],
        fields=[
            customentity.EntityField(
                name="venueId",
                value="Different Venue 1",
            ),
            customentity.EntityField(
                name="venueOwner",
                value="Example owner 1",
            )
        ]
    )
    # when we deploy over the top
    mod_deploy = fbnconfig.Deployment(deployment.id, [entity_type, ce_instance])
    fbnconfig.deployex(mod_deploy, lusid_env, token)
    # then the field has changed
    res = client.get(f"/api/api/customentities/~{deployment.id}/{identifier_type.code}/ce-example",
        params={"identifierScope": identifier_type.scope}).json()
    assert res["fields"][0]["value"] == "Different Venue 1"


def test_update_entity_identifier(deployment, entity_type, identifier_type):
    # given an existing deployment
    fbnconfig.deployex(deployment, lusid_env, token)
    # and an instance with some mnodified fields
    ce_instance = customentity.EntityResource(
        id="ce1-instance",
        entity_type=entity_type,
        description="An example custom entity instance",
        display_name="Example Instance",
        identifiers=[
            customentity.EntityIdentifier(
                identifier_type=identifier_type,
                identifier_value="different-example",
            )
        ],
        fields=[
            customentity.EntityField(
                name="venueId",
                value="Different Venue 1",
            ),
            customentity.EntityField(
                name="venueOwner",
                value="Example owner 1",
            )
        ]
    )
    # when we deploy over the top
    mod_deploy = fbnconfig.Deployment(deployment.id, [entity_type, ce_instance])
    fbnconfig.deployex(mod_deploy, lusid_env, token)
    # then the field has changed
    res = client.get("/api/api/customentities/"
        f"~{deployment.id}/{identifier_type.code}/different-example",
        params={"identifierScope": identifier_type.scope}).json()
    assert res["fields"][0]["value"] == "Different Venue 1"


@pytest.mark.asyncio
async def test_entity_list(deployment, unique_name):
    # GIVEN Entity is deployed
    fbnconfig.deployex(deployment, lusid_env, token)
    deployment_name = unique_name
    entity_type = f"~{deployment_name}"
    identifier_scope = f"ce-example-{deployment_name}"
    identifier_code = "ceidtype"
    identifier_value = "ce-example"
    client = fbnconfig.create_client(lusid_env, token, is_async=True)
    # WHEN calling list with filter for the deployment name
    result = await customentity.EntityResource.list(
        client=client,
        query_params={},
        path_params={"entityType": entity_type}
    )
    # THEN the result has correct structure
    expected_result = {
        "values": [
            {
                "keys": {
                    "entityType": entity_type,
                    "identifierType": identifier_code,
                    "identifierValue": identifier_value,
                    "identifierScope": identifier_scope,
                },
                "value": {
                    "id": (f"entity_{entity_type}_{identifier_code}_"
                           f"{identifier_value}_{identifier_scope}".lower()),
                    "entityType": {
                        "$ref": f"entity-type_{entity_type}".lower()
                    },
                    "description": "An example custom entity instance",
                    "displayName": "Example Instance",
                    "identifiers": [
                        {
                            "identifierType": {
                                "$ref": f"identifier-type_{identifier_code}".lower()
                            },
                            "identifierValue": identifier_value,
                            "effectiveFrom": "0001-01-01T00:00:00.0000000+00:00",
                            "effectiveTo": "9999-12-31T23:59:59.9999999+00:00"
                        }
                    ],
                    "fields": [
                        {
                            "name": "venueId",
                            "value": "Example Venue 1",
                            "effectiveFrom": "0001-01-01T00:00:00.0000000+00:00",
                            "effectiveTo": "9999-12-31T23:59:59.9999999+00:00"
                        },
                        {
                            "name": "venueOwner",
                            "value": "Example owner 1",
                            "effectiveFrom": "0001-01-01T00:00:00.0000000+00:00",
                            "effectiveTo": "9999-12-31T23:59:59.9999999+00:00"
                        }
                    ],
                    "properties": {},
                }
            }
        ]
    }
    assert result["values"] == expected_result["values"]


@pytest.mark.asyncio
async def test_entity_fetch(deployment, unique_name):
    # GIVEN Entity is deployed
    fbnconfig.deployex(deployment, lusid_env, token)
    deployment_name = unique_name
    entity_type = f"~{deployment_name}"
    identifier_scope = f"ce-example-{deployment_name}"
    identifier_code = "ceidtype"
    identifier_value = "ce-example"
    client = fbnconfig.create_client(lusid_env, token, is_async=True)
    # WHEN the fetch method is called for that resource
    response = await customentity.EntityResource.fetch(
        client=client,
        keys={
            "entityType": entity_type,
            "identifierType": identifier_code,
            "identifierValue": identifier_value,
            "identifierScope": identifier_scope,
        }
    )
    # THEN the response is as expected
    expected_response = {
        "id": f"entity_{entity_type}_{identifier_code}_{identifier_value}_{identifier_scope}".lower(),
        "entityType": {
            "$ref": f"entity-type_{entity_type}".lower()
        },
        "description": "An example custom entity instance",
        "displayName": "Example Instance",
        "identifiers": [
            {
                "identifierType": {
                    "$ref": f"identifier-type_{identifier_code}".lower()
                },
                "identifierValue": identifier_value,
                "effectiveFrom": "0001-01-01T00:00:00.0000000+00:00",
                "effectiveTo": "9999-12-31T23:59:59.9999999+00:00"
            }
        ],
        "fields": [
            {
                "name": "venueId",
                "value": "Example Venue 1",
                "effectiveFrom": "0001-01-01T00:00:00.0000000+00:00",
                "effectiveTo": "9999-12-31T23:59:59.9999999+00:00"
            },
            {
                "name": "venueOwner",
                "value": "Example owner 1",
                "effectiveFrom": "0001-01-01T00:00:00.0000000+00:00",
                "effectiveTo": "9999-12-31T23:59:59.9999999+00:00"
            }
        ],
        "properties": {},
    }
    assert response == expected_response


@pytest.mark.asyncio
async def test_entity_type_list_with_filter(deployment, unique_name):
    # GIVEN CustomEntity is deployed
    fbnconfig.deployex(deployment, lusid_env, token)
    entity_type = f"~{unique_name}"
    client = fbnconfig.create_client(lusid_env, token, is_async=True)
    # WHEN calling list with filter for the deployment name
    result = await customentity.EntityTypeResource.list(
        client=client,
        query_params={"filter": f"entityTypeName eq '{unique_name}'"},
        path_params={}
    )
    # THEN the result has correct structure
    expected_result = {
        "values": [
            {
                "keys": {
                    "entityType": entity_type,
                },
                "value": {
                    "id": f"entity-type_{entity_type}".lower(),
                    "entityTypeName": unique_name,
                    "displayName": "Takeaway menu",
                    "description": "A menu",
                    "fieldSchema": [
                        {
                            "lifetime": "Perpetual",
                            "name": "venueId",
                            "type": "String",
                            "required": True
                        },
                        {
                            "lifetime": "TimeVariant",
                            "name": "venueOwner",
                            "type": "String",
                            "required": True
                        }
                    ]
                }
            }
        ]
    }
    assert result == expected_result


@pytest.mark.asyncio
async def test_entity_type_fetch(deployment, unique_name):
    # GIVEN CustomEntity is deployed
    fbnconfig.deployex(deployment, lusid_env, token)
    entity_type = f"~{unique_name}"
    client = fbnconfig.create_client(lusid_env, token, is_async=True)
    # WHEN the fetch method is called for that resource
    response = await customentity.EntityTypeResource.fetch(
        client=client,
        keys={
            "entityType": entity_type
        }
    )
    # THEN the response is as expected
    expected_response = {
        "id": f"entity-type_{entity_type}".lower(),
        "entityTypeName": unique_name,
        "displayName": "Takeaway menu",
        "description": "A menu",
        "fieldSchema": [
            {
                "lifetime": "Perpetual",
                "name": "venueId",
                "type": "String",
                "required": True
            },
            {
                "lifetime": "TimeVariant",
                "name": "venueOwner",
                "type": "String",
                "required": True
            }
        ]
    }
    assert response == expected_response
