#    Licensed 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 allure
import pytest

from configs import config
from tests.api.volume import base

CONF = config.CONF


class TestVolumesSnapshotListJSON(base.BaseVolumeTest):
    """Test listing volume snapshots"""

    @classmethod
    def skip_checks(cls):
        super(TestVolumesSnapshotListJSON, cls).skip_checks()
        if not CONF.volume_feature_enabled.snapshot:
            raise cls.skipException("Cinder volume snapshots are disabled")
        if not CONF.volume.volume_type_ssd and not CONF.volume.volume_type_hdd:
            raise cls.skipException("Only SSD or HDD type volume support snapshot")

    @classmethod
    def resource_setup(cls):
        super(TestVolumesSnapshotListJSON, cls).resource_setup()
        volume_origin = cls.create_volume()

        # Create snapshots with params
        for _ in range(3):
            snapshot = cls.create_snapshot(volume_origin["id"])
        cls.snapshot = snapshot

    def _list_by_param_values_and_assert(self, with_detail=False, **params):
        """list or list_details with given params and validates result."""

        fetched_snap_list = self.snapshots_client.list_snapshots(
            detail=with_detail, **params
        )["snapshots"]

        # Validating params of fetched snapshots
        for snap in fetched_snap_list:
            for key, value in params.items():
                details_msg = "details" if with_detail else ""
                msg = f"Failed to list snapshots {details_msg} by {key}"
                if value != snap[key]:
                    return False, msg
        return True, ""

    def _list_snapshots_by_param_limit(self, limit, expected_elements):
        """list snapshots by limit param"""

        # Get snapshots list using limit parameter
        fetched_snap_list = self.snapshots_client.list_snapshots(limit=limit)[
            "snapshots"
        ]
        # Validating filtered snapshots length equals to expected_elements
        if expected_elements != len(fetched_snap_list):
            return (
                False,
                f"Failed verify filtered snapshots length({len(fetched_snap_list)}) equals to expected_elements({expected_elements})",
            )
        return True, ""

    @pytest.mark.positive
    def test_snapshots_list_with_params(self):
        """Test listing snapshots with params

        :ref: `test_snapshots_list_with_params <https://docs.openstack.org/tempest/latest/tests/volume/volume.html#volume.test_volumes_snapshots_list.VolumesSnapshotListTestJSON.test_snapshots_list_with_params>`__
        """

        # Verify list snapshots by display_name filter
        with allure.step("Verify list snapshots by display name filter"):
            params = {"name": self.snapshot["name"]}
            is_success, msg = self._list_by_param_values_and_assert(**params)
            assert is_success, msg

        # Verify list snapshots by status filter
        with allure.step("Verify list snapshots by status filter"):
            params = {"status": "available"}
            is_success, msg = self._list_by_param_values_and_assert(**params)
            assert is_success, msg

        # Verify list snapshots by status and display name filter
        with allure.step("Verify list snapshots by display name and status filter"):
            params = {"status": "available", "name": self.snapshot["name"]}
            is_success, msg = self._list_by_param_values_and_assert(**params)
            assert is_success, msg

    @pytest.mark.positive
    def test_snapshots_list_details_with_params(self):
        """Test listing snapshot details with params

        :ref: `test_snapshots_list_details_with_params <https://docs.openstack.org/tempest/latest/tests/volume/volume.html#volume.test_volumes_snapshots_list.VolumesSnapshotListTestJSON.test_snapshots_list_details_with_params>`__
        """

        # Verify list snapshot details by display_name filter
        with allure.step("Verify list snapshot details by display_name filter"):
            params = {"name": self.snapshot["name"]}
            is_success, msg = self._list_by_param_values_and_assert(
                with_detail=True, **params
            )
            assert is_success, msg
        # Verify list snapshot details by status filter
        with allure.step("Verify list snapshot details by status filter"):
            params = {"status": "available"}
            is_success, msg = self._list_by_param_values_and_assert(
                with_detail=True, **params
            )
            assert is_success, msg
        # Verify list snapshot details by status and display name filter
        with allure.step(
            "Verify list snapshot details by status and display name filter"
        ):
            params = {"status": "available", "name": self.snapshot["name"]}
            is_success, msg = self._list_by_param_values_and_assert(
                with_detail=True, **params
            )
            assert is_success, msg

    @pytest.mark.positive
    def test_snapshot_list_param_limit(self):
        """Test listing snapshot with limit returns the limited elements

        If listing snapshots with limit=1, then 1 snapshot is returned.

        :ref: `test_snapshot_list_param_limit <https://docs.openstack.org/tempest/latest/tests/volume/volume.html#volume.test_volumes_snapshots_list.VolumesSnapshotListTestJSON.test_snapshot_list_param_limit>`__
        """
        with allure.step("Verify list snapshot with limit param"):
            is_success, msg = self._list_snapshots_by_param_limit(
                limit=1, expected_elements=1
            )
            assert is_success, msg

    @pytest.mark.positive
    def test_snapshot_list_param_limit_equals_infinite(self):
        """Test listing snapshot with infinite limit

        If listing snapshots with limit greater than the count of all
        snapshots, then all snapshots are returned.

        :ref: `test_snapshot_list_param_limit_equals_infinite <https://docs.openstack.org/tempest/latest/tests/volume/volume.html#volume.test_volumes_snapshots_list.VolumesSnapshotListTestJSON.test_snapshot_list_param_limit_equals_infinite>`__
        """
        with allure.step("Verify list snapshot with infinite limit param"):
            snap_list = self.snapshots_client.list_snapshots()["snapshots"]
            is_success, msg = self._list_snapshots_by_param_limit(
                limit=100000, expected_elements=len(snap_list)
            )
            assert is_success, msg

    @pytest.mark.positive
    def test_snapshot_list_param_limit_equals_zero(self):
        """Test listing snapshot with zero limit should return empty list

        :ref: `test_snapshot_list_param_limit_equals_zero <https://docs.openstack.org/tempest/latest/tests/volume/volume.html#volume.test_volumes_snapshots_list.VolumesSnapshotListTestJSON.test_snapshot_list_param_limit_equals_zero>`__
        """
        with allure.step("Verify list snapshot with zero limit param"):
            is_success, msg = self._list_snapshots_by_param_limit(
                limit=0, expected_elements=0
            )
            assert is_success, msg

    def _list_snapshots_param_sort(self, sort_key, sort_dir):
        snap_list = self.snapshots_client.list_snapshots(
            sort_key=sort_key, sort_dir=sort_dir
        )["snapshots"]
        if not snap_list:
            return False, "There is no snapshot list"
        if sort_key == "display_name":
            sort_key = "name"
        # Note: On Cinder API, 'display_name' works as a sort key
        # on a request, a volume name appears as 'name' on the response.
        # So Tempest needs to change the key name here for this inconsistent
        # API behavior.
        sorted_list = [snapshot[sort_key] for snapshot in snap_list]
        msg = "The list of snapshots was not sorted correctly."
        if sorted(sorted_list, reverse=sort_dir == "desc") != sorted_list:
            return False, msg
        return True, ""

    @pytest.mark.positive
    def test_snapshot_list_param_sort_id_asc(self):
        """Test listing snapshots sort by id ascendingly

        :ref: `test_snapshot_list_param_sort_id_asc <https://docs.openstack.org/tempest/latest/tests/volume/volume.html#volume.test_volumes_snapshots_list.VolumesSnapshotListTestJSON.test_snapshot_list_param_sort_id_asc>`__
        """
        with allure.step("Verify list snapshot sort by id ascendingly"):
            is_success, msg = self._list_snapshots_param_sort(
                sort_key="id", sort_dir="asc"
            )
            assert is_success, msg

    @pytest.mark.positive
    def test_snapshot_list_param_sort_id_desc(self):
        """Test listing snapshots sort by id descendingly

        :ref: `test_snapshot_list_param_sort_id_desc <https://docs.openstack.org/tempest/latest/tests/volume/volume.html#volume.test_volumes_snapshots_list.VolumesSnapshotListTestJSON.test_snapshot_list_param_sort_id_desc>`__
        """
        with allure.step("Verify list snapshot sort by id descendingly"):
            is_success, msg = self._list_snapshots_param_sort(
                sort_key="id", sort_dir="desc"
            )
            assert is_success, msg

    @pytest.mark.positive
    def test_snapshot_list_param_sort_created_at_asc(self):
        """Test listing snapshots sort by created_at ascendingly

        :ref: `test_snapshot_list_param_sort_created_at_asc <https://docs.openstack.org/tempest/latest/tests/volume/volume.html#volume.test_volumes_snapshots_list.VolumesSnapshotListTestJSON.test_snapshot_list_param_sort_created_at_asc>`__
        """
        with allure.step("Verify list snapshot sort by created_at ascendingly"):
            is_success, msg = self._list_snapshots_param_sort(
                sort_key="created_at", sort_dir="asc"
            )
            assert is_success, msg

    @pytest.mark.positive
    def test_snapshot_list_param_sort_created_at_desc(self):
        """Test listing snapshots sort by created_at descendingly

        :ref: `test_snapshot_list_param_sort_created_at_desc <https://docs.openstack.org/tempest/latest/tests/volume/volume.html#volume.test_volumes_snapshots_list.VolumesSnapshotListTestJSON.test_snapshot_list_param_sort_created_at_desc>`__
        """
        with allure.step("Verify list snapshot sort by created_at descendingly"):
            is_success, msg = self._list_snapshots_param_sort(
                sort_key="created_at", sort_dir="desc"
            )
            assert is_success, msg

    @pytest.mark.positive
    def test_snapshot_list_param_sort_name_asc(self):
        """Test listing snapshots sort by display_name ascendingly

        :ref: `test_snapshot_list_param_sort_name_asc <https://docs.openstack.org/tempest/latest/tests/volume/volume.html#volume.test_volumes_snapshots_list.VolumesSnapshotListTestJSON.test_snapshot_list_param_sort_name_asc>`__
        """
        with allure.step("Verify list snapshot sort by display_name ascendingly"):
            is_success, msg = self._list_snapshots_param_sort(
                sort_key="display_name", sort_dir="asc"
            )
            assert is_success, msg

    @pytest.mark.positive
    def test_snapshot_list_param_sort_name_desc(self):
        """Test listing snapshots sort by display_name descendingly

        :ref: `test_snapshot_list_param_sort_name_desc <https://docs.openstack.org/tempest/latest/tests/volume/volume.html#volume.test_volumes_snapshots_list.VolumesSnapshotListTestJSON.test_snapshot_list_param_sort_name_desc>`__
        """
        with allure.step("Verify list snapshot sort by display_name descendingly"):
            is_success, msg = self._list_snapshots_param_sort(
                sort_key="display_name", sort_dir="desc"
            )
            assert is_success, msg

    @pytest.mark.positive
    def test_snapshot_list_param_marker(self):
        """Test listing snapshots with marker

        The list of snapshots should end before the provided marker

        :ref: `test_snapshot_list_param_marker <https://docs.openstack.org/tempest/latest/tests/volume/volume.html#volume.test_volumes_snapshots_list.VolumesSnapshotListTestJSON.test_snapshot_list_param_marker>`__
        """
        with allure.step("List snapshot and get list reverse order"):
            snap_list = self.snapshots_client.list_snapshots()["snapshots"]
            # list_snapshots will take the reverse order as they are created.
            snapshot_id_list = [snap["id"] for snap in snap_list][::-1]
        with allure.step("List snapshot with marker"):
            params = {"marker": snapshot_id_list[1]}
            snap_list = self.snapshots_client.list_snapshots(**params)["snapshots"]
        with allure.step("Verify list of snapshots with marker"):
            fetched_list_id = [snap["id"] for snap in snap_list]
            # Verify the list of snapshots ends before the provided
            # marker(second snapshot), therefore only the first snapshot
            # should displayed.
            assert (
                snapshot_id_list[:1] == fetched_list_id
            ), "Failed to verify snapshot list with marker param"

    @pytest.mark.positive
    def test_snapshot_list_param_offset(self):
        """Test listing snapshots with offset and limit

        If listing snapshots with offset=2 and limit=3, then at most 3(limit)
        snapshots located in the position 2(offset) in the all snapshots list
        should be returned.
        (The items in the all snapshots list start from position 0.)

        :ref: `test_snapshot_list_param_offset <https://docs.openstack.org/tempest/latest/tests/volume/volume.html#volume.test_volumes_snapshots_list.VolumesSnapshotListTestJSON.test_snapshot_list_param_offset>`__
        """
        with allure.step("List snapshot with offset and limit param"):
            params = {"offset": 2, "limit": 3}
            snap_list = self.snapshots_client.list_snapshots(**params)["snapshots"]
        with allure.step("Verify snapshot with offset and limit param"):
            # Verify the list of snapshots skip offset=2 from the first element
            # (total 3 elements), therefore only one snapshot should display
            assert 1 == len(
                snap_list
            ), "Failed to verify snapshot list with offset param"
