import os
from types import SimpleNamespace

import pytest

import fbnconfig
from fbnconfig import Deployment, identity
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 = {}


def resources(deployment_name):
    return [
        identity.ApplicationResource(
            id="test_app",
            client_id=f"inttest-app-{deployment_name}",
            display_name=f"Application{deployment_name}",
            type=identity.ApplicationType.NATIVE,
        )
    ]


# this cant be scope=module, it needs to be unique per test
@pytest.fixture
def setup_deployment():
    deployment_name = gen("app", length=6)
    print(f"\nRunning for deployment {deployment_name}...")
    fbnconfig.deployex(Deployment(deployment_name, resources(deployment_name)), lusid_env, token)
    yield SimpleNamespace(name=deployment_name)
    try:
        print("\nTearing down resources...")
        fbnconfig.deployex(fbnconfig.Deployment(deployment_name, []), lusid_env, token)
    except Exception:
        pass  # Ignore cleanup errors


@pytest.fixture
def deployment(setup_deployment):
    deployment_name = setup_deployment.name
    return Deployment(deployment_name, resources(deployment_name))


def test_teardown(setup_deployment):
    deployment_name = setup_deployment.name
    fbnconfig.deployex(fbnconfig.Deployment(deployment_name, []), lusid_env, token)
    client = fbnconfig.create_client(lusid_env, token)
    matches = get_applications_by_client_id(client, f"inttest-app-{deployment_name}")
    assert len(matches) == 0


def test_create(setup_deployment, deployment):
    fbnconfig.deployex(deployment, lusid_env, token)
    client = fbnconfig.create_client(lusid_env, token)
    matches = get_applications_by_client_id(client, f"inttest-app-{setup_deployment.name}")
    assert len(matches) == 1
    app = matches[0]
    assert app["displayName"] == f"Application{setup_deployment.name}"
    assert app["type"] == "Native"


def test_update(setup_deployment, deployment):
    fbnconfig.deployex(deployment, lusid_env, token)
    client = fbnconfig.create_client(lusid_env, token)
    matches = get_applications_by_client_id(client, f"inttest-app-{setup_deployment.name}")
    assert len(matches) == 1
    app = matches[0]
    assert app["displayName"] == f"Application{setup_deployment.name}"
    assert app["type"] == "Native"


def get_applications_by_client_id(client, client_id):
    get = client.request("get", "/identity/api/applications")
    get.raise_for_status()
    applications = get.json()
    return [app for app in applications if app["clientId"] == client_id]


@pytest.mark.asyncio
async def test_application_list(setup_deployment, deployment):
    # GIVEN Application is deployed
    fbnconfig.deployex(deployment, lusid_env, token)
    deployment_name = setup_deployment.name
    client_id = f"inttest-app-{deployment_name}"
    client = fbnconfig.create_client(lusid_env, token, is_async=True)
    # WHEN calling list method for that resource
    result = await identity.ApplicationResource.list(
        client=client,
        query_params={},
        path_params={}
    )
    application = next(
        (i for i in result["values"] if i["value"]["clientId"] == client_id)
    )
    application_id = application["value"]["applicationId"]
    application_issuer = application["value"]["issuer"]
    # THEN the result has correct structure
    expected_application = {
        "keys": {
            "applicationId": application_id,
        },
        "value": {
            "id": f"application_{application_id}".lower(),
            "applicationId": application_id,
            "clientId": client_id,
            "type": "Native",
            "displayName": f"Application{deployment_name}",
            "issuer": application_issuer,
            "secret": None
        }
    }
    assert application == expected_application


@pytest.mark.asyncio
async def test_application_fetch(setup_deployment, deployment):
    # GIVEN Application is deployed
    fbnconfig.deployex(deployment, lusid_env, token)
    deployment_name = setup_deployment.name
    client_id = f"inttest-app-{deployment_name}"
    client = fbnconfig.create_client(lusid_env, token, is_async=True)
    applications_response = await client.get("/identity/api/applications")
    applications = applications_response.json()
    application = next(
        (app for app in applications if app["clientId"] == client_id)
    )
    application_id = application["id"]
    application_issuer = application["issuer"]
    # WHEN the fetch method is called for that resource
    response = await identity.ApplicationResource.fetch(
        client=client,
        keys={
            "applicationId": application_id,
        }
    )
    # THEN the response is as expected
    expected_response = {
        "id": f"application_{application_id}".lower(),
        "applicationId": application_id,
        "clientId": client_id,
        "type": "Native",
        "displayName": f"Application{deployment_name}",
        "issuer": application_issuer,
        "secret": None
    }
    assert response == expected_response
