# -*- coding: utf-8 -*-
# Copyright (c) Ansible project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

from ansible_collections.community.general.tests.unit.compat.mock import patch
from ansible_collections.community.general.tests.unit.compat import unittest
from ansible.module_utils import basic
import ansible_collections.community.general.plugins.modules.ocapi_info as module
from ansible_collections.community.general.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson
from ansible_collections.community.general.tests.unit.plugins.modules.utils import set_module_args, exit_json, fail_json

MOCK_BASE_URI = "mockBaseUri"
MOCK_JOB_NAME_IN_PROGRESS = "MockJobInProgress"
MOCK_JOB_NAME_COMPLETE = "MockJobComplete"
MOCK_JOB_NAME_DOES_NOT_EXIST = "MockJobDoesNotExist"

ACTION_WAS_SUCCESSFUL = "Action was successful."

MOCK_SUCCESSFUL_HTTP_RESPONSE = {
    "ret": True,
    "data": {}
}

MOCK_404_RESPONSE = {
    "ret": False,
    "status": 404
}

MOCK_HTTP_RESPONSE_JOB_IN_PROGRESS = {
    "ret": True,
    "data": {
        "Self": "https://openflex-data24-usalp02120qo0012-iomb:443/Storage/Devices/openflex-data24-usalp02120qo0012/Jobs/FirmwareUpdate/",
        "ID": MOCK_JOB_NAME_IN_PROGRESS,
        "PercentComplete": 10,
        "Status": {
            "State": {
                "ID": 16,
                "Name": "In service"
            },
            "Health": [
                {
                    "ID": 5,
                    "Name": "OK"
                }
            ]
        }
    }
}

MOCK_HTTP_RESPONSE_JOB_COMPLETE = {
    "ret": True,
    "data": {
        "Self": "https://openflex-data24-usalp02120qo0012-iomb:443/Storage/Devices/openflex-data24-usalp02120qo0012/Jobs/FirmwareUpdate/",
        "ID": MOCK_JOB_NAME_COMPLETE,
        "PercentComplete": 100,
        "Status": {
            "State": {
                "ID": 65540,
                "Name": "Activate needed"
            },
            "Health": [
                {
                    "ID": 5,
                    "Name": "OK"
                }
            ],
            "Details": [
                "Completed."
            ]
        }
    }
}


def get_bin_path(self, arg, required=False):
    """Mock AnsibleModule.get_bin_path"""
    return arg


def get_exception_message(ansible_exit_json):
    """From an AnsibleExitJson exception, get the message string."""
    return ansible_exit_json.exception.args[0]["msg"]


def mock_get_request(*args, **kwargs):
    """Mock for get_request."""
    url = args[1]
    if url == "https://" + MOCK_BASE_URI:
        return MOCK_SUCCESSFUL_HTTP_RESPONSE
    elif url == "https://" + MOCK_BASE_URI + '/Jobs/' + MOCK_JOB_NAME_IN_PROGRESS:
        return MOCK_HTTP_RESPONSE_JOB_IN_PROGRESS
    elif url == "https://" + MOCK_BASE_URI + '/Jobs/' + MOCK_JOB_NAME_COMPLETE:
        return MOCK_HTTP_RESPONSE_JOB_COMPLETE
    elif url == "https://" + MOCK_BASE_URI + '/Jobs/' + MOCK_JOB_NAME_DOES_NOT_EXIST:
        return MOCK_404_RESPONSE
    else:
        raise RuntimeError("Illegal GET call to: " + args[1])


def mock_put_request(*args, **kwargs):
    """Mock put_request.  PUT should never happen so it will raise an error."""
    raise RuntimeError("Illegal PUT call to: " + args[1])


def mock_delete_request(*args, **kwargs):
    """Mock delete request.  DELETE should never happen so it will raise an error."""
    raise RuntimeError("Illegal DELETE call to: " + args[1])


def mock_post_request(*args, **kwargs):
    """Mock post_request.  POST should never happen so it will raise an error."""
    raise RuntimeError("Illegal POST call to: " + args[1])


class TestOcapiInfo(unittest.TestCase):
    def setUp(self):
        self.mock_module_helper = patch.multiple(basic.AnsibleModule,
                                                 exit_json=exit_json,
                                                 fail_json=fail_json,
                                                 get_bin_path=get_bin_path)
        self.mock_module_helper.start()
        self.addCleanup(self.mock_module_helper.stop)

    def test_module_fail_when_required_args_missing(self):
        with self.assertRaises(AnsibleFailJson) as ansible_fail_json:
            set_module_args({})
            module.main()
        self.assertIn("missing required arguments:", get_exception_message(ansible_fail_json))

    def test_module_fail_when_unknown_category(self):
        with self.assertRaises(AnsibleFailJson) as ansible_fail_json:
            set_module_args({
                'category': 'unknown',
                'command': 'JobStatus',
                'username': 'USERID',
                'password': 'PASSW0RD=21',
                'baseuri': MOCK_BASE_URI
            })
            module.main()
        self.assertIn("Invalid Category 'unknown", get_exception_message(ansible_fail_json))

    def test_module_fail_when_unknown_command(self):
        with self.assertRaises(AnsibleFailJson) as ansible_fail_json:
            set_module_args({
                'category': 'Jobs',
                'command': 'unknown',
                'username': 'USERID',
                'password': 'PASSW0RD=21',
                'baseuri': MOCK_BASE_URI
            })
            module.main()
        self.assertIn("Invalid Command 'unknown", get_exception_message(ansible_fail_json))

    def test_job_status_in_progress(self):
        with patch.multiple("ansible_collections.community.general.plugins.module_utils.ocapi_utils.OcapiUtils",
                            get_request=mock_get_request,
                            put_request=mock_put_request,
                            delete_request=mock_delete_request,
                            post_request=mock_post_request):
            with self.assertRaises(AnsibleExitJson) as ansible_exit_json:
                set_module_args({
                    'category': 'Jobs',
                    'command': 'JobStatus',
                    'job_name': MOCK_JOB_NAME_IN_PROGRESS,
                    'baseuri': MOCK_BASE_URI,
                    'username': 'USERID',
                    'password': 'PASSWORD=21'
                })
                module.main()
            self.assertEqual(ACTION_WAS_SUCCESSFUL, get_exception_message(ansible_exit_json))
            response_data = ansible_exit_json.exception.args[0]
            self.assertEqual(MOCK_HTTP_RESPONSE_JOB_IN_PROGRESS["data"]["PercentComplete"], response_data["percentComplete"])
            self.assertEqual(MOCK_HTTP_RESPONSE_JOB_IN_PROGRESS["data"]["Status"]["State"]["ID"], response_data["operationStatusId"])
            self.assertEqual(MOCK_HTTP_RESPONSE_JOB_IN_PROGRESS["data"]["Status"]["State"]["Name"], response_data["operationStatus"])
            self.assertEqual(MOCK_HTTP_RESPONSE_JOB_IN_PROGRESS["data"]["Status"]["Health"][0]["Name"], response_data["operationHealth"])
            self.assertEqual(MOCK_HTTP_RESPONSE_JOB_IN_PROGRESS["data"]["Status"]["Health"][0]["ID"], response_data["operationHealthId"])
            self.assertTrue(response_data["jobExists"])
            self.assertFalse(response_data["changed"])
            self.assertEqual(ACTION_WAS_SUCCESSFUL, response_data["msg"])
            self.assertIsNone(response_data["details"])

    def test_job_status_complete(self):
        with patch.multiple("ansible_collections.community.general.plugins.module_utils.ocapi_utils.OcapiUtils",
                            get_request=mock_get_request,
                            put_request=mock_put_request,
                            delete_request=mock_delete_request,
                            post_request=mock_post_request):
            with self.assertRaises(AnsibleExitJson) as ansible_exit_json:
                set_module_args({
                    'category': 'Jobs',
                    'command': 'JobStatus',
                    'job_name': MOCK_JOB_NAME_COMPLETE,
                    'baseuri': MOCK_BASE_URI,
                    'username': 'USERID',
                    'password': 'PASSWORD=21'
                })
                module.main()
            self.assertEqual(ACTION_WAS_SUCCESSFUL, get_exception_message(ansible_exit_json))
            response_data = ansible_exit_json.exception.args[0]
            self.assertEqual(MOCK_HTTP_RESPONSE_JOB_COMPLETE["data"]["PercentComplete"], response_data["percentComplete"])
            self.assertEqual(MOCK_HTTP_RESPONSE_JOB_COMPLETE["data"]["Status"]["State"]["ID"], response_data["operationStatusId"])
            self.assertEqual(MOCK_HTTP_RESPONSE_JOB_COMPLETE["data"]["Status"]["State"]["Name"], response_data["operationStatus"])
            self.assertEqual(MOCK_HTTP_RESPONSE_JOB_COMPLETE["data"]["Status"]["Health"][0]["Name"], response_data["operationHealth"])
            self.assertEqual(MOCK_HTTP_RESPONSE_JOB_COMPLETE["data"]["Status"]["Health"][0]["ID"], response_data["operationHealthId"])
            self.assertTrue(response_data["jobExists"])
            self.assertFalse(response_data["changed"])
            self.assertEqual(ACTION_WAS_SUCCESSFUL, response_data["msg"])
            self.assertEqual(["Completed."], response_data["details"])

    def test_job_status_not_found(self):
        with patch.multiple("ansible_collections.community.general.plugins.module_utils.ocapi_utils.OcapiUtils",
                            get_request=mock_get_request,
                            put_request=mock_put_request,
                            delete_request=mock_delete_request,
                            post_request=mock_post_request):
            with self.assertRaises(AnsibleExitJson) as ansible_exit_json:
                set_module_args({
                    'category': 'Jobs',
                    'command': 'JobStatus',
                    'job_name': MOCK_JOB_NAME_DOES_NOT_EXIST,
                    'baseuri': MOCK_BASE_URI,
                    'username': 'USERID',
                    'password': 'PASSWORD=21'
                })
                module.main()
            self.assertEqual(ACTION_WAS_SUCCESSFUL, get_exception_message(ansible_exit_json))
            response_data = ansible_exit_json.exception.args[0]
            self.assertFalse(response_data["jobExists"])
            self.assertEqual(0, response_data["percentComplete"])
            self.assertEqual(1, response_data["operationStatusId"])
            self.assertEqual("Not Available", response_data["operationStatus"])
            self.assertIsNone(response_data["operationHealth"])
            self.assertIsNone(response_data["operationHealthId"])
            self.assertFalse(response_data["changed"])
            self.assertEqual(ACTION_WAS_SUCCESSFUL, response_data["msg"])
            self.assertEqual("Job does not exist.", response_data["details"])
