# -*- coding: utf-8 -*-

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

import logging
import os

import json

from django.conf import settings

from ralph.discovery.hardware import get_disk_shares
from ralph.discovery.models import DeviceType
from ralph.scan.errors import ConnectionError, NoMatchError, NoLanError
from ralph.scan.plugins import get_base_result_template
from ralph.util import network


SETTINGS = settings.SCAN_PLUGINS.get(__name__, {})


logger = logging.getLogger("SCAN")


def _connect_ssh(ip_address, user, password):
    if not network.check_tcp_port(ip_address, 22):
        raise ConnectionError('Port 22 closed on a Proxmox server.')
    return network.connect_ssh(ip_address, user, password)


def _get_master_ip_address(ssh, ip_address, cluster_cfg=None):
    if not cluster_cfg:
        stdin, stdout, stderr = ssh.exec_command("cat /etc/pve/cluster.cfg")
        data = stdout.read()
    else:
        data = cluster_cfg
    if not data:
        stdin, stdout, stderr = ssh.exec_command("pvesh get /nodes")
        data = stdout.read()
        if data:
            for node in json.loads(data):
                stdin, stdout, stderr = ssh.exec_command(
                    'pvesh get "/nodes/%s/dns"' % node['node'],
                )
                dns_data = stdout.read()
                if not dns_data:
                    return ip_address
                ip_address = json.loads(dns_data)['dns1']
                break
        else:
            return ip_address
    nodes = {}
    current_node = None
    for line in data.splitlines():
        line = line.strip()
        if line.endswith('{'):
            current_node = line.replace('{', '').strip()
            nodes[current_node] = {}
        elif line.endswith('}'):
            current_node = None
        elif ':' in line and current_node:
            key, value = (v.strip() for v in line.split(':', 1))
            nodes[current_node][key] = value
    for node, pairs in nodes.iteritems():
        is_master = node.startswith('master')
        try:
            ip_address = pairs['IP']
        except KeyError:
            continue
        if is_master:
            return ip_address
    return ip_address


def _get_cluster_member(ssh, ip_address):
    stdin, stdout, stderr = ssh.exec_command("ifconfig eth0 | head -n 1")
    mac = stdout.readline().split()[-1]
    return {
        'model_name': 'Proxmox',
        'mac_addresses': [mac],
        'installed_software': [{
            'model_name': 'Proxmox',
            'path': 'proxmox',
        }],
        'system_ip_addresses': [ip_address],
    }


def _get_local_disk_size(ssh, disk):
    """Return the size of a disk image file, in bytes"""

    path = os.path.join('/var/lib/vz/images', disk)
    stdin, stdout, stderr = ssh.exec_command("du -m '%s'" % path)
    line = stdout.read().strip()
    if not line:
        return 0
    size = int(line.split(None, 1)[0])
    return size


def _get_virtual_machine_info(
    ssh,
    vmid,
    master_ip_address,
    storages,
    hypervisor_ip_address,
):
    stdin, stdout, stderr = ssh.exec_command(
        "cat /etc/qemu-server/%d.conf" % vmid,
    )
    lines = stdout.readlines()
    if not lines:
        # Proxmox 2 uses a different directory structure
        stdin, stdout, stderr = ssh.exec_command(
            "cat /etc/pve/nodes/*/qemu-server/%d.conf" % vmid,
        )
        lines = stdout.readlines()
    disks = {}
    lan_model = None
    name = 'unknown'
    for line in lines:
        line = line.strip()
        if line.startswith('#') or ':' not in line:
            continue
        key, value = line.split(':', 1)
        if key.startswith('vlan'):
            lan_model, lan_mac = value.split('=', 1)
        elif key.startswith('net'):
            lan_model, lan_mac = value.split('=', 1)
            if ',' in lan_mac:
                lan_mac = lan_mac.split(',', 1)[0]
        elif key == 'name':
            name = value.strip()
        elif key == 'sockets':
            cpu_count = int(value.strip())
        elif key.startswith('ide') or key.startswith('virtio'):
            disks[key] = value.strip()
    if lan_model is None:
        raise NoLanError(
            "No LAN for virtual server %s. Hypervisor IP: %s" % (
                vmid,
                hypervisor_ip_address,
            ),
        )
    device_info = {
        'model_name': 'Proxmox qemu kvm',
        'type': DeviceType.virtual_server.raw,
        'mac_addresses': [lan_mac],
        'management': master_ip_address,  # ?
        'hostname': name,
    }
    detected_disks = []
    detected_shares = []
    for slot, disk in disks.iteritems():
        params = {}
        if ',' in disk:
            disk, rawparams = disk.split(',', 1)
            for kv in rawparams.split(','):
                if not kv.strip():
                    continue
                k, v = kv.split('=', 1)
                params[k] = v.strip()
        if ':' in disk:
            vg, lv = disk.split(':', 1)
        else:
            vg = ''
            lv = disk
        if vg == 'local':
            size = _get_local_disk_size(ssh, lv)
            if not size > 0:
                continue
            detected_disks.append({
                'family': 'QEMU disk image',
                'size': size,
                'label': slot,
                'mount_point': lv,
            })
            continue
        if vg in ('', 'local', 'pve-local'):
            continue
        vol = '%s:%s' % (vg, lv)
        try:
            wwn, size = storages[lv]
        except KeyError:
            logger.warning(
                'Volume %s does not exist. Hypervisor IP: %s' % (
                    lv,
                    hypervisor_ip_address,
                ),
            )
            continue
        detected_shares.append({
            'serial_number': wwn,
            'is_virtual': True,
            'size': size,
            'volume': vol,
        })
    if detected_disks:
        device_info['disks'] = detected_disks
    if detected_shares:
        device_info['disk_shares'] = detected_shares
    detected_cpus = [
        {
            'family': 'QEMU Virtual',
            'model_name': 'QEMU Virtual CPU',
            'label': 'CPU {}'.format(i + 1),
            'index': i + 1,
            'cores': 1,
        } for i in range(cpu_count)
    ]
    if detected_cpus:
        device_info['processors'] = detected_cpus
    return device_info


def _get_virtual_machines(ssh, master_ip_address, hypervisor_ip_address):
    detected_machines = []
    storages = get_disk_shares(ssh)
    stdin, stdout, stderr = ssh.exec_command("qm list")
    for line in stdout:
        line = line.strip()
        if line.startswith('VMID'):
            continue
        vmid, name, status, mem, bootdisk, pid = (
            v.strip() for v in line.split()
        )
        if status != 'running':
            continue
        vmid = int(vmid)
        try:
            device_info = _get_virtual_machine_info(
                ssh,
                vmid,
                master_ip_address,
                storages,
                hypervisor_ip_address,
            )
        except NoLanError as e:
            logger.warning(unicode(e))
        else:
            detected_machines.append(device_info)
    return detected_machines


def _ssh_proxmox(ip_address, user, password):
    ssh = _connect_ssh(ip_address, user, password)
    try:
        cluster_cfg = None
        for command in (
            'cat /etc/pve/cluster.cfg',
            'cat /etc/pve/cluster.conf',
            'cat /etc/pve/storage.cfg',
            'pvecm help',
        ):
            stdin, stdout, stderr = ssh.exec_command(command)
            data = stdout.read()
            if data != '':
                if command == 'cat /etc/pve/cluster.cfg':
                    cluster_cfg = data
                break
        else:
            raise NoMatchError('This is not a PROXMOX server.')
        master_ip_address = _get_master_ip_address(
            ssh,
            ip_address,
            cluster_cfg,
        )
        cluster_member = _get_cluster_member(ssh, ip_address)
        subdevices = _get_virtual_machines(
            ssh,
            master_ip_address,
            ip_address,
        )
        if subdevices:
            cluster_member['subdevices'] = subdevices
    finally:
        ssh.close()
    return cluster_member


def scan_address(ip_address, **kwargs):
    if 'nx-os' in (kwargs.get('snmp_name') or '').lower():
        raise NoMatchError('Incompatible Nexus found.')
    if kwargs.get('http_family') not in ('Proxmox',):
        raise NoMatchError('It is not Proxmox.')
    user = SETTINGS.get('user')
    password = SETTINGS.get('password')
    messages = []
    result = get_base_result_template('ssh_proxmox', messages)
    if not user or not password:
        result['status'] = 'error'
        messages.append(
            'Not configured. Set SSH_USER and SSH_PASSWORD in your '
            'configuration file.',
        )
    else:
        try:
            device_info = _ssh_proxmox(ip_address, user, password)
        except (ConnectionError, NoMatchError) as e:
            result['status'] = 'error'
            messages.append(unicode(e))
        else:
            result.update({
                'status': 'success',
                'device': device_info,
            })
    return result
