import boto3
import pytest
import sure  # noqa # pylint: disable=unused-import

from botocore.exceptions import ClientError
from moto import mock_apigateway

from .test_apigateway import create_method_integration


@mock_apigateway
def test_create_stage_minimal():
    client = boto3.client("apigateway", region_name="us-west-2")
    stage_name = "staging"
    response = client.create_rest_api(name="my_api", description="this is my api")
    api_id = response["id"]

    create_method_integration(client, api_id)
    response = client.create_deployment(restApiId=api_id, stageName=stage_name)
    deployment_id = response["id"]

    new_stage_name = "current"
    response = client.create_stage(
        restApiId=api_id, stageName=new_stage_name, deploymentId=deployment_id
    )

    response.should.have.key("stageName").equals(new_stage_name)
    response.should.have.key("deploymentId").equals(deployment_id)
    response.should.have.key("methodSettings").equals({})
    response.should.have.key("variables").equals({})
    response.should.have.key("ResponseMetadata").should.have.key(
        "HTTPStatusCode"
    ).equals(201)
    response.should.have.key("description").equals("")
    response.shouldnt.have.key("cacheClusterStatus")
    response.should.have.key("cacheClusterEnabled").equals(False)

    stage = client.get_stage(restApiId=api_id, stageName=new_stage_name)
    stage["stageName"].should.equal(new_stage_name)
    stage["deploymentId"].should.equal(deployment_id)


@mock_apigateway
def test_create_stage_with_env_vars():
    client = boto3.client("apigateway", region_name="us-west-2")
    stage_name = "staging"
    response = client.create_rest_api(name="my_api", description="this is my api")
    api_id = response["id"]

    create_method_integration(client, api_id)
    response = client.create_deployment(restApiId=api_id, stageName=stage_name)
    deployment_id = response["id"]

    new_stage_name_with_vars = "stage_with_vars"
    response = client.create_stage(
        restApiId=api_id,
        stageName=new_stage_name_with_vars,
        deploymentId=deployment_id,
        variables={"env": "dev"},
    )

    response.should.have.key("stageName").equals(new_stage_name_with_vars)
    response.should.have.key("deploymentId").equals(deployment_id)
    response.should.have.key("methodSettings").equals({})
    response.should.have.key("variables").equals({"env": "dev"})
    response.should.have.key("ResponseMetadata").should.have.key(
        "HTTPStatusCode"
    ).equals(201)
    response.should.have.key("description").equals("")
    response.shouldnt.have.key("cacheClusterStatus")
    response.should.have.key("cacheClusterEnabled").equals(False)

    stage = client.get_stage(restApiId=api_id, stageName=new_stage_name_with_vars)
    stage["stageName"].should.equal(new_stage_name_with_vars)
    stage["deploymentId"].should.equal(deployment_id)
    stage["variables"].should.have.key("env").which.should.match("dev")


@mock_apigateway
def test_create_stage_with_vars_and_cache():
    client = boto3.client("apigateway", region_name="us-west-2")
    stage_name = "staging"
    response = client.create_rest_api(name="my_api", description="this is my api")
    api_id = response["id"]

    create_method_integration(client, api_id)
    response = client.create_deployment(restApiId=api_id, stageName=stage_name)
    deployment_id = response["id"]

    new_stage_name = "stage_with_vars_and_cache_settings"
    response = client.create_stage(
        restApiId=api_id,
        stageName=new_stage_name,
        deploymentId=deployment_id,
        variables={"env": "dev"},
        cacheClusterEnabled=True,
        description="hello moto",
    )

    response.should.have.key("stageName").equals(new_stage_name)
    response.should.have.key("deploymentId").equals(deployment_id)
    response.should.have.key("methodSettings").equals({})
    response.should.have.key("variables").equals({"env": "dev"})
    response.should.have.key("ResponseMetadata").should.have.key(
        "HTTPStatusCode"
    ).equals(201)
    response.should.have.key("description").equals("hello moto")
    response.should.have.key("cacheClusterStatus").equals("AVAILABLE")
    response.should.have.key("cacheClusterEnabled").equals(True)
    response.should.have.key("cacheClusterSize").equals("0.5")

    stage = client.get_stage(restApiId=api_id, stageName=new_stage_name)

    stage["cacheClusterSize"].should.equal("0.5")


@mock_apigateway
def test_create_stage_with_cache_settings():
    client = boto3.client("apigateway", region_name="us-west-2")
    stage_name = "staging"
    response = client.create_rest_api(name="my_api", description="this is my api")
    api_id = response["id"]

    create_method_integration(client, api_id)
    response = client.create_deployment(restApiId=api_id, stageName=stage_name)
    deployment_id = response["id"]

    new_stage_name = "stage_with_vars_and_cache_settings_and_size"
    response = client.create_stage(
        restApiId=api_id,
        stageName=new_stage_name,
        deploymentId=deployment_id,
        variables={"env": "dev"},
        cacheClusterEnabled=True,
        cacheClusterSize="1.6",
        tracingEnabled=True,
        description="hello moto",
    )

    response.should.have.key("stageName").equals(new_stage_name)
    response.should.have.key("deploymentId").equals(deployment_id)
    response.should.have.key("methodSettings").equals({})
    response.should.have.key("variables").equals({"env": "dev"})
    response.should.have.key("ResponseMetadata").should.have.key(
        "HTTPStatusCode"
    ).equals(201)
    response.should.have.key("description").equals("hello moto")
    response.should.have.key("cacheClusterStatus").equals("AVAILABLE")
    response.should.have.key("cacheClusterEnabled").equals(True)
    response.should.have.key("cacheClusterSize").equals("1.6")
    response.should.have.key("tracingEnabled").equals(True)

    stage = client.get_stage(restApiId=api_id, stageName=new_stage_name)
    stage["stageName"].should.equal(new_stage_name)
    stage["deploymentId"].should.equal(deployment_id)
    stage["variables"].should.have.key("env").which.should.match("dev")
    stage["cacheClusterSize"].should.equal("1.6")


@mock_apigateway
def test_recreate_stage_from_deployment():
    client = boto3.client("apigateway", region_name="us-west-2")
    stage_name = "staging"
    response = client.create_rest_api(name="my_api", description="this is my api")
    api_id = response["id"]
    create_method_integration(client, api_id)
    depl_id1 = client.create_deployment(restApiId=api_id, stageName=stage_name)["id"]

    with pytest.raises(ClientError) as exc:
        client.create_stage(
            restApiId=api_id, stageName=stage_name, deploymentId=depl_id1
        )
    err = exc.value.response["Error"]
    err["Code"].should.equal("ConflictException")
    err["Message"].should.equal("Stage already exists")


@mock_apigateway
def test_create_stage_twice():
    client = boto3.client("apigateway", region_name="us-west-2")
    stage_name = "staging"
    response = client.create_rest_api(name="my_api", description="this is my api")
    api_id = response["id"]
    create_method_integration(client, api_id)
    depl_id1 = client.create_deployment(restApiId=api_id, stageName=stage_name)["id"]

    new_stage_name = "current"
    client.create_stage(
        restApiId=api_id, stageName=new_stage_name, deploymentId=depl_id1
    )

    with pytest.raises(ClientError) as exc:
        client.create_stage(
            restApiId=api_id, stageName=new_stage_name, deploymentId=depl_id1
        )
    err = exc.value.response["Error"]
    err["Code"].should.equal("ConflictException")
    err["Message"].should.equal("Stage already exists")


@mock_apigateway
def test_delete_stage():
    client = boto3.client("apigateway", region_name="us-west-2")
    stage_name = "staging"
    response = client.create_rest_api(name="my_api", description="this is my api")
    api_id = response["id"]
    create_method_integration(client, api_id)
    depl_id1 = client.create_deployment(restApiId=api_id, stageName=stage_name)["id"]
    depl_id2 = client.create_deployment(restApiId=api_id, stageName=stage_name)["id"]

    new_stage_name = "current"
    client.create_stage(
        restApiId=api_id, stageName=new_stage_name, deploymentId=depl_id1
    )

    new_stage_name_with_vars = "stage_with_vars"
    client.create_stage(
        restApiId=api_id,
        stageName=new_stage_name_with_vars,
        deploymentId=depl_id2,
        variables={"env": "dev"},
    )
    stages = client.get_stages(restApiId=api_id)["item"]
    stage_names = [stage["stageName"] for stage in stages]
    stage_names.should.have.length_of(3)
    stage_names.should.contain(stage_name)
    stage_names.should.contain(new_stage_name)
    stage_names.should.contain(new_stage_name_with_vars)

    # delete stage
    response = client.delete_stage(restApiId=api_id, stageName=new_stage_name_with_vars)
    response["ResponseMetadata"]["HTTPStatusCode"].should.equal(202)

    # verify other stage still exists
    stages = client.get_stages(restApiId=api_id)["item"]
    stage_names = [stage["stageName"] for stage in stages]
    stage_names.should.have.length_of(2)
    stage_names.shouldnt.contain(new_stage_name_with_vars)


@mock_apigateway
def test_delete_stage_created_by_deployment():
    client = boto3.client("apigateway", region_name="us-west-2")
    stage_name = "staging"
    response = client.create_rest_api(name="my_api", description="this is my api")
    api_id = response["id"]
    create_method_integration(client, api_id)
    depl_id1 = client.create_deployment(restApiId=api_id, stageName=stage_name)["id"]

    # Sanity check that the deployment exists
    depls = client.get_deployments(restApiId=api_id)["items"]
    depls.should.have.length_of(1)
    set(depls[0].keys()).should.equal({"id", "createdDate"})

    # Sanity check that the stage exists
    stage = client.get_stages(restApiId=api_id)["item"][0]
    stage.should.have.key("deploymentId").equals(depl_id1)
    stage.should.have.key("stageName").equals(stage_name)

    # delete stage
    response = client.delete_stage(restApiId=api_id, stageName=stage_name)
    response["ResponseMetadata"]["HTTPStatusCode"].should.equal(202)

    # verify no stage exists
    stages = client.get_stages(restApiId=api_id)
    stages.should.have.key("item").equals([])

    # verify deployment still exists, unchanged
    depls = client.get_deployments(restApiId=api_id)["items"]
    depls.should.have.length_of(1)
    set(depls[0].keys()).should.equal({"id", "createdDate"})


@mock_apigateway
def test_delete_stage_unknown_stage():
    client = boto3.client("apigateway", region_name="us-west-2")
    response = client.create_rest_api(name="my_api", description="this is my api")
    api_id = response["id"]
    with pytest.raises(ClientError) as exc:
        client.delete_stage(restApiId=api_id, stageName="unknown")
    err = exc.value.response["Error"]
    err["Message"].should.equal("Invalid stage identifier specified")
    err["Code"].should.equal("NotFoundException")


@mock_apigateway
def test_update_stage_configuration():
    client = boto3.client("apigateway", region_name="us-west-2")
    stage_name = "staging"
    response = client.create_rest_api(name="my_api", description="this is my api")
    api_id = response["id"]
    create_method_integration(client, api_id)

    response = client.create_deployment(
        restApiId=api_id, stageName=stage_name, description="1.0.1"
    )
    deployment_id = response["id"]

    response = client.get_deployment(restApiId=api_id, deploymentId=deployment_id)

    response.should.have.key("id").equals(deployment_id)
    response.should.have.key("ResponseMetadata").should.have.key(
        "HTTPStatusCode"
    ).equals(200)
    response.should.have.key("description").equals("1.0.1")

    response = client.create_deployment(
        restApiId=api_id, stageName=stage_name, description="1.0.2"
    )
    deployment_id2 = response["id"]

    stage = client.get_stage(restApiId=api_id, stageName=stage_name)
    stage["stageName"].should.equal(stage_name)
    stage["deploymentId"].should.equal(deployment_id2)
    stage.shouldnt.have.key("cacheClusterSize")
    stage.shouldnt.have.key("cacheClusterStatus")

    client.update_stage(
        restApiId=api_id,
        stageName=stage_name,
        patchOperations=[
            {"op": "replace", "path": "/cacheClusterEnabled", "value": "True"}
        ],
    )

    stage = client.get_stage(restApiId=api_id, stageName=stage_name)

    stage.should.have.key("cacheClusterSize").which.should.equal("0.5")
    stage.should.have.key("cacheClusterStatus").equals("AVAILABLE")

    client.update_stage(
        restApiId=api_id,
        stageName=stage_name,
        patchOperations=[
            {"op": "replace", "path": "/cacheClusterSize", "value": "1.6"}
        ],
    )

    stage = client.get_stage(restApiId=api_id, stageName=stage_name)

    stage.should.have.key("cacheClusterSize").which.should.equal("1.6")

    client.update_stage(
        restApiId=api_id,
        stageName=stage_name,
        patchOperations=[
            {"op": "replace", "path": "/deploymentId", "value": deployment_id},
            {"op": "replace", "path": "/variables/environment", "value": "dev"},
            {"op": "replace", "path": "/variables/region", "value": "eu-west-1"},
            {"op": "replace", "path": "/*/*/caching/dataEncrypted", "value": "True"},
            {"op": "replace", "path": "/cacheClusterEnabled", "value": "True"},
            {
                "op": "replace",
                "path": "/description",
                "value": "stage description update",
            },
            {"op": "replace", "path": "/cacheClusterSize", "value": "1.6"},
        ],
    )

    client.update_stage(
        restApiId=api_id,
        stageName=stage_name,
        patchOperations=[
            {"op": "remove", "path": "/variables/region", "value": "eu-west-1"}
        ],
    )

    stage = client.get_stage(restApiId=api_id, stageName=stage_name)

    stage["description"].should.match("stage description update")
    stage["cacheClusterSize"].should.equal("1.6")
    stage["variables"]["environment"].should.match("dev")
    stage["variables"].should_not.have.key("region")
    stage["cacheClusterEnabled"].should.equal(True)
    stage["deploymentId"].should.match(deployment_id)
    stage["methodSettings"].should.have.key("*/*")
    stage["methodSettings"]["*/*"].should.have.key(
        "cacheDataEncrypted"
    ).which.should.be.true


@mock_apigateway
def test_update_stage_add_access_log_settings():
    client = boto3.client("apigateway", region_name="us-west-2")
    stage_name = "staging"
    response = client.create_rest_api(name="my_api", description="this is my api")
    api_id = response["id"]
    create_method_integration(client, api_id)

    client.create_deployment(
        restApiId=api_id, stageName=stage_name, description="1.0.1"
    )

    client.update_stage(
        restApiId=api_id,
        stageName=stage_name,
        patchOperations=[
            {
                "op": "replace",
                "path": "/accessLogSettings/destinationArn",
                "value": "arn:aws:logs:us-east-1:123456789012:log-group:foo-bar-x0hyv",
            },
            {
                "op": "replace",
                "path": "/accessLogSettings/format",
                "value": "$context.identity.sourceIp msg",
            },
        ],
    )

    stage = client.get_stage(restApiId=api_id, stageName=stage_name)
    stage.should.have.key("accessLogSettings").equals(
        {
            "format": "$context.identity.sourceIp msg",
            "destinationArn": "arn:aws:logs:us-east-1:123456789012:log-group:foo-bar-x0hyv",
        }
    )


@mock_apigateway
def test_update_stage_tracing_disabled():
    client = boto3.client("apigateway", region_name="us-west-2")
    stage_name = "staging"
    response = client.create_rest_api(name="my_api", description="this is my api")
    api_id = response["id"]
    create_method_integration(client, api_id)
    response = client.create_deployment(restApiId=api_id, stageName=stage_name)

    client.update_stage(
        restApiId=api_id,
        stageName=stage_name,
        patchOperations=[
            {"op": "replace", "path": "/tracingEnabled", "value": "false"}
        ],
    )

    stage = client.get_stage(restApiId=api_id, stageName=stage_name)
    stage.should.have.key("tracingEnabled").equals(False)

    client.update_stage(
        restApiId=api_id,
        stageName=stage_name,
        patchOperations=[{"op": "replace", "path": "/tracingEnabled", "value": "true"}],
    )

    stage = client.get_stage(restApiId=api_id, stageName=stage_name)
    stage.should.have.key("tracingEnabled").equals(True)


@mock_apigateway
def test_update_stage_remove_access_log_settings():
    client = boto3.client("apigateway", region_name="us-west-2")
    stage_name = "staging"
    response = client.create_rest_api(name="my_api", description="this is my api")
    api_id = response["id"]
    create_method_integration(client, api_id)

    client.create_deployment(
        restApiId=api_id, stageName=stage_name, description="1.0.1"
    )

    client.update_stage(
        restApiId=api_id,
        stageName=stage_name,
        patchOperations=[{"op": "remove", "path": "/accessLogSettings"}],
    )

    stage = client.get_stage(restApiId=api_id, stageName=stage_name)
    stage.shouldnt.have.key("accessLogSettings")


@mock_apigateway
def test_update_stage_configuration_unknown_operation():
    client = boto3.client("apigateway", region_name="us-west-2")
    stage_name = "staging"
    response = client.create_rest_api(name="my_api", description="this is my api")
    api_id = response["id"]
    create_method_integration(client, api_id)

    client.create_deployment(
        restApiId=api_id, stageName=stage_name, description="1.0.1"
    )

    with pytest.raises(ClientError) as exc:
        client.update_stage(
            restApiId=api_id,
            stageName=stage_name,
            patchOperations=[
                {"op": "unknown_op", "path": "/notasetting", "value": "eu-west-1"}
            ],
        )
    err = exc.value.response["Error"]
    err["Code"].should.equal("ValidationException")
    err["Message"].should.equal(
        "Member must satisfy enum value set: [add, remove, move, test, replace, copy]"
    )


@mock_apigateway
def test_non_existent_stage():
    client = boto3.client("apigateway", region_name="us-west-2")
    response = client.create_rest_api(name="my_api", description="this is my api")
    api_id = response["id"]

    with pytest.raises(ClientError) as exc:
        client.get_stage(restApiId=api_id, stageName="xxx")
    err = exc.value.response["Error"]
    err["Code"].should.equal("NotFoundException")
