import os
from types import SimpleNamespace

import pytest
from httpx import HTTPStatusError

import fbnconfig
import tests.integration.recipe as recipe
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)


@pytest.fixture
def setup_deployment():
    deployment_name = gen("recipe")
    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)


@pytest.fixture
def recipe_dict():
    return {
        "market": {
            "marketRules": [
                {
                    "key": "Fx.CurrencyPair.*",
                    "supplier": "DataScope",
                    "dataScope": "SomeScopeToLookAt",
                    "quoteType": "Rate",
                    "field": "Mid",
                    "priceSource": "",
                    "sourceSystem": "Lusid",
                    "fallThroughOnAccessDenied": False
                }
            ],
            "suppliers": {},
            "options": {
                "defaultSupplier": "Lusid",
                "defaultInstrumentCodeType": "LusidInstrumentId",
                "defaultScope": "default",
                "attemptToInferMissingFx": False,
                "calendarScope": "CoppClarkHolidayCalendars",
                "conventionScope": "Conventions"
            },
            "specificRules": [],
            "groupedMarketRules": []
        },
        "pricing": {
            "modelRules": [],
            "modelChoice": {},
            "options": {
                "modelSelection": {
                    "library": "Lusid",
                    "model": "SimpleStatic"
                },
                "useInstrumentTypeToDeterminePricer": False,
                "allowAnyInstrumentsWithSecUidToPriceOffLookup": False,
                "allowPartiallySuccessfulEvaluation": False,
                "produceSeparateResultForLinearOtcLegs": False,
                "enableUseOfCachedUnitResults": False,
                "windowValuationOnInstrumentStartEnd": False,
                "removeContingentCashflowsInPaymentDiary": False,
                "useChildSubHoldingKeysForPortfolioExpansion": False,
                "validateDomesticAndQuoteCurrenciesAreConsistent": False,
                "mbsValuationUsingHoldingCurrentFace": False,
                "convertSrsCashFlowsToPortfolioCurrency": False,
                "conservedQuantityForLookthroughExpansion": "PV",
                "returnZeroPv": {
                    "instrumentMatured": False
                },
                "enableLegLevelInferenceForCustomSrsColumns": False,
                "useInstrumentScaleFactorAsDefault": False
            },
            "resultDataRules": [],
            "holdingPricingInfo": {
                "fallbackField": "None",
                "overrideField": "None",
                "specificFallbacks": [],
                "specificOverrides": []
            }
        },
        "aggregation": {
            "options": {
                "useAnsiLikeSyntax": False,
                "allowPartialEntitlementSuccess": False,
                "applyIso4217Rounding": False
            }
        },
        "description": "",
        "holding": {
            "taxLotLevelHoldings": True
        }
    }


def test_teardown(setup_deployment):
    # create first
    deployment_name = setup_deployment.name
    fbnconfig.deployex(recipe.configure(setup_deployment), lusid_env, token)
    fbnconfig.deployex(fbnconfig.Deployment(deployment_name, []), lusid_env, token)
    with pytest.raises(HTTPStatusError) as error:
        client.get(f"/api/api/recipes/sc1/cd1_{deployment_name}")
    assert error.value.response.status_code == 404
    with pytest.raises(HTTPStatusError) as error:
        client.get(f"/api/api/recipes/sc1/cd2_{deployment_name}")
    assert error.value.response.status_code == 404


def test_create(setup_deployment):
    deployment_name = setup_deployment.name
    fbnconfig.deployex(recipe.configure(setup_deployment), lusid_env, token)
    search_1 = client.get(f"/api/api/recipes/sc1/cd1_{deployment_name}")
    assert search_1.status_code == 200
    search_2 = client.get(f"/api/api/recipes/sc1/cd2_{deployment_name}")
    assert search_2.status_code == 200


def test_update(setup_deployment):
    deployment_name = setup_deployment.name
    fbnconfig.deployex(recipe.configure(setup_deployment), lusid_env, token)
    update = fbnconfig.deployex(recipe.configure(setup_deployment), lusid_env, token)
    search_1 = client.get(f"/api/api/recipes/sc1/cd1_{deployment_name}")
    assert search_1.status_code == 200
    search_2 = client.get(f"/api/api/recipes/sc1/cd2_{deployment_name}")
    assert search_2.status_code == 200
    assert [a.change for a in update if a.type == "RecipeResource"] == ["nochange", "nochange"]


@pytest.mark.asyncio
async def test_recipe_list_with_filter(setup_deployment, recipe_dict):
    # GIVEN Recipe is deployed
    fbnconfig.deployex(recipe.configure(setup_deployment), lusid_env, token)
    scope = "sc1"
    code = f"cd1_{setup_deployment.name}"
    client = fbnconfig.create_client(lusid_env, token, is_async=True)
    # WHEN calling list with filter for the deployment name
    result = await recipe.recipe.RecipeResource.list(
        client=client,
        query_params={"filter": f"value.code eq '{code}'"},
        path_params={}
    )
    # THEN the result has correct structure
    expected_result = {
        "values": [
            {
                "keys": {
                    "scope": scope,
                    "code": code,
                },
                "value": {
                    "id": f"recipe_{scope}_{code}".lower(),
                    "scope": scope,
                    "code": code,
                    "recipe": recipe_dict
                }
            }
        ]
    }
    assert result["values"] == expected_result["values"]


@pytest.mark.asyncio
async def test_recipe_fetch(setup_deployment, recipe_dict):
    # GIVEN Recipe is deployed
    fbnconfig.deployex(recipe.configure(setup_deployment), lusid_env, token)
    scope = "sc1"
    code = f"cd1_{setup_deployment.name}"
    client = fbnconfig.create_client(lusid_env, token, is_async=True)
    # WHEN the fetch method is called for that resource
    response = await recipe.recipe.RecipeResource.fetch(
        client=client,
        keys={
            "scope": scope,
            "code": code
        }
    )
    # THEN the response is as expected
    expected_response = {
        "id": f"recipe_{scope}_{code}".lower(),
        "scope": scope,
        "code": code,
        "recipe": recipe_dict
    }
    assert response == expected_response
