# Copyright 2015 Dell Inc.
#
#    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.

from urllib import parse as urllib

from oslo_log import log as logging

from tempest import clients
from tempest.common import credentials_factory as credentials
from tempest.common import identity
from tempest.common import utils
from tempest.common.utils import net_info
from tempest import config
from tempest.lib import exceptions

LOG = logging.getLogger('tempest.cmd.cleanup')
CONF = config.CONF

CONF_FLAVORS = None
CONF_IMAGES = None
CONF_NETWORKS = []
CONF_PRIV_NETWORK_NAME = None
CONF_PUB_NETWORK = None
CONF_PUB_ROUTER = None
CONF_PROJECTS = None
CONF_USERS = None

IS_CINDER = None
IS_GLANCE = None
IS_NEUTRON = None
IS_NOVA = None


def init_conf():
    global CONF_FLAVORS
    global CONF_IMAGES
    global CONF_NETWORKS
    global CONF_PRIV_NETWORK
    global CONF_PRIV_NETWORK_NAME
    global CONF_PUB_NETWORK
    global CONF_PUB_ROUTER
    global CONF_PROJECTS
    global CONF_USERS
    global IS_CINDER
    global IS_GLANCE
    global IS_HEAT
    global IS_NEUTRON
    global IS_NOVA

    IS_CINDER = CONF.service_available.cinder
    IS_GLANCE = CONF.service_available.glance
    IS_NEUTRON = CONF.service_available.neutron
    IS_NOVA = CONF.service_available.nova

    CONF_FLAVORS = [CONF.compute.flavor_ref, CONF.compute.flavor_ref_alt]
    CONF_IMAGES = [CONF.compute.image_ref, CONF.compute.image_ref_alt]
    CONF_PRIV_NETWORK_NAME = CONF.compute.fixed_network_name
    CONF_PUB_NETWORK = CONF.network.public_network_id
    CONF_PUB_ROUTER = CONF.network.public_router_id
    CONF_PROJECTS = [CONF.auth.admin_project_name]
    CONF_USERS = [CONF.auth.admin_username]

    if IS_NEUTRON:
        CONF_PRIV_NETWORK = _get_network_id(CONF.compute.fixed_network_name,
                                            CONF.auth.admin_project_name)
        CONF_NETWORKS = [CONF_PUB_NETWORK, CONF_PRIV_NETWORK]


def _get_network_id(net_name, project_name):
    am = clients.Manager(
        credentials.get_configured_admin_credentials())
    net_cl = am.networks_client
    pr_cl = am.projects_client

    networks = net_cl.list_networks()
    project = identity.get_project_by_name(pr_cl, project_name)
    p_id = project['id']
    n_id = None
    for net in networks['networks']:
        if (net['project_id'] == p_id and net['name'] == net_name):
            n_id = net['id']
            break
    return n_id


class BaseService(object):
    def __init__(self, kwargs):
        self.client = None
        for key, value in kwargs.items():
            setattr(self, key, value)

        self.tenant_filter = {}
        if hasattr(self, 'tenant_id'):
            self.tenant_filter['project_id'] = self.tenant_id

    def _filter_by_tenant_id(self, item_list):
        if (item_list is None or
                not item_list or
                not hasattr(self, 'tenant_id') or
                self.tenant_id is None or
                'tenant_id' not in item_list[0]):
            return item_list

        return [item for item in item_list
                if item['tenant_id'] == self.tenant_id]

    def list(self):
        pass

    def delete(self):
        pass

    def dry_run(self):
        pass

    def save_state(self):
        pass

    def run(self):
        try:
            if self.is_dry_run:
                self.dry_run()
            elif self.is_save_state:
                self.save_state()
            else:
                self.delete()
        except exceptions.NotImplemented as exc:
            # Many OpenStack services use extensions logic to implement the
            # features or resources. Tempest cleanup tries to clean up the test
            # resources without having much logic of extensions checks etc.
            # If any of the extension is missing then, service will return
            # NotImplemented error.
            msg = ("Got NotImplemented error in %s, full exception: %s" %
                   (str(self.__class__), str(exc)))
            LOG.exception(msg)
            self.got_exceptions.append(exc)


class SnapshotService(BaseService):

    def __init__(self, manager, **kwargs):
        super(SnapshotService, self).__init__(kwargs)
        self.client = manager.snapshots_client_latest

    def list(self):
        client = self.client
        snaps = client.list_snapshots()['snapshots']
        if not self.is_save_state:
            # recreate list removing saved snapshots
            snaps = [snap for snap in snaps if snap['id']
                     not in self.saved_state_json['snapshots'].keys()]
        LOG.debug("List count, %s Snapshots", len(snaps))
        return snaps

    def delete(self):
        snaps = self.list()
        client = self.client
        for snap in snaps:
            try:
                LOG.debug("Deleting Snapshot with id %s", snap['id'])
                client.delete_snapshot(snap['id'])
            except Exception:
                LOG.exception("Delete Snapshot %s exception.", snap['id'])

    def dry_run(self):
        snaps = self.list()
        self.data['snapshots'] = snaps

    def save_state(self):
        snaps = self.list()
        self.data['snapshots'] = {}
        for snap in snaps:
            self.data['snapshots'][snap['id']] = snap['name']


class ServerService(BaseService):
    def __init__(self, manager, **kwargs):
        super(ServerService, self).__init__(kwargs)
        self.client = manager.servers_client
        self.server_groups_client = manager.server_groups_client

    def list(self):
        client = self.client
        servers_body = client.list_servers()
        servers = servers_body['servers']
        if not self.is_save_state:
            # recreate list removing saved servers
            servers = [server for server in servers if server['id']
                       not in self.saved_state_json['servers'].keys()]
        LOG.debug("List count, %s Servers", len(servers))
        return servers

    def delete(self):
        client = self.client
        servers = self.list()
        for server in servers:
            try:
                LOG.debug("Deleting Server with id %s", server['id'])
                client.delete_server(server['id'])
            except Exception:
                LOG.exception("Delete Server %s exception.", server['id'])

    def dry_run(self):
        servers = self.list()
        self.data['servers'] = servers

    def save_state(self):
        servers = self.list()
        self.data['servers'] = {}
        for server in servers:
            self.data['servers'][server['id']] = server['name']


class ServerGroupService(ServerService):

    def list(self):
        client = self.server_groups_client
        sgs = client.list_server_groups()['server_groups']
        if not self.is_save_state:
            # recreate list removing saved server_groups
            sgs = [sg for sg in sgs if sg['id']
                   not in self.saved_state_json['server_groups'].keys()]
        LOG.debug("List count, %s Server Groups", len(sgs))
        return sgs

    def delete(self):
        client = self.server_groups_client
        sgs = self.list()
        for sg in sgs:
            try:
                LOG.debug("Deleting Server Group with id %s", sg['id'])
                client.delete_server_group(sg['id'])
            except Exception:
                LOG.exception("Delete Server Group %s exception.", sg['id'])

    def dry_run(self):
        sgs = self.list()
        self.data['server_groups'] = sgs

    def save_state(self):
        sgs = self.list()
        self.data['server_groups'] = {}
        for sg in sgs:
            self.data['server_groups'][sg['id']] = sg['name']


class KeyPairService(BaseService):
    def __init__(self, manager, **kwargs):
        super(KeyPairService, self).__init__(kwargs)
        self.client = manager.keypairs_client

    def list(self):
        client = self.client
        keypairs = client.list_keypairs()['keypairs']
        if not self.is_save_state:
            # recreate list removing saved keypairs
            keypairs = [keypair for keypair in keypairs
                        if keypair['keypair']['name']
                        not in self.saved_state_json['keypairs'].keys()]
        LOG.debug("List count, %s Keypairs", len(keypairs))
        return keypairs

    def delete(self):
        client = self.client
        keypairs = self.list()
        for k in keypairs:
            name = k['keypair']['name']
            try:
                LOG.debug("Deleting keypair %s", name)
                client.delete_keypair(name)
            except Exception:
                LOG.exception("Delete Keypair %s exception.", name)

    def dry_run(self):
        keypairs = self.list()
        self.data['keypairs'] = keypairs

    def save_state(self):
        keypairs = self.list()
        self.data['keypairs'] = {}
        for keypair in keypairs:
            keypair = keypair['keypair']
            self.data['keypairs'][keypair['name']] = keypair


class VolumeService(BaseService):
    def __init__(self, manager, **kwargs):
        super(VolumeService, self).__init__(kwargs)
        self.client = manager.volumes_client_latest

    def list(self):
        client = self.client
        vols = client.list_volumes()['volumes']
        if not self.is_save_state:
            # recreate list removing saved volumes
            vols = [vol for vol in vols if vol['id']
                    not in self.saved_state_json['volumes'].keys()]
        LOG.debug("List count, %s Volumes", len(vols))
        return vols

    def delete(self):
        client = self.client
        vols = self.list()
        for v in vols:
            try:
                LOG.debug("Deleting volume with id %s", v['id'])
                client.delete_volume(v['id'])
            except Exception:
                LOG.exception("Delete Volume %s exception.", v['id'])

    def dry_run(self):
        vols = self.list()
        self.data['volumes'] = vols

    def save_state(self):
        vols = self.list()
        self.data['volumes'] = {}
        for vol in vols:
            self.data['volumes'][vol['id']] = vol['name']


class VolumeQuotaService(BaseService):
    def __init__(self, manager, **kwargs):
        super(VolumeQuotaService, self).__init__(kwargs)
        self.client = manager.volume_quotas_client_latest

    def delete(self):
        client = self.client
        try:
            LOG.debug("Deleting Volume Quotas for project with id %s",
                      self.project_id)
            client.delete_quota_set(self.project_id)
        except Exception:
            LOG.exception("Delete Volume Quotas exception for 'project %s'.",
                          self.project_id)

    def dry_run(self):
        quotas = self.client.show_quota_set(
            self.project_id, params={'usage': True})['quota_set']
        self.data['volume_quotas'] = quotas


class NovaQuotaService(BaseService):
    def __init__(self, manager, **kwargs):
        super(NovaQuotaService, self).__init__(kwargs)
        self.client = manager.quotas_client
        self.limits_client = manager.limits_client

    def delete(self):
        client = self.client
        try:
            LOG.debug("Deleting Nova Quotas for project with id %s",
                      self.project_id)
            client.delete_quota_set(self.project_id)
        except Exception:
            LOG.exception("Delete Nova Quotas exception for 'project %s'.",
                          self.project_id)

    def dry_run(self):
        client = self.limits_client
        quotas = client.show_limits()['limits']
        self.data['compute_quotas'] = quotas['absolute']


class NetworkQuotaService(BaseService):
    def __init__(self, manager, **kwargs):
        super(NetworkQuotaService, self).__init__(kwargs)
        self.client = manager.network_quotas_client

    def delete(self):
        client = self.client
        try:
            LOG.debug("Deleting Network Quotas for project with id %s",
                      self.project_id)
            client.reset_quotas(self.project_id)
        except Exception:
            LOG.exception("Delete Network Quotas exception for 'project %s'.",
                          self.project_id)

    def dry_run(self):
        resp = [quota for quota in self.client.list_quotas()['quotas']
                if quota['project_id'] == self.project_id]
        self.data['network_quotas'] = resp


# Begin network service classes
class BaseNetworkService(BaseService):
    def __init__(self, manager, **kwargs):
        super(BaseNetworkService, self).__init__(kwargs)
        self.networks_client = manager.networks_client
        self.subnets_client = manager.subnets_client
        self.ports_client = manager.ports_client
        self.floating_ips_client = manager.floating_ips_client
        self.metering_labels_client = manager.metering_labels_client
        self.metering_label_rules_client = manager.metering_label_rules_client
        self.security_groups_client = manager.security_groups_client
        self.routers_client = manager.routers_client
        self.subnetpools_client = manager.subnetpools_client

    def _filter_by_conf_networks(self, item_list):
        if not item_list or not all(('network_id' in i for i in item_list)):
            return item_list

        return [item for item in item_list if item['network_id']
                not in CONF_NETWORKS]


class NetworkService(BaseNetworkService):

    def list(self):
        client = self.networks_client
        networks = client.list_networks(**self.tenant_filter)
        networks = networks['networks']

        if not self.is_save_state:
            # recreate list removing saved networks
            networks = [network for network in networks if network['id']
                        not in self.saved_state_json['networks'].keys()]
        # filter out networks declared in tempest.conf
        if self.is_preserve:
            networks = [network for network in networks
                        if network['id'] not in CONF_NETWORKS]
        LOG.debug("List count, %s Networks", len(networks))
        return networks

    def delete(self):
        client = self.networks_client
        networks = self.list()
        for n in networks:
            try:
                LOG.debug("Deleting Network with id %s", n['id'])
                client.delete_network(n['id'])
            except Exception:
                LOG.exception("Delete Network %s exception.", n['id'])

    def dry_run(self):
        networks = self.list()
        self.data['networks'] = networks

    def save_state(self):
        networks = self.list()
        self.data['networks'] = {}
        for network in networks:
            self.data['networks'][network['id']] = network


class NetworkFloatingIpService(BaseNetworkService):

    def list(self):
        client = self.floating_ips_client
        flips = client.list_floatingips(**self.tenant_filter)
        flips = flips['floatingips']

        if not self.is_save_state:
            # recreate list removing saved flips
            flips = [flip for flip in flips if flip['id']
                     not in self.saved_state_json['floatingips'].keys()]
        LOG.debug("List count, %s Network Floating IPs", len(flips))
        return flips

    def delete(self):
        client = self.floating_ips_client
        flips = self.list()
        for flip in flips:
            try:
                LOG.debug("Deleting Network Floating IP with id %s",
                          flip['id'])
                client.delete_floatingip(flip['id'])
            except Exception:
                LOG.exception("Delete Network Floating IP %s exception.",
                              flip['id'])

    def dry_run(self):
        flips = self.list()
        self.data['floatingips'] = flips

    def save_state(self):
        flips = self.list()
        self.data['floatingips'] = {}
        for flip in flips:
            self.data['floatingips'][flip['id']] = flip


class NetworkRouterService(BaseNetworkService):

    def list(self):
        client = self.routers_client
        routers = client.list_routers(**self.tenant_filter)
        routers = routers['routers']

        if not self.is_save_state:
            # recreate list removing saved routers
            routers = [router for router in routers if router['id']
                       not in self.saved_state_json['routers'].keys()]
        if self.is_preserve:
            routers = [router for router in routers
                       if router['id'] != CONF_PUB_ROUTER]

        LOG.debug("List count, %s Routers", len(routers))
        return routers

    def delete(self):
        client = self.routers_client
        ports_client = self.ports_client
        routers = self.list()
        for router in routers:
            rid = router['id']
            ports = [port for port
                     in ports_client.list_ports(device_id=rid)['ports']
                     if net_info.is_router_interface_port(port)]
            for port in ports:
                try:
                    LOG.debug("Deleting port with id %s of router with id %s",
                              port['id'], rid)
                    client.remove_router_interface(rid, port_id=port['id'])
                except Exception:
                    LOG.exception("Delete Router Interface exception for "
                                  "'port %s' of 'router %s'.", port['id'], rid)
            try:
                LOG.debug("Deleting Router with id %s", rid)
                client.delete_router(rid)
            except Exception:
                LOG.exception("Delete Router %s exception.", rid)

    def dry_run(self):
        routers = self.list()
        self.data['routers'] = routers

    def save_state(self):
        routers = self.list()
        self.data['routers'] = {}
        for router in routers:
            self.data['routers'][router['id']] = router['name']


class NetworkMeteringLabelRuleService(NetworkService):

    def list(self):
        client = self.metering_label_rules_client
        rules = client.list_metering_label_rules()
        rules = rules['metering_label_rules']
        rules = self._filter_by_tenant_id(rules)

        if not self.is_save_state:
            saved_rules = self.saved_state_json['metering_label_rules'].keys()
            # recreate list removing saved rules
            rules = [rule for rule in rules if rule['id'] not in saved_rules]
        LOG.debug("List count, %s Metering Label Rules", len(rules))
        return rules

    def delete(self):
        client = self.metering_label_rules_client
        rules = self.list()
        for rule in rules:
            try:
                LOG.debug("Deleting Metering Label Rule with id %s",
                          rule['id'])
                client.delete_metering_label_rule(rule['id'])
            except Exception:
                LOG.exception("Delete Metering Label Rule %s exception.",
                              rule['id'])

    def dry_run(self):
        rules = self.list()
        self.data['metering_label_rules'] = rules

    def save_state(self):
        rules = self.list()
        self.data['metering_label_rules'] = {}
        for rule in rules:
            self.data['metering_label_rules'][rule['id']] = rule


class NetworkMeteringLabelService(BaseNetworkService):

    def list(self):
        client = self.metering_labels_client
        labels = client.list_metering_labels()
        labels = labels['metering_labels']
        labels = self._filter_by_tenant_id(labels)

        if not self.is_save_state:
            # recreate list removing saved labels
            labels = [label for label in labels if label['id']
                      not in self.saved_state_json['metering_labels'].keys()]
        LOG.debug("List count, %s Metering Labels", len(labels))
        return labels

    def delete(self):
        client = self.metering_labels_client
        labels = self.list()
        for label in labels:
            try:
                LOG.debug("Deleting Metering Label with id %s", label['id'])
                client.delete_metering_label(label['id'])
            except Exception:
                LOG.exception("Delete Metering Label %s exception.",
                              label['id'])

    def dry_run(self):
        labels = self.list()
        self.data['metering_labels'] = labels

    def save_state(self):
        labels = self.list()
        self.data['metering_labels'] = {}
        for label in labels:
            self.data['metering_labels'][label['id']] = label['name']


class NetworkPortService(BaseNetworkService):

    def list(self):
        client = self.ports_client
        ports = [port for port in
                 client.list_ports(**self.tenant_filter)['ports']
                 if port["device_owner"] == "" or
                 port["device_owner"].startswith("compute:")]

        if not self.is_save_state:
            # recreate list removing saved ports
            ports = [port for port in ports if port['id']
                     not in self.saved_state_json['ports'].keys()]
        if self.is_preserve:
            ports = self._filter_by_conf_networks(ports)

        LOG.debug("List count, %s Ports", len(ports))
        return ports

    def delete(self):
        client = self.ports_client
        ports = self.list()
        for port in ports:
            try:
                LOG.debug("Deleting port with id %s", port['id'])
                client.delete_port(port['id'])
            except Exception:
                LOG.exception("Delete Port %s exception.", port['id'])

    def dry_run(self):
        ports = self.list()
        self.data['ports'] = ports

    def save_state(self):
        ports = self.list()
        self.data['ports'] = {}
        for port in ports:
            self.data['ports'][port['id']] = port['name']


class NetworkSecGroupService(BaseNetworkService):
    def list(self):
        client = self.security_groups_client
        filter = self.tenant_filter
        # cannot delete default sec group so never show it.
        secgroups = [secgroup for secgroup in
                     client.list_security_groups(**filter)['security_groups']
                     if secgroup['name'] != 'default']

        if not self.is_save_state:
            # recreate list removing saved security_groups
            secgroups = [secgroup for secgroup in secgroups if secgroup['id']
                         not in self.saved_state_json['security_groups'].keys()
                         ]
        if self.is_preserve:
            secgroups = [secgroup for secgroup in secgroups
                         if secgroup['security_group_rules'][0]['project_id']
                         not in CONF_PROJECTS]
        LOG.debug("List count, %s security_groups", len(secgroups))
        return secgroups

    def delete(self):
        client = self.security_groups_client
        secgroups = self.list()
        for secgroup in secgroups:
            try:
                LOG.debug("Deleting security_group with id %s", secgroup['id'])
                client.delete_security_group(secgroup['id'])
            except Exception:
                LOG.exception("Delete security_group %s exception.",
                              secgroup['id'])

    def dry_run(self):
        secgroups = self.list()
        self.data['security_groups'] = secgroups

    def save_state(self):
        secgroups = self.list()
        self.data['security_groups'] = {}
        for secgroup in secgroups:
            self.data['security_groups'][secgroup['id']] = secgroup['name']


class NetworkSubnetService(BaseNetworkService):

    def list(self):
        client = self.subnets_client
        subnets = client.list_subnets(**self.tenant_filter)
        subnets = subnets['subnets']
        if not self.is_save_state:
            # recreate list removing saved subnets
            subnets = [subnet for subnet in subnets if subnet['id']
                       not in self.saved_state_json['subnets'].keys()]
        if self.is_preserve:
            subnets = self._filter_by_conf_networks(subnets)
        LOG.debug("List count, %s Subnets", len(subnets))
        return subnets

    def delete(self):
        client = self.subnets_client
        subnets = self.list()
        for subnet in subnets:
            try:
                LOG.debug("Deleting subnet with id %s", subnet['id'])
                client.delete_subnet(subnet['id'])
            except Exception:
                LOG.exception("Delete Subnet %s exception.", subnet['id'])

    def dry_run(self):
        subnets = self.list()
        self.data['subnets'] = subnets

    def save_state(self):
        subnets = self.list()
        self.data['subnets'] = {}
        for subnet in subnets:
            self.data['subnets'][subnet['id']] = subnet['name']


class NetworkSubnetPoolsService(BaseNetworkService):

    def list(self):
        client = self.subnetpools_client
        pools = client.list_subnetpools(**self.tenant_filter)['subnetpools']
        if not self.is_save_state:
            # recreate list removing saved subnet pools
            pools = [pool for pool in pools if pool['id']
                     not in self.saved_state_json['subnetpools'].keys()]
        if self.is_preserve:
            pools = [pool for pool in pools if pool['project_id']
                     not in CONF_PROJECTS]
        LOG.debug("List count, %s Subnet Pools", len(pools))
        return pools

    def delete(self):
        client = self.subnetpools_client
        pools = self.list()
        for pool in pools:
            try:
                LOG.debug("Deleting Subnet Pool with id %s", pool['id'])
                client.delete_subnetpool(pool['id'])
            except Exception:
                LOG.exception("Delete Subnet Pool %s exception.", pool['id'])

    def dry_run(self):
        pools = self.list()
        self.data['subnetpools'] = pools

    def save_state(self):
        pools = self.list()
        self.data['subnetpools'] = {}
        for pool in pools:
            self.data['subnetpools'][pool['id']] = pool['name']


# begin global services
class RegionService(BaseService):

    def __init__(self, manager, **kwargs):
        super(RegionService, self).__init__(kwargs)
        self.client = manager.regions_client

    def list(self):
        client = self.client
        regions = client.list_regions()
        if not self.is_save_state:
            regions = [region for region in regions['regions'] if region['id']
                       not in self.saved_state_json['regions'].keys()]
            LOG.debug("List count, %s Regions", len(regions))
            return regions
        else:
            LOG.debug("List count, %s Regions", len(regions['regions']))
            return regions['regions']

    def delete(self):
        client = self.client
        regions = self.list()
        for region in regions:
            try:
                LOG.debug("Deleting region with id %s", region['id'])
                client.delete_region(region['id'])
            except Exception:
                LOG.exception("Delete Region %s exception.", region['id'])

    def dry_run(self):
        regions = self.list()
        self.data['regions'] = {}
        for region in regions:
            self.data['regions'][region['id']] = region

    def save_state(self):
        regions = self.list()
        self.data['regions'] = {}
        for region in regions:
            self.data['regions'][region['id']] = region


class FlavorService(BaseService):
    def __init__(self, manager, **kwargs):
        super(FlavorService, self).__init__(kwargs)
        self.client = manager.flavors_client

    def list(self):
        client = self.client
        flavors = client.list_flavors({"is_public": None})['flavors']
        if not self.is_save_state:
            # recreate list removing saved flavors
            flavors = [flavor for flavor in flavors if flavor['id']
                       not in self.saved_state_json['flavors'].keys()]

        if self.is_preserve:
            flavors = [flavor for flavor in flavors
                       if flavor['id'] not in CONF_FLAVORS]
        LOG.debug("List count, %s Flavors after reconcile", len(flavors))
        return flavors

    def delete(self):
        client = self.client
        flavors = self.list()
        for flavor in flavors:
            try:
                LOG.debug("Deleting flavor with id %s", flavor['id'])
                client.delete_flavor(flavor['id'])
            except Exception:
                LOG.exception("Delete Flavor %s exception.", flavor['id'])

    def dry_run(self):
        flavors = self.list()
        self.data['flavors'] = flavors

    def save_state(self):
        flavors = self.list()
        self.data['flavors'] = {}
        for flavor in flavors:
            self.data['flavors'][flavor['id']] = flavor['name']


class ImageService(BaseService):
    def __init__(self, manager, **kwargs):
        super(ImageService, self).__init__(kwargs)
        self.client = manager.image_client_v2

    def list(self):
        client = self.client
        response = client.list_images()
        images = []
        images.extend(response['images'])
        while 'next' in response:
            parsed = urllib.urlparse(response['next'])
            marker = urllib.parse_qs(parsed.query)['marker'][0]
            response = client.list_images(params={"marker": marker})
            images.extend(response['images'])

        if not self.is_save_state:
            images = [image for image in images if image['id']
                      not in self.saved_state_json['images'].keys()]
        if self.is_preserve:
            images = [image for image in images
                      if image['id'] not in CONF_IMAGES]
        LOG.debug("List count, %s Images after reconcile", len(images))
        return images

    def delete(self):
        client = self.client
        images = self.list()
        for image in images:
            try:
                LOG.debug("Deleting image with id %s", image['id'])
                client.delete_image(image['id'])
            except Exception:
                LOG.exception("Delete Image %s exception.", image['id'])

    def dry_run(self):
        images = self.list()
        self.data['images'] = images

    def save_state(self):
        self.data['images'] = {}
        images = self.list()
        for image in images:
            self.data['images'][image['id']] = image['name']


class UserService(BaseService):

    def __init__(self, manager, **kwargs):
        super(UserService, self).__init__(kwargs)
        self.client = manager.users_v3_client

    def list(self):
        users = self.client.list_users()['users']

        if not self.is_save_state:
            users = [user for user in users if user['id']
                     not in self.saved_state_json['users'].keys()]

        if self.is_preserve:
            users = [user for user in users if user['name']
                     not in CONF_USERS]

        elif not self.is_save_state:  # Never delete admin user
            users = [user for user in users if user['name'] !=
                     CONF.auth.admin_username]

        LOG.debug("List count, %s Users after reconcile", len(users))
        return users

    def delete(self):
        users = self.list()
        for user in users:
            try:
                LOG.debug("Deleting user with id %s", user['id'])
                self.client.delete_user(user['id'])
            except Exception:
                LOG.exception("Delete User %s exception.", user['id'])

    def dry_run(self):
        users = self.list()
        self.data['users'] = users

    def save_state(self):
        users = self.list()
        self.data['users'] = {}
        for user in users:
            self.data['users'][user['id']] = user['name']


class RoleService(BaseService):

    def __init__(self, manager, **kwargs):
        super(RoleService, self).__init__(kwargs)
        self.client = manager.roles_v3_client

    def list(self):
        try:
            roles = self.client.list_roles()['roles']
            # reconcile roles with saved state and never list admin role
            if not self.is_save_state:
                roles = [role for role in roles if
                         (role['id'] not in
                          self.saved_state_json['roles'].keys() and
                          role['name'] != CONF.identity.admin_role)]
                LOG.debug("List count, %s Roles after reconcile", len(roles))
            return roles
        except Exception:
            LOG.exception("Cannot retrieve Roles.")
            return []

    def delete(self):
        roles = self.list()
        for role in roles:
            try:
                LOG.debug("Deleting role with id %s", role['id'])
                self.client.delete_role(role['id'])
            except Exception:
                LOG.exception("Delete Role %s exception.", role['id'])

    def dry_run(self):
        roles = self.list()
        self.data['roles'] = roles

    def save_state(self):
        roles = self.list()
        self.data['roles'] = {}
        for role in roles:
            self.data['roles'][role['id']] = role['name']


class ProjectService(BaseService):

    def __init__(self, manager, **kwargs):
        super(ProjectService, self).__init__(kwargs)
        self.client = manager.projects_client

    def list(self):
        projects = self.client.list_projects()['projects']
        if not self.is_save_state:
            project_ids = self.saved_state_json['projects']
            projects = [project
                        for project in projects
                        if (project['id'] not in project_ids and
                            project['name'] != CONF.auth.admin_project_name)]

        if self.is_preserve:
            projects = [project
                        for project in projects
                        if project['name'] not in CONF_PROJECTS]

        LOG.debug("List count, %s Projects after reconcile", len(projects))
        return projects

    def delete(self):
        projects = self.list()
        for project in projects:
            try:
                LOG.debug("Deleting project with id %s", project['id'])
                self.client.delete_project(project['id'])
            except Exception:
                LOG.exception("Delete project %s exception.", project['id'])

    def dry_run(self):
        projects = self.list()
        self.data['projects'] = projects

    def save_state(self):
        projects = self.list()
        self.data['projects'] = {}
        for project in projects:
            self.data['projects'][project['id']] = project['name']


class DomainService(BaseService):

    def __init__(self, manager, **kwargs):
        super(DomainService, self).__init__(kwargs)
        self.client = manager.domains_client

    def list(self):
        client = self.client
        domains = client.list_domains()['domains']
        if not self.is_save_state:
            domains = [domain for domain in domains if domain['id']
                       not in self.saved_state_json['domains'].keys()]

        LOG.debug("List count, %s Domains after reconcile", len(domains))
        return domains

    def delete(self):
        client = self.client
        domains = self.list()
        for domain in domains:
            try:
                LOG.debug("Deleting domain with id %s", domain['id'])
                client.update_domain(domain['id'], enabled=False)
                client.delete_domain(domain['id'])
            except Exception:
                LOG.exception("Delete Domain %s exception.", domain['id'])

    def dry_run(self):
        domains = self.list()
        self.data['domains'] = domains

    def save_state(self):
        domains = self.list()
        self.data['domains'] = {}
        for domain in domains:
            self.data['domains'][domain['id']] = domain['name']


def get_project_associated_cleanup_services():
    """Returns list of project service classes.

    The list contains services whose resources need to be deleted prior,
    the project they are associated with, deletion. The resources cannot be
    most likely deleted after the project is deleted first.
    """
    project_associated_services = []
    # TODO(gmann): Tempest should provide some plugin hook for cleanup
    # script extension to plugin tests also.
    if IS_NOVA:
        project_associated_services.append(NovaQuotaService)
    if IS_CINDER:
        project_associated_services.append(VolumeQuotaService)
    if IS_NEUTRON:
        project_associated_services.append(NetworkQuotaService)
    return project_associated_services


def get_resource_cleanup_services():
    """Returns list of project related classes.

    The list contains services whose resources are associated with a project,
    however, their deletion is possible also after the project is deleted
    first.
    """
    resource_cleanup_services = []
    # TODO(gmann): Tempest should provide some plugin hook for cleanup
    # script extension to plugin tests also.
    if IS_NOVA:
        resource_cleanup_services.append(ServerService)
        resource_cleanup_services.append(KeyPairService)
        resource_cleanup_services.append(ServerGroupService)
    if IS_NEUTRON:
        resource_cleanup_services.append(NetworkFloatingIpService)
        if utils.is_extension_enabled('metering', 'network'):
            resource_cleanup_services.append(NetworkMeteringLabelRuleService)
            resource_cleanup_services.append(NetworkMeteringLabelService)
        resource_cleanup_services.append(NetworkRouterService)
        resource_cleanup_services.append(NetworkPortService)
        resource_cleanup_services.append(NetworkSubnetService)
        resource_cleanup_services.append(NetworkService)
        resource_cleanup_services.append(NetworkSecGroupService)
        resource_cleanup_services.append(NetworkSubnetPoolsService)
    if IS_CINDER:
        resource_cleanup_services.append(SnapshotService)
        resource_cleanup_services.append(VolumeService)
    return resource_cleanup_services


def get_global_cleanup_services():
    global_services = []
    if IS_NOVA:
        global_services.append(FlavorService)
    if IS_GLANCE:
        global_services.append(ImageService)
    global_services.append(UserService)
    global_services.append(ProjectService)
    global_services.append(DomainService)
    global_services.append(RoleService)
    global_services.append(RegionService)
    return global_services
