###############################################################################
# Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems)   #
# and others                                                                  #
#                                                                             #
# All rights reserved. This program and the accompanying materials            #
# are made available under the terms of the Apache License, Version 2.0       #
# which accompanies this distribution, and is available at                    #
# http://www.apache.org/licenses/LICENSE-2.0                                  #
###############################################################################
import json

from discover.fetchers.api.api_access import ApiAccess
from discover.fetchers.db.db_access import DbAccess
from discover.fetchers.cli.cli_fetch_host_details import CliFetchHostDetails
from utils.ssh_connection import SshError


class ApiFetchProjectHosts(ApiAccess, DbAccess, CliFetchHostDetails):
    def __init__(self):
        super(ApiFetchProjectHosts, self).__init__()

    def get(self, project_id):
        if project_id != self.admin_project:
            # do not scan hosts except under project 'admin'
            return []
        token = self.v2_auth_pwd(self.admin_project)
        if not token:
            return []
        ret = []
        for region in self.regions:
            ret.extend(self.get_for_region(region, token))
        return ret

    def get_for_region(self, region, token):
        endpoint = self.get_region_url(region, "nova")
        ret = []
        if not token:
            return []
        req_url = endpoint + "/os-availability-zone/detail"
        headers = {
            "X-Auth-Project-Id": self.admin_project,
            "X-Auth-Token": token["id"]
        }
        response = self.get_url(req_url, headers)
        if "status" in response and int(response["status"]) != 200:
            return []
        az_info = response["availabilityZoneInfo"]
        hosts = {}
        for doc in az_info:
            az_hosts = self.get_hosts_from_az(doc)
            for h in az_hosts:
                if h["name"] in hosts:
                    # merge host_type data between AZs
                    existing_entry = hosts[h["name"]]
                    for t in h["host_type"]:
                        self.add_host_type(existing_entry, t, doc['zoneName'])
                else:
                    hosts[h["name"]] = h
                    ret.append(h)
        # get os_id for hosts using the os-hypervisors API call
        req_url = endpoint + "/os-hypervisors"
        response = self.get_url(req_url, headers)
        if "status" in response and int(response["status"]) != 200:
            return ret
        if "hypervisors" not in response:
            return ret
        for h in response["hypervisors"]:
            hvname = h["hypervisor_hostname"]
            if '.' in hvname and hvname not in hosts:
                hostname = hvname[:hvname.index('.')]
            else:
                hostname = hvname
            try:
                doc = hosts[hostname]
            except KeyError:
                # TBD - add error output
                continue
            doc["os_id"] = str(h["id"])
            self.fetch_compute_node_ip_address(doc, hvname)
        # get more network nodes details
        self.fetch_network_node_details(ret)
        return ret

    def get_hosts_from_az(self, az):
        ret = []
        for h in az["hosts"]:
            doc = self.get_host_details(az, h)
            ret.append(doc)
        return ret

    def get_host_details(self, az, h):
        # for hosts we use the name
        services = az["hosts"][h]
        doc = {
            "id": h,
            "host": h,
            "name": h,
            "zone": az["zoneName"],
            "parent_type": "availability_zone",
            "parent_id": az["zoneName"],
            "services": services,
            "host_type": []
        }
        if "nova-conductor" in services:
            s = services["nova-conductor"]
            if s["available"] and s["active"]:
                self.add_host_type(doc, "Controller", az['zoneName'])
        if "nova-compute" in services:
            s = services["nova-compute"]
            if s["available"] and s["active"]:
                self.add_host_type(doc, "Compute", az['zoneName'])
        self.fetch_host_os_details(doc)
        return doc

    # fetch more details of network nodes from neutron DB agents table
    def fetch_network_node_details(self, docs):
        hosts = {}
        for doc in docs:
            hosts[doc["host"]] = doc
        query = """
          SELECT DISTINCT host, host AS id, configurations
          FROM {}.agents
          WHERE agent_type IN ('Metadata agent', 'DHCP agent', 'L3 agent')
        """.format(self.neutron_db)
        results = self.get_objects_list(query, "")
        for r in results:
            host = r["host"]
            if host not in hosts:
                self.log.error("host from agents table not in hosts list: {}"
                               .format(host))
                continue
            host = hosts[host]
            host["config"] = json.loads(r["configurations"])
            self.add_host_type(host, "Network", '')

    # fetch ip_address from nova.compute_nodes table if possible
    def fetch_compute_node_ip_address(self, doc, h):
        query = """
      SELECT host_ip AS ip_address
      FROM nova.compute_nodes
      WHERE hypervisor_hostname = %s
    """
        results = self.get_objects_list_for_id(query, "", h)
        for db_row in results:
            doc.update(db_row)

    @staticmethod
    def add_host_type(doc, host_type, zone):
        if host_type not in doc["host_type"]:
            doc["host_type"].append(host_type)
            if host_type == 'Compute':
                doc['zone'] = zone
                doc['parent_id'] = zone

    def fetch_host_os_details(self, doc):
        cmd = 'cat /etc/os-release && echo "ARCHITECURE=`arch`"'
        try:
            lines = self.run_fetch_lines(cmd, ssh_to_host=doc['host'])
        except SshError as e:
            self.log.error('{}: {}', cmd, str(e))
        os_attributes = {}
        attributes_to_fetch = {
            'NAME': 'name',
            'VERSION': 'version',
            'ID': 'ID',
            'ID_LIKE': 'ID_LIKE',
            'ARCHITECURE': 'architecure'
        }
        for attr in attributes_to_fetch:
            matches = [l for l in lines if l.startswith(attr + '=')]
            if matches:
                line = matches[0]
                attr_name = attributes_to_fetch[attr]
                os_attributes[attr_name] = line[line.index('=')+1:].strip('"')
        if os_attributes:
            doc['OS'] = os_attributes