import boto3
import pytest

from botocore.exceptions import ClientError
from moto import mock_budgets
from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID


@mock_budgets
def test_create_and_describe_budget_minimal_params():
    client = boto3.client("budgets", region_name="us-east-1")
    resp = client.create_budget(
        AccountId=ACCOUNT_ID,
        Budget={
            "BudgetLimit": {"Amount": "10", "Unit": "USD"},
            "BudgetName": "testbudget",
            "TimeUnit": "DAILY",
            "BudgetType": "COST",
        },
    )
    assert resp["ResponseMetadata"]["HTTPStatusCode"] == 200

    try:
        budget = client.describe_budget(AccountId=ACCOUNT_ID, BudgetName="testbudget")[
            "Budget"
        ]
    except OverflowError:
        pytest.skip("This test requires 64-bit time_t")
    assert "BudgetLimit" in budget
    assert "Amount" in budget["BudgetLimit"]
    assert budget["BudgetLimit"]["Amount"] == "10"
    assert "Unit" in budget["BudgetLimit"]
    assert budget["BudgetLimit"]["Unit"] == "USD"
    assert budget["BudgetName"] == "testbudget"
    assert budget["TimeUnit"] == "DAILY"
    assert budget["BudgetType"] == "COST"
    assert "CalculatedSpend" in budget
    assert "ActualSpend" in budget["CalculatedSpend"]
    assert budget["CalculatedSpend"]["ActualSpend"] == {"Amount": "0", "Unit": "USD"}
    assert "ForecastedSpend" in budget["CalculatedSpend"]
    assert budget["CalculatedSpend"]["ForecastedSpend"] == {
        "Amount": "0",
        "Unit": "USD",
    }
    assert "CostTypes" in budget
    assert budget["CostTypes"]["IncludeCredit"] is True
    assert budget["CostTypes"]["IncludeDiscount"] is True
    assert budget["CostTypes"]["IncludeOtherSubscription"] is True
    assert budget["CostTypes"]["IncludeRecurring"] is True
    assert budget["CostTypes"]["IncludeRefund"] is True
    assert budget["CostTypes"]["IncludeSubscription"] is True
    assert budget["CostTypes"]["IncludeSupport"] is True
    assert budget["CostTypes"]["IncludeTax"] is True
    assert budget["CostTypes"]["IncludeUpfront"] is True
    assert budget["CostTypes"]["UseAmortized"] is False
    assert budget["CostTypes"]["UseBlended"] is False
    assert "LastUpdatedTime" in budget
    assert "TimePeriod" in budget
    assert "Start" in budget["TimePeriod"]
    assert budget["TimePeriod"]["Start"]
    assert "End" in budget["TimePeriod"]
    assert budget["TimePeriod"]["End"]
    assert budget["TimeUnit"] == "DAILY"


@mock_budgets
def test_create_existing_budget():
    client = boto3.client("budgets", region_name="us-east-1")
    client.create_budget(
        AccountId=ACCOUNT_ID,
        Budget={
            "BudgetLimit": {"Amount": "10", "Unit": "USD"},
            "BudgetName": "testb",
            "TimeUnit": "DAILY",
            "BudgetType": "COST",
        },
    )

    with pytest.raises(ClientError) as exc:
        client.create_budget(
            AccountId=ACCOUNT_ID,
            Budget={
                "BudgetLimit": {"Amount": "10", "Unit": "USD"},
                "BudgetName": "testb",
                "TimeUnit": "DAILY",
                "BudgetType": "COST",
            },
        )
    err = exc.value.response["Error"]
    assert err["Code"] == "DuplicateRecordException"
    assert err["Message"] == "Error creating budget: testb - the budget already exists."


@mock_budgets
def test_create_budget_without_limit_param():
    client = boto3.client("budgets", region_name="us-east-1")
    with pytest.raises(ClientError) as exc:
        client.create_budget(
            AccountId=ACCOUNT_ID,
            Budget={"BudgetName": "testb", "TimeUnit": "DAILY", "BudgetType": "COST"},
        )
    err = exc.value.response["Error"]
    assert err["Code"] == "InvalidParameterException"
    assert (
        err["Message"]
        == "Unable to create/update budget - please provide one of the followings: Budget Limit/ Planned Budget Limit/ Auto Adjust Data"
    )


@mock_budgets
def test_describe_unknown_budget():
    client = boto3.client("budgets", region_name="us-east-1")
    with pytest.raises(ClientError) as exc:
        client.describe_budget(AccountId=ACCOUNT_ID, BudgetName="unknown")
    err = exc.value.response["Error"]
    assert err["Code"] == "NotFoundException"
    assert err["Message"] == "Unable to get budget: unknown - the budget doesn't exist."


@mock_budgets
def test_describe_no_budgets():
    client = boto3.client("budgets", region_name="us-east-1")
    resp = client.describe_budgets(AccountId=ACCOUNT_ID)
    assert resp["Budgets"] == []


@mock_budgets
def test_create_and_describe_all_budgets():
    client = boto3.client("budgets", region_name="us-east-1")
    client.create_budget(
        AccountId=ACCOUNT_ID,
        Budget={
            "BudgetLimit": {"Amount": "10", "Unit": "USD"},
            "BudgetName": "testbudget",
            "TimeUnit": "DAILY",
            "BudgetType": "COST",
        },
    )

    try:
        res = client.describe_budgets(AccountId=ACCOUNT_ID)
    except OverflowError:
        pytest.skip("This test requires 64-bit time_t")
    assert len(res["Budgets"]) == 1


@mock_budgets
def test_delete_budget():
    client = boto3.client("budgets", region_name="us-east-1")
    client.create_budget(
        AccountId=ACCOUNT_ID,
        Budget={
            "BudgetLimit": {"Amount": "10", "Unit": "USD"},
            "BudgetName": "b1",
            "TimeUnit": "DAILY",
            "BudgetType": "COST",
        },
    )

    resp = client.delete_budget(AccountId=ACCOUNT_ID, BudgetName="b1")
    assert resp["ResponseMetadata"]["HTTPStatusCode"] == 200

    res = client.describe_budgets(AccountId=ACCOUNT_ID)
    assert len(res["Budgets"]) == 0


@mock_budgets
def test_delete_unknown_budget():
    client = boto3.client("budgets", region_name="us-east-1")
    with pytest.raises(ClientError) as exc:
        client.delete_budget(AccountId=ACCOUNT_ID, BudgetName="unknown")
    err = exc.value.response["Error"]
    assert err["Code"] == "NotFoundException"
    assert (
        err["Message"]
        == "Unable to delete budget: unknown - the budget doesn't exist. Try creating it first. "
    )
