"""
# Copyright 2019-2021 VMware, Inc.
# All rights reserved. -- VMware Confidential
"""
import os
import logging
import time
import traceback
import utils
from l10n import msgMetadata as _T, localizedString as _
from patch_errors import UserError
logger = logging.getLogger(__name__)
PARTITION_NAME = ''
VG_NAME = ''
LV_NAME = ''
MOUNT_POINT = ''
DEVICE_NAME = ''
DISK_NAME = ''
FILE_NAME = '/etc/fstab'
FS_TYPE='ext4'


def count_disk():
    '''
    This method finds the no of disks present in the VM.
    This can be used pre-addition of the disk as well
    as post-addition of the disk, to get disk count
    '''
    logger.info("Count the number of disks present.")
    cmd = 'fdisk -l| grep "/dev/sd*"|wc -l'
    rc, out, err = utils.run_command(cmd)
    if rc:
        raise Exception(err)
    logger.info("Number of disks present are: %s", out)
    return int(out)


def rescan_storage():
    '''
    This method Rescans the scsi storage associated with the vm.
    '''
    logger.info("Rescan the SCSI storage.")
    path = '/sys/class/scsi_host/'
    iscsi_hosts = os.listdir(path)
    for iscsi_host in iscsi_hosts:
        time.sleep(5)
        scan_path = ''.join(['/sys/class/scsi_host/', iscsi_host, '/scan'])
        cmd = 'echo "- - -" >>' + ' ' + scan_path
        logger.info("Performing scan of the SCSI storage %s.", scan_path)
        rc, out, err = utils.run_command(cmd) #pylint: disable=W0612
        if rc:
            raise Exception(err)
    logger.info("Performed scan of entire SCSI storage.")


def get_partition_name():
    '''
    This method gets the name of the recently added partition
    :return:newly added partition name
    :type
    '''
    logger.info("Retrieve name of the newly added partition.")
    cmd = "ls -ltr /dev/sd* |tail -n 1 |awk '{print $NF}'"
    rc, out, err = utils.run_command(cmd)
    if rc:
        raise Exception(err)
    logger.info("Newly added partition is :%s.", out)
    return out


def create_physical_partition():
    """
    This Module creates the physical partition on the added disk.
    """
    logger.info("Creating new physical partition on the newly added disk.")
    cmd= '(echo "n"; echo ""; echo ""; echo ""; echo ""; echo "q") | fdisk ' \
            + PARTITION_NAME
    rc, _out, err = utils.run_command(cmd)
    if rc:
        raise Exception(err)
    logger.info("Created new physical partition on the newly added disk.")


def create_volume_group():
    '''
    This Module creates the volume group.
    '''
    logger.info("Creating new volume group.")
    cmd =' '.join(['vgcreate', VG_NAME, PARTITION_NAME])
    rc, _out, err = utils.run_command(cmd)
    if rc:
        raise Exception(err)
    logger.info("Created new volume group %s-%s.", VG_NAME, PARTITION_NAME)


def create_logical_volume():
    """
    This Module creates the logical volume of specified size.
    """
    logger.info("Creating logical volume of specified size.")
    cmd = ' '.join(['lvcreate', '-n', LV_NAME, '-l', '100%FREE', VG_NAME])
    rc, _out, err = utils.run_command(cmd)
    if rc:
        raise Exception(err)
    logger.info("Created logical volume of specified size.")


def create_file_system_over_logical_volume():
    '''
    This Module creates file system over logical volume.
    '''
    logger.info("Creating ext4 file system over logical volume.")
    logical_volume_path = '/dev/mapper/%s-%s'%(VG_NAME, LV_NAME)
    cmd = ' '.join(['mkfs.ext4', logical_volume_path])
    rc, _out, err = utils.run_command(cmd)
    if rc:
        raise Exception(err)
    logger.info("Created ext4 file system over logical volume %s.",
                logical_volume_path)


def create_mount_directory():
    '''
    This Module creates Directory on which we mount the disk
    '''
    try:
        global MOUNT_POINT
        MOUNT_POINT = '/storage/%s' % DISK_NAME
        logger.info("Creating directory %s to mount the disk.", MOUNT_POINT)
        os.mkdir(MOUNT_POINT)
        logger.info("Created %s directory to mount the disk.", MOUNT_POINT)
    except Exception as e:
        raise e


def mount_disk():
    """
    This Module mounts logical volume to mount directory
    """
    logger.info("Mounting the disk to the directory.")
    LV_PATH = '/dev/mapper/%s-%s'%(VG_NAME, LV_NAME)
    cmd =' '.join(['mount', LV_PATH, MOUNT_POINT])
    rc, _out, err = utils.run_command(cmd)
    if rc:
        raise Exception(err)
    logger.info("Mounted %s to the directory %s.", LV_PATH , MOUNT_POINT)


def compare_disk_count(old_disk_count, new_disk_count):
    """
    Compares the disk count before the scan and disk count after scan
    :param old_disk_count: no of disks on the appliance before scan
    :type  old_disk_count int

    :param new_disk_count: no of disks on the appliance after scan
    :return:
    """
    logger.info("Comparing disk count after disk addition.")
    if old_disk_count < new_disk_count:
        logger.info("New disk is added")
        return True
    logger.info("Found no difference between disk count before and after scan.")
    return False


def add_entry_in_fstab():
    '''
    Create entry of mountpoint in the fstab file
    '''
    logger.info("Create new disk entry in the fstab file.")
    # 1. Get Block UUID
    get_blk_uuid = "blkid -s UUID -o value %s" % DEVICE_NAME
    rc, _out, err = utils.run_command(get_blk_uuid)
    if rc:
        logger.error("Unable to block uuid for device %s" %DEVICE_NAME)
        raise Exception(err)
    blk_uuid = _out.strip()
    # 2. Add entry using UUID in fstab
    fs_uuid = "UUID=%s" %blk_uuid
    options = ','.join(['rw','nosuid', 'nodev', 'exec', 'auto', 'nouser',
                        'async'])
    cmd = ' '.join(['echo', fs_uuid, MOUNT_POINT, FS_TYPE,
                    options, '0' , '2', '>>', FILE_NAME])
    rc, _out, err = utils.run_command(cmd)
    if rc:
        raise Exception(err)
    logger.info("Created new disk entry in the fstab file.")


def check_disk_added():
    """
    This method checks new disk is added or not
    :return: True if disk is added else retry for 3 times and
    raise Exception
    """
    old_disk_cnt = count_disk()
    retry_count = 3
    while True:
        rescan_storage()
        new_disk_count = count_disk()
        disk_added = compare_disk_count(old_disk_cnt, new_disk_count)
        if disk_added:
            return
        time.sleep(5)
        retry_count = retry_count - 1
        if retry_count == 0:
            cause = _(_T("check.disk.added.failed.error",
                         "Failed to check if disk was added. "
                         "Re-confirm if disk was added."))
            raise UserError(cause=cause)


def configure_added_disk(disk_name):
    """
    This method configures the newly added disk
    :param disk_name: name of the disk which needs to be configured
    :return:
    """
    global DISK_NAME, LV_NAME, VG_NAME, PARTITION_NAME, DEVICE_NAME
    try:
        check_disk_added()
        PARTITION_NAME = get_partition_name()
        DISK_NAME = disk_name
        VG_NAME = "%s_vg" % DISK_NAME
        LV_NAME = disk_name
        if disk_name == "lvm_snapshot":
            LV_NAME = "lv_%s" % disk_name
            VG_NAME = "vg_%s" % disk_name
        DEVICE_NAME = '/dev/%s/%s' % (VG_NAME, LV_NAME)

        create_physical_partition()
        create_volume_group()
        create_logical_volume()
        create_file_system_over_logical_volume()
        create_mount_directory()
        mount_disk()
        add_entry_in_fstab()
    except Exception:
        cause = _(_T('configuring.newly.added.disk.failed',
                     'Configuring newly added disk failed due to %s.'),
                  traceback.format_exc())
        raise UserError(cause=cause)
