import os
from types import SimpleNamespace

import pytest
from httpx import HTTPStatusError
from pytest import fixture

import fbnconfig
import tests.integration.access as access
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 = {}


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

    yield SimpleNamespace(name=deployment_name)
    # Teardown: Clean up resources (if any) after the test
    print("\nTearing down resources...")
    fbnconfig.deployex(fbnconfig.Deployment(deployment_name, []), lusid_env, token)


def test_teardown(setup_deployment):
    deployment_name = setup_deployment.name
    client = fbnconfig.create_client(lusid_env, token)
    # setup deployment
    fbnconfig.deployex(access.configure(setup_deployment), lusid_env, token)
    # check it exists
    client.request("get", f"/access/api/policies/policy-{deployment_name}")
    # Tear it down
    fbnconfig.deployex(fbnconfig.Deployment(deployment_name, []), lusid_env, token)
    # check it was deleted
    try:
        client.request("get", f"/access/api/policies/policy-{deployment_name}")
    except HTTPStatusError as error:
        assert error.response.status_code == 404


def test_create(setup_deployment):
    fbnconfig.deployex(access.configure(setup_deployment), lusid_env, token)
    client = fbnconfig.create_client(lusid_env, token)
    search = client.request("get", f"/access/api/roles/role-{setup_deployment.name}")
    assert search.status_code == 200


def test_update_nochange(setup_deployment):
    fbnconfig.deployex(access.configure(setup_deployment), lusid_env, token)
    update = fbnconfig.deployex(access.configure(setup_deployment), lusid_env, token)
    assert [a.change for a in update] == ["nochange"] * 2 + ["attach"] + ["nochange"] * 7


@pytest.mark.asyncio
async def test_policy_list(setup_deployment):
    # GIVEN Policy resource is deployed
    fbnconfig.deployex(access.configure(setup_deployment), lusid_env, token)
    deployment_name = setup_deployment.name
    # WHEN calling list with filter for the deployment name
    client = fbnconfig.create_client(lusid_env, token, is_async=True)
    expected_result = {
        "values": [{
            "keys": {
                "code": f"policy-{deployment_name}",
                "scope": "default",
            },
            "value": {
                "id": f"policy_default_policy-{deployment_name}".lower(),
                "scope": "default",
                "code": f"policy-{deployment_name}",
                "description": "Policy with an Id selector",
                "applications": [
                    "Scheduler"
                ],
                "grant": "Allow",
                "selectors": [
                    {
                        "idSelectorDefinition": {
                            "description": "whatever",
                            "name": "banjo",
                            "identifier": {
                                "scope": f"w_{deployment_name}",
                                "code": "y"
                            },
                            "actions": [
                                {
                                    "scope": deployment_name,
                                    "activity": "execute",
                                    "entity": "Feature"
                                }
                            ]
                        }
                    }
                ],
                "when": {
                    "activate": "2024-08-31T18:00:00.0000000+00:00",
                    "deactivate": "9999-12-31T23:59:59.9999999+00:00"
                }
            }}]
    }
    result = await access.access.PolicyResource.list(
        client=client,
        query_params={"filter": f"id.code eq 'policy-{deployment_name}'"}
    )
    # THEN the result is as expected
    assert result == expected_result


@pytest.mark.asyncio
async def test_policy_list_returns_next_page(setup_deployment):
    # GIVEN Policy resource is deployed
    fbnconfig.deployex(access.configure(setup_deployment), lusid_env, token)
    client = fbnconfig.create_client(lusid_env, token, is_async=True)
    # WHEN calling list with limit of 1
    result_page = await access.access.PolicyResource.list(
        client=client,
        query_params={"limit": 1}
    )
    # THEN the result is as expected
    assert "values" in result_page and isinstance(result_page["values"], list)
    assert "nextPage" in result_page


@pytest.mark.asyncio
async def test_policy_fetch(setup_deployment):
    # GIVEN Policy resource is deployed
    deployment_name = setup_deployment.name
    scope = "default"
    code = f"policy-{deployment_name}"
    client = fbnconfig.create_client(lusid_env, token, is_async=True)
    # WHEN the fetch method is called for that resource
    response = await access.access.PolicyResource.fetch(
        client=client,
        keys={
            "scope": scope,
            "code": code
        }
    )
    # THEN the response is as expected
    expected_response = {
        "id": f"policy_{scope}_{code}".lower(),
        "description": "Policy with an Id selector",
        "applications": [
            "Scheduler"
        ],
        "grant": "Allow",
        "selectors": [
            {
                "idSelectorDefinition": {
                    "identifier": {
                        "code": "y",
                        "scope": f"w_{deployment_name}"
                    },
                    "actions": [
                        {
                            "scope": deployment_name,
                            "activity": "execute",
                            "entity": "Feature"
                        }
                    ],
                    "name": "banjo",
                    "description": "whatever"
                }
            }
        ],
        "when": {
            "activate": "2024-08-31T18:00:00.0000000+00:00",
            "deactivate": "9999-12-31T23:59:59.9999999+00:00"
        },
        "scope": scope,
        "code": code
    }
    assert response == expected_response


@pytest.mark.asyncio
async def test_policy_template_list_with_filter(setup_deployment):
    # GIVEN PolicyTemplate resource is deployed
    fbnconfig.deployex(access.configure(setup_deployment), lusid_env, token)
    deployment_name = setup_deployment.name
    client = fbnconfig.create_client(lusid_env, token, is_async=True)
    # WHEN calling list with filter for the deployment name
    result = await access.access.PolicyTemplateResource.list(
        client=client,
        query_params={"filter": f"code eq 'policy-template-{deployment_name}'"}
    )
    # THEN the result has correct structure
    expected_result = {
        "values": [
            {
                "keys": {
                    "code": f"policy-template-{deployment_name}"
                },
                "value": {
                    "id": f"policy-template_default_policy-template-{deployment_name}".lower(),
                    "displayName": "updated-policy-template",
                    "scope": "default",
                    "code": f"policy-template-{deployment_name}",
                    "description": "Example policy template for a policy that grants access to "
                                   "some resource",
                    "templatedSelectors": [
                        {
                            "application": "example_application",
                            "tag": "Data",
                            "selector": {
                                "idSelectorDefinition": {
                                    "identifier": {
                                        "code": "y",
                                        "scope": f"w_{deployment_name}"
                                    },
                                    "actions": [
                                        {
                                            "scope": deployment_name,
                                            "activity": "execute",
                                            "entity": "Feature"
                                        }
                                    ],
                                    "name": "banjo",
                                    "description": "whatever"
                                }
                            }
                        }
                    ],
                }
            }
        ]
    }
    assert result == expected_result


@pytest.mark.asyncio
async def test_policy_template_list_returns_next_page(setup_deployment):
    # GIVEN PolicyTemplate resource is deployed
    fbnconfig.deployex(access.configure(setup_deployment), lusid_env, token)
    client = fbnconfig.create_client(lusid_env, token, is_async=True)
    # WHEN calling list with limit of 1
    result_page = await access.access.PolicyTemplateResource.list(
        client=client,
        query_params={"limit": 1}
    )
    # THEN the result is as expected
    assert "values" in result_page and isinstance(result_page["values"], list)
    assert "nextPage" in result_page


@pytest.mark.asyncio
async def test_policy_template_fetch(setup_deployment):
    # GIVEN PolicyTemplate resource is deployed
    fbnconfig.deployex(access.configure(setup_deployment), lusid_env, token)
    deployment_name = setup_deployment.name
    scope = "default"
    code = f"policy-template-{deployment_name}"
    client = fbnconfig.create_client(lusid_env, token, is_async=True)
    # WHEN the fetch method is called for that resource
    response = await access.access.PolicyTemplateResource.fetch(
        client=client,
        keys={
            "scope": scope,
            "code": code
        }
    )
    # THEN the response is as expected
    expected_response = {
        "id": f"policy-template_{scope}_{code}".lower(),
        "displayName": "updated-policy-template",
        "scope": "default",
        "code": code,
        "description": "Example policy template for a policy that grants access to some resource",
        "templatedSelectors": [
            {
                "application": "example_application",
                "tag": "Data",
                "selector": {
                    "idSelectorDefinition": {
                        "identifier": {
                            "code": "y",
                            "scope": f"w_{deployment_name}"
                        },
                        "actions": [
                            {
                                "scope": deployment_name,
                                "activity": "execute",
                                "entity": "Feature"
                            }
                        ],
                        "name": "banjo",
                        "description": "whatever"
                    }
                }
            }
        ],
    }
    assert response == expected_response


@pytest.mark.asyncio
async def test_policy_collection_list_returns_next_page(setup_deployment):
    # GIVEN PolicyCollection resource is deployed
    fbnconfig.deployex(access.configure(setup_deployment), lusid_env, token)
    client = fbnconfig.create_client(lusid_env, token, is_async=True)
    # WHEN calling list with limit of 2
    result_page = await access.access.PolicyCollectionResource.list(
        client=client,
        query_params={"limit": 2}
    )
    # THEN the result is as expected
    assert "values" in result_page and isinstance(result_page["values"], list)
    assert "nextPage" in result_page


@pytest.mark.asyncio
async def test_role_resource_list(setup_deployment):
    # GIVEN deployed configuration and async client
    fbnconfig.deployex(access.configure(setup_deployment), lusid_env, token)
    client = fbnconfig.create_client(lusid_env, token, is_async=True)
    # WHEN calling list without pagination (API does not support it)
    result = await access.access.RoleResource.list(
        client=client,
        query_params={
            # this API does not support pagination and filtering
        }
    )
    # THEN the result has correct structure
    # (not assert whole data because it does not support pagination and return a lot of results)
    assert "values" in result and isinstance(result["values"], list)
    resource = result["values"][0]["value"]
    assert "id" in resource and resource["id"].startswith("role_")
    assert "code" in resource and isinstance(resource["code"], str)
    assert "scope" in resource and isinstance(resource["scope"], str)
    assert "permission" in resource and isinstance(resource["permission"], str)
    assert "when" in resource and isinstance(resource["when"], dict)


@pytest.mark.asyncio
async def test_role_fetch(setup_deployment):
    # GIVEN a Role exists in LUSID
    fbnconfig.deployex(access.configure(setup_deployment), lusid_env, token)
    deployment_name = setup_deployment.name
    scope = "default"
    code = f"role-{deployment_name}"
    client = fbnconfig.create_client(lusid_env, token, is_async=True)
    # WHEN the fetch method is called for that resource
    response = await access.access.RoleResource.fetch(
        client=client,
        keys={
            "scope": scope,
            "code": code
        }
    )
    # THEN the response is as expected
    expected_response = {
        "id": f"role_{scope}_{code}".lower(),
        "scope": scope,
        "code": code,
        "roleHierarchyIndex": 0,
        "description": "Role with an Id selector",
        "resource": {
            "policyIdRoleResource": {
                "policies": [
                    {
                        "id": f"policy_default_policy-{deployment_name}".lower(),
                        "scope": "default",
                        "code": f"policy-{deployment_name}"
                    }
                ],
                "policyCollections": []
            }
        },
        "when": {
            "activate": "2024-08-31T18:00:00.0000000+00:00",
            "deactivate": "9999-12-31T23:59:59.9999999+00:00"
        },
        "permission": "Read",
    }
    assert response == expected_response
