# Copyright 2021 Red Hat, Inc.
# All Rights Reserved.
#
#    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 time

from tempest import config
from tempest.lib import exceptions

from manila_tempest_tests.services.share.v2.json import shares_client
from manila_tempest_tests import share_exceptions

CONF = config.CONF
LATEST_MICROVERSION = CONF.share.max_api_microversion


def _get_access_rule(body, rule_id):
    for rule in body:
        if rule['id'] in rule_id:
            return rule


def _get_name_of_raise_method(resource_name):
    if resource_name == 'snapshot_access':
        return 'AccessRuleBuildErrorException'
    if resource_name == 'share_replica':
        return 'ShareInstanceBuildErrorException'
    resource_name = resource_name.title()
    name = resource_name.replace('_', '')
    return name + 'BuildErrorException'


def wait_for_resource_status(client, resource_id, status,
                             resource_name='share', rule_id=None,
                             status_attr='status',
                             raise_rule_in_error_state=True,
                             version=LATEST_MICROVERSION,
                             timeout=None):
    """Waits for a resource to reach a given status."""

    get_resource_action = {
        'share': 'get_share',
        'snapshot': 'get_snapshot',
        'share_server': 'show_share_server',
        'share_instance': 'get_share_instance',
        'snapshot_instance': 'get_snapshot_instance',
        'access_rule': 'list_access_rules',
        'snapshot_access': 'list_snapshot_access_rules',
        'share_group': 'get_share_group',
        'share_group_snapshot': 'get_share_group_snapshot',
        'share_replica': 'get_share_replica',
        'share_backup': 'get_share_backup'
    }

    action_name = get_resource_action[resource_name]
    # This code snippet is intended to set the dictionary key of the returned
    # response for share access rule and for snapshot access rule.
    if 'access' in resource_name:
        rn = '_'.join(action_name.split('_')[1:-1]) + '_list'
    else:
        rn = resource_name

    # Since API v2 requests require an additional parameter for micro-versions,
    # it's necessary to pass the required parameters according to the version.
    resource_action = getattr(client, action_name)
    method_args = [resource_id]
    method_kwargs = {}
    if isinstance(client, shares_client.SharesV2Client):
        method_kwargs.update({'version': version})
    body = resource_action(*method_args, **method_kwargs)[rn]

    if 'access' in resource_name:
        status_attr = 'state'
        body = _get_access_rule(body, rule_id)

    resource_status = body[status_attr]
    start = int(time.time())

    exp_status = status if isinstance(status, list) else [status]
    resource_status_check_time_out = client.build_timeout
    if timeout is not None:
        resource_status_check_time_out = timeout
    while resource_status not in exp_status:
        time.sleep(client.build_interval)
        body = resource_action(*method_args, **method_kwargs)[rn]

        if 'access' in resource_name:
            status_attr = 'state'
            body = _get_access_rule(body, rule_id)

        resource_status = body[status_attr]

        if resource_status in exp_status:
            return
        elif 'error' in resource_status.lower() and raise_rule_in_error_state:
            raise_method = _get_name_of_raise_method(resource_name)
            resource_exception = getattr(share_exceptions, raise_method)
            raise resource_exception(resource_id=resource_id)
        if int(time.time()) - start >= resource_status_check_time_out:
            message = ('%s %s failed to reach %s status (current %s) '
                       'within the required time (%s s).' %
                       (resource_name.replace('_', ' '), resource_id, status,
                        resource_status, resource_status_check_time_out))
            raise exceptions.TimeoutException(message)


def wait_for_migration_status(client, share_id, dest_host, status_to_wait,
                              version=LATEST_MICROVERSION):
    """Waits for a share to migrate to a certain host."""
    statuses = ((status_to_wait,)
                if not isinstance(status_to_wait, (tuple, list, set))
                else status_to_wait)
    share = client.get_share(share_id, version=version)['share']
    migration_timeout = CONF.share.migration_timeout
    start = int(time.time())
    while share['task_state'] not in statuses:
        time.sleep(client.build_interval)
        share = client.get_share(share_id, version=version)['share']
        if share['task_state'] in statuses:
            break
        elif share['task_state'] == 'migration_error':
            raise share_exceptions.ShareMigrationException(
                share_id=share['id'], src=share['host'], dest=dest_host)
        elif int(time.time()) - start >= migration_timeout:
            message = ('Share %(share_id)s failed to reach a status in'
                       '%(status)s when migrating from host %(src)s to '
                       'host %(dest)s within the required time '
                       '%(timeout)s.' % {
                           'src': share['host'],
                           'dest': dest_host,
                           'share_id': share['id'],
                           'timeout': client.build_timeout,
                           'status': str(statuses),
                       })
            raise exceptions.TimeoutException(message)
    return share


def wait_for_snapshot_access_rule_deletion(client, snapshot_id, rule_id):
    rule = client.get_snapshot_access_rule(snapshot_id, rule_id)
    start = int(time.time())

    while rule is not None:
        time.sleep(client.build_interval)

        rule = client.get_snapshot_access_rule(snapshot_id, rule_id)

        if rule is None:
            return
        if int(time.time()) - start >= client.build_timeout:
            message = ('The snapshot access rule %(id)s failed to delete '
                       'within the required time (%(time)ss).' %
                       {
                           'time': client.build_timeout,
                           'id': rule_id,
                       })
            raise exceptions.TimeoutException(message)


def wait_for_message(client, resource_id):
    """Waits until a message for a resource with given id exists"""
    start = int(time.time())
    message = None

    while not message:
        time.sleep(client.build_interval)
        for msg in client.list_messages()['messages']:
            if msg['resource_id'] == resource_id:
                return msg

        if int(time.time()) - start >= client.build_timeout:
            message = ('No message for resource with id %s was created in'
                       ' the required time (%s s).' %
                       (resource_id, client.build_timeout))
            raise exceptions.TimeoutException(message)


def wait_for_soft_delete(client, share_id, version=LATEST_MICROVERSION):
    """Wait for a share soft delete to recycle bin."""
    share = client.get_share(share_id, version=version)['share']
    start = int(time.time())
    while not share['is_soft_deleted']:
        time.sleep(client.build_interval)
        share = client.get_share(share_id, version=version)['share']
        if share['is_soft_deleted']:
            break
        elif int(time.time()) - start >= client.build_timeout:
            message = ('Share %(share_id)s failed to be soft deleted to '
                       'recycle bin within the required time '
                       '%(timeout)s.' % {
                           'share_id': share['id'],
                           'timeout': client.build_timeout,
                       })
            raise exceptions.TimeoutException(message)


def wait_for_restore(client, share_id, version=LATEST_MICROVERSION):
    """Wait for a share restore from recycle bin."""
    share = client.get_share(share_id, version=version)['share']
    start = int(time.time())
    while share['is_soft_deleted']:
        time.sleep(client.build_interval)
        share = client.get_share(share_id, version=version)['share']
        if not share['is_soft_deleted']:
            break
        elif int(time.time()) - start >= client.build_timeout:
            message = ('Share %(share_id)s failed to restore from '
                       'recycle bin within the required time '
                       '%(timeout)s.' % {
                           'share_id': share['id'],
                           'timeout': client.build_timeout,
                       })
            raise exceptions.TimeoutException(message)


def wait_for_subnet_create_check(client, share_network_id,
                                 neutron_net_id=None,
                                 neutron_subnet_id=None,
                                 availability_zone=None):
    result = client.subnet_create_check(
        share_network_id, neutron_net_id=neutron_net_id,
        neutron_subnet_id=neutron_subnet_id,
        availability_zone=availability_zone)
    start = int(time.time())
    while not result['compatible']:
        time.sleep(client.build_interval)
        result = client.subnet_create_check(
            share_network_id, neutron_net_id=neutron_net_id,
            neutron_subnet_id=neutron_subnet_id,
            availability_zone=availability_zone)
        if result['compatible']:
            break
        elif int(time.time()) - start >= client.build_timeout or (
                result['compatible'] is False):
            message = ('Subnet create check failed within the '
                       'required time %(timeout)s seconds for share network '
                       '%(share_network)s.' % {
                           'timeout': client.build_timeout,
                           'share_network': share_network_id,
                       })
            raise exceptions.TimeoutException(message)
