import json

import pytest
import utc

from mekansm import core
from mekansm import errors


@pytest.fixture
def dep_data():
    return {
        "triggered_by": "Kappa",
        "id": "d-minglee",
        "repository": "Mekansm",
        "sha": "KappaKappaKappaKappa",
        "environment": "testing",
        "owner": "dta",
        "account": "aws-account-id",
        "application": "dta-Mekansm",
        "group": "dta-Mekansm-testing",
        "s3location": "myartifacts/dta/Mekansm",
        "bundletype": "tgz",
        "config": "CodeDeployDefault.AllAtOnce"
    }


def dummy_context():
    return object()


def test_deployment_serialization_has_valid_keys(dep_data):
    d = core.Deployment(dummy_context(), **dep_data)
    got = d.as_dict()["attributes"].keys()
    assert set(got) == set(dep_data.keys()) - {"id"}


def test_deployment_is_serializable_by_encoder(dep_data):
    d = core.Deployment(dummy_context(), **dep_data)
    assert len(json.dumps(d.as_dict(), cls=core.MekansmJSONEncoder)) > 0


def test_deployment_error_with_missing_fields(dep_data):
    dep_data.pop("repository")
    with pytest.raises(errors.InvalidDeploymentFields) as exc:
        core.Deployment(dummy_context(), **dep_data)
    missing = exc.value.missing_fields
    assert "repository" in missing and len(missing) == 1
    assert not exc.value.unknown_fields


def test_deployment_error_with_unknown_fields(dep_data):
    dep_data["kappa"] = "minglee"
    with pytest.raises(errors.InvalidDeploymentFields) as exc:
        core.Deployment(dummy_context(), **dep_data)
    unknown = exc.value.unknown_fields
    assert "kappa" in unknown and len(unknown) == 1
    assert not exc.value.missing_fields


def test_deployment_error_with_missing_and_unknown_fields(dep_data):
    dep_data.pop("repository")
    dep_data["kappa"] = "minglee"
    with pytest.raises(errors.InvalidDeploymentFields) as exc:
        core.Deployment(dummy_context(), **dep_data)
    missing = exc.value.missing_fields
    unknown = exc.value.unknown_fields
    assert "repository" in missing and len(missing) == 1
    assert "kappa" in unknown and len(unknown) == 1


def test_none_is_invalid_deployment_id():
    assert not core.Deployment.is_valid_id(None)


def test_integer_is_invalid_deployment_id():
    assert not core.Deployment.is_valid_id(1234)


def test_string_is_invalid_deployment_id():
    assert core.Deployment.is_valid_id("abcd")


def test_get_deployment_with_invalid_id():
    with pytest.raises(errors.InvalidDeploymentId) as exc:
        core.Deployment.get(dummy_context(), 42)
    assert "42" in exc.value.message


def test_get_nonexistant_deployment():

    class StubCtx:
        def get_deployment(self, deployment_id):
            assert deployment_id == "d-Kappa"
            return None

    with pytest.raises(errors.DeploymentNotFound) as exc:
        core.Deployment.get(StubCtx(), "d-Kappa")
    assert "d-Kappa" in exc.value.message


def test_get_deployment(dep_data):

    class StubCtx:
        def get_deployment(self, deployment_id):
            assert deployment_id == "d-Kappa"
            return dep_data

    dep = core.Deployment.get(StubCtx(), "d-Kappa")
    assert isinstance(dep, core.Deployment)
    assert dep.data == dep_data


def test_deployment_find_by(dep_data):
    data = [dep_data]
    filter_obj_value = {"deployment": "d-Kappa"}

    class StubCtx:
        def get_deployments(self, filter_obj):
            assert filter_obj == filter_obj_value
            return data

    deps = core.Deployment.find_by(StubCtx(), filter_obj_value)
    assert [dep.data for dep in deps] == data


def test_create_deployment(dep_data):
    nodes_data = [
        {"id": "i-1", "datacenter": "d-1"},
        {"id": "i-2", "datacenter": "d-2"}
    ]
    data = dep_data.copy()
    data["nodes"] = nodes_data

    class StubCtx:
        def create_deployment(self, deployment_data, nodes):
            assert deployment_data == dep_data
            assert nodes == nodes_data
            return data

    dep = core.Deployment.create(StubCtx(), **data)
    assert dep.data == dep_data
    assert [node.data for node in dep.nodes] == nodes_data


def test_deployment_set_status(dep_data):

    class StubCtx:
        def set_deployment_status(self, deployment_id, status):
            assert deployment_id == dep_data["id"]
            assert status == "Failed"

    dep = core.Deployment(StubCtx(), **dep_data)
    dep.set_status("Failed")
    assert dep["status"] == "Failed"


def test_deployment_instances_statuses(dep_data):
    nodes = [{"id": "i-1"}]

    class StubCtx:
        def get_nodes(self, filter_obj):
            assert filter_obj == {"deployment": dep_data["id"]}
            return nodes

        def get_deployment_instance_status(self, deployment_id, node_id):
            assert deployment_id == dep_data["id"]
            assert node_id == "i-1"
            return {
                "node": node_id,
                "deployment": deployment_id,
                "update_time": utc.now(),
                "status": "Failed"
            }

        def get_deployment_nodes_status(self, deployment_id):
            return [
                {
                    "node": "i-1",
                    "status": "Failed",
                    "lastUpdatedAt": "soon",
                    "lifecycleEvents": []
                }
            ]

    dep = core.Deployment(StubCtx(), **dep_data)
    assert ["Failed"] == [ins_s["status"] for ins_s in dep.instances_statuses]


def test_get_node_with_invalid_id():
    with pytest.raises(errors.InvalidNodeId) as exc:
        core.Node.get(dummy_context(), 42)
    assert "42" in exc.value.message


def test_get_nonexistant_node():

    class StubCtx:
        def get_node(self, node_id):
            assert node_id == "i-Kappa"
            return None

    with pytest.raises(errors.NodeNotFound) as exc:
        core.Node.get(StubCtx(), "i-Kappa")
    assert "i-Kappa" in exc.value.message


def test_get_node():
    node_data = {"id": "i-1", "datacenter": "dc-1"}

    class StubCtx:
        def get_node(self, node_id):
            assert node_id == "i-Kappa"
            return node_data

    node = core.Node.get(StubCtx(), "i-Kappa")
    assert isinstance(node, core.Node)
    assert node.data == node_data


def test_node_find_by():
    data = [{"id": "i-1", "datacenter": "dc-1"}]
    filter_obj_value = {"deployment": "d-Kappa"}

    class StubCtx:
        def get_nodes(self, filter_obj):
            assert filter_obj == filter_obj_value
            return data

    nodes = core.Node.find_by(StubCtx(), filter_obj_value)
    assert [node.data for node in nodes] == data


def test_node_deployments(dep_data):
    node_data = {"id": "i-1", "datacenter": "dc-1"}

    class StubCtx:
        def get_deployments(self, filter_obj):
            assert filter_obj == {"owner": "Kappa", "node": "i-1"}
            return [dep_data]

    node = core.Node(StubCtx(), **node_data)
    got = node.deployments({"owner": "Kappa"})
    assert len(got) == 1
    assert got.pop()["id"] == dep_data["id"]


def test_get_nonexistant_nodestatus():

    class StubCtx:
        def get_deployment_instance_status(self, deployment_id, node_id):
            assert deployment_id == "d-Kappa"
            assert node_id == "i-1"
            return None

    with pytest.raises(errors.NodeStatusNotFound) as exc:
        core.NodeStatus.get(StubCtx(), "d-Kappa", "i-1")
    assert "d-Kappa" in exc.value.message
    assert "i-1" in exc.value.message


def test_get_nodestatus():
    ns_data = {"deployment": "d-Kappa", "node": "i-1", "status": "Failed"}

    class StubCtx:
        def get_deployment_instance_status(self, deployment_id, node_id):
            assert deployment_id == "d-Kappa"
            assert node_id == "i-1"
            return ns_data

    ns = core.NodeStatus.get(StubCtx(), "d-Kappa", "i-1")
    assert isinstance(ns, core.NodeStatus)
    assert ns.data == ns_data
