# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
import json
from unittest.mock import Mock, call

from libcloud.test import unittest
from libcloud.compute.base import NodeSize, NodeImage, NodeLocation, NodeAuthSSHKey
from libcloud.common.upcloud import (
    PlanPrice,
    UpcloudNodeDestroyer,
    UpcloudNodeOperations,
    UpcloudTimeoutException,
    UpcloudCreateNodeRequestBody,
    _StorageDevice,
)


class TestUpcloudCreateNodeRequestBody(unittest.TestCase):
    def setUp(self):
        self.image = NodeImage(
            id="01000000-0000-4000-8000-000030060200",
            name="Ubuntu Server 16.04 LTS (Xenial Xerus)",
            driver="",
            extra={"type": "template"},
        )
        self.location = NodeLocation(id="fi-hel1", name="Helsinki #1", country="FI", driver="")
        self.size = NodeSize(
            id="1xCPU-1GB",
            name="1xCPU-1GB",
            ram=1024,
            disk=30,
            bandwidth=2048,
            extra={"core_number": 1, "storage_tier": "maxiops"},
            price=None,
            driver="",
        )

    def test_creating_node_from_template_image(self):
        body = UpcloudCreateNodeRequestBody(
            name="ts", image=self.image, location=self.location, size=self.size
        )
        json_body = body.to_json()
        dict_body = json.loads(json_body)
        expected_body = {
            "server": {
                "title": "ts",
                "hostname": "localhost",
                "plan": "1xCPU-1GB",
                "zone": "fi-hel1",
                "login_user": {"username": "root", "create_password": "yes"},
                "storage_devices": {
                    "storage_device": [
                        {
                            "action": "clone",
                            "title": "Ubuntu Server 16.04 LTS (Xenial Xerus)",
                            "storage": "01000000-0000-4000-8000-000030060200",
                            "size": 30,
                            "tier": "maxiops",
                        }
                    ]
                },
            }
        }
        self.assertDictEqual(expected_body, dict_body)

    def test_creating_node_from_cdrom_image(self):
        image = NodeImage(
            id="01000000-0000-4000-8000-000030060200",
            name="Ubuntu Server 16.04 LTS (Xenial Xerus)",
            driver="",
            extra={"type": "cdrom"},
        )
        body = UpcloudCreateNodeRequestBody(
            name="ts", image=image, location=self.location, size=self.size
        )
        json_body = body.to_json()
        dict_body = json.loads(json_body)
        expected_body = {
            "server": {
                "title": "ts",
                "hostname": "localhost",
                "plan": "1xCPU-1GB",
                "zone": "fi-hel1",
                "login_user": {"username": "root", "create_password": "yes"},
                "storage_devices": {
                    "storage_device": [
                        {
                            "action": "create",
                            "size": 30,
                            "tier": "maxiops",
                            "title": "Ubuntu Server 16.04 LTS (Xenial Xerus)",
                        },
                        {
                            "action": "attach",
                            "storage": "01000000-0000-4000-8000-000030060200",
                            "type": "cdrom",
                        },
                    ]
                },
            }
        }
        self.assertDictEqual(expected_body, dict_body)

    def test_creating_node_using_ssh_keys(self):
        auth = NodeAuthSSHKey("sshkey")

        body = UpcloudCreateNodeRequestBody(
            name="ts",
            image=self.image,
            location=self.location,
            size=self.size,
            auth=auth,
        )
        json_body = body.to_json()
        dict_body = json.loads(json_body)
        expected_body = {
            "server": {
                "title": "ts",
                "hostname": "localhost",
                "plan": "1xCPU-1GB",
                "zone": "fi-hel1",
                "login_user": {
                    "username": "root",
                    "ssh_keys": {"ssh_key": ["sshkey"]},
                },
                "storage_devices": {
                    "storage_device": [
                        {
                            "action": "clone",
                            "size": 30,
                            "title": "Ubuntu Server 16.04 LTS (Xenial Xerus)",
                            "tier": "maxiops",
                            "storage": "01000000-0000-4000-8000-000030060200",
                        }
                    ]
                },
            }
        }
        self.assertDictEqual(expected_body, dict_body)

    def test_creating_node_using_hostname(self):
        body = UpcloudCreateNodeRequestBody(
            name="ts",
            image=self.image,
            location=self.location,
            size=self.size,
            ex_hostname="myhost.upcloud.com",
        )
        json_body = body.to_json()
        dict_body = json.loads(json_body)
        expected_body = {
            "server": {
                "title": "ts",
                "hostname": "myhost.upcloud.com",
                "plan": "1xCPU-1GB",
                "zone": "fi-hel1",
                "login_user": {"username": "root", "create_password": "yes"},
                "storage_devices": {
                    "storage_device": [
                        {
                            "action": "clone",
                            "title": "Ubuntu Server 16.04 LTS (Xenial Xerus)",
                            "storage": "01000000-0000-4000-8000-000030060200",
                            "tier": "maxiops",
                            "size": 30,
                        }
                    ]
                },
            }
        }
        self.assertDictEqual(expected_body, dict_body)

    def test_creating_node_with_non_default_username(self):
        body = UpcloudCreateNodeRequestBody(
            name="ts",
            image=self.image,
            location=self.location,
            size=self.size,
            ex_username="someone",
        )
        json_body = body.to_json()
        dict_body = json.loads(json_body)

        login_user = dict_body["server"]["login_user"]
        self.assertDictEqual({"username": "someone", "create_password": "yes"}, login_user)


class TestStorageDevice(unittest.TestCase):
    def setUp(self):
        self.image = NodeImage(
            id="01000000-0000-4000-8000-000030060200",
            name="Ubuntu Server 16.04 LTS (Xenial Xerus)",
            driver="",
            extra={"type": "template"},
        )
        self.size = NodeSize(
            id="1xCPU-1GB",
            name="1xCPU-1GB",
            ram=1024,
            disk=30,
            bandwidth=2048,
            extra={"core_number": 1},
            price=None,
            driver="",
        )

    def test_storage_tier_default_value(self):
        storagedevice = _StorageDevice(self.image, self.size)
        d = storagedevice.to_dict()

        self.assertEqual(d["storage_device"][0]["tier"], "maxiops")

    def test_storage_tier_given(self):
        self.size.extra["storage_tier"] = "hdd"
        storagedevice = _StorageDevice(self.image, self.size)
        d = storagedevice.to_dict()

        self.assertEqual(d["storage_device"][0]["tier"], "hdd")


class TestUpcloudNodeDestroyer(unittest.TestCase):
    def setUp(self):
        self.mock_sleep = Mock()
        self.mock_operations = Mock(spec=UpcloudNodeOperations)
        self.destroyer = UpcloudNodeDestroyer(self.mock_operations, sleep_func=self.mock_sleep)

    def test_node_already_in_stopped_state(self):
        self.mock_operations.get_node_state.side_effect = ["stopped"]

        self.assertTrue(self.destroyer.destroy_node(1))

        self.assertTrue(self.mock_operations.stop_node.call_count == 0)
        self.mock_operations.destroy_node.assert_called_once_with(1)

    def test_node_in_error_state(self):
        self.mock_operations.get_node_state.side_effect = ["error"]

        self.assertFalse(self.destroyer.destroy_node(1))

        self.assertTrue(self.mock_operations.stop_node.call_count == 0)
        self.assertTrue(self.mock_operations.destroy_node.call_count == 0)

    def test_node_in_started_state(self):
        self.mock_operations.get_node_state.side_effect = ["started", "stopped"]

        self.assertTrue(self.destroyer.destroy_node(1))

        self.mock_operations.stop_node.assert_called_once_with(1)
        self.mock_operations.destroy_node.assert_called_once_with(1)

    def test_node_in_maintenace_state(self):
        self.mock_operations.get_node_state.side_effect = [
            "maintenance",
            "maintenance",
            None,
        ]

        self.assertTrue(self.destroyer.destroy_node(1))

        self.mock_sleep.assert_has_calls(
            [call(self.destroyer.WAIT_AMOUNT), call(self.destroyer.WAIT_AMOUNT)]
        )

        self.assertTrue(self.mock_operations.stop_node.call_count == 0)
        self.assertTrue(self.mock_operations.destroy_node.call_count == 0)

    def test_node_statys_in_started_state_for_awhile(self):
        self.mock_operations.get_node_state.side_effect = [
            "started",
            "started",
            "stopped",
        ]

        self.assertTrue(self.destroyer.destroy_node(1))

        # Only one all for stop should be done
        self.mock_operations.stop_node.assert_called_once_with(1)
        self.mock_sleep.assert_has_calls([call(self.destroyer.WAIT_AMOUNT)])
        self.mock_operations.destroy_node.assert_called_once_with(1)

    def test_reuse(self):
        """Verify that internal flag self.destroyer._stop_node is handled properly"""
        self.mock_operations.get_node_state.side_effect = [
            "started",
            "stopped",
            "started",
            "stopped",
        ]
        self.assertTrue(self.destroyer.destroy_node(1))
        self.assertTrue(self.destroyer.destroy_node(1))

        self.assertEqual(self.mock_sleep.call_count, 0)
        self.assertEqual(self.mock_operations.stop_node.call_count, 2)

    def test_timeout(self):
        self.mock_operations.get_node_state.side_effect = ["maintenance"] * 50

        self.assertRaises(UpcloudTimeoutException, self.destroyer.destroy_node, 1)

    def test_timeout_reuse(self):
        """Verify sleep count is handled properly"""
        self.mock_operations.get_node_state.side_effect = ["maintenance"] * 50
        self.assertRaises(UpcloudTimeoutException, self.destroyer.destroy_node, 1)

        self.mock_operations.get_node_state.side_effect = ["maintenance", None]
        self.assertTrue(self.destroyer.destroy_node(1))


class TestPlanPrice(unittest.TestCase):
    def setUp(self):
        prices = [
            {"name": "uk-lon1", "server_plan_1xCPU-1GB": {"amount": 1, "price": 1.488}},
            {"name": "fi-hel1", "server_plan_1xCPU-1GB": {"amount": 1, "price": 1.588}},
        ]
        self.pp = PlanPrice(prices)

    def test_zone_prices(self):
        location = NodeLocation(id="fi-hel1", name="Helsinki #1", country="FI", driver=None)
        self.assertEqual(self.pp.get_price("1xCPU-1GB", location), 1.588)

    def test_plan_not_found_in_zone(self):
        location = NodeLocation(id="no_such_location", name="", country="", driver=None)
        self.assertIsNone(self.pp.get_price("1xCPU-1GB", location))

    def test_no_location_given(self):
        self.assertIsNone(self.pp.get_price("1xCPU-1GB"))


if __name__ == "__main__":
    sys.exit(unittest.main())
