<?php

namespace ModulesGarden\ProxmoxAddon\App\Listeners\Cloud;

use Illuminate\Database\Capsule\Manager as DB;
use MGProvision\Proxmox\v2\Api;
use MGProvision\Proxmox\v2\models\File;
use ModulesGarden\ProxmoxAddon\App\Events\Cloud\QemuUpdateEvent;
use ModulesGarden\ProxmoxAddon\App\Enum\Cloud\ConfigurableOption;
use ModulesGarden\ProxmoxAddon\App\Models\VirtualInterface;
use ModulesGarden\ProxmoxAddon\App\Services\Cloud\VirtualInterfaceConverter;
use ModulesGarden\ProxmoxAddon\App\Traits\Cloud\SnippetTrait;
use ModulesGarden\ProxmoxAddon\App\Traits\Cloud\VmNetwork;
use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;

class QemuUpdateListener extends VmListener
{

    use VmNetwork;
    use SnippetTrait;

    public function handle(QemuUpdateEvent $event)
    {
        $this->event    = $event;
        $this->initVmAnVmModel();
        $configToDelete = $this->deleteCloudInit();
        if (!empty($configToDelete))
        {
            sl('Vm')->getVm()->deleteConfig(implode(",", $configToDelete));
        }
        if(sl('Vm')->getVm()->hasCloudInit()){
            $this->cloudInitDriveUpdate();
        }
        try
        {
            Api::beginTransaction();
            DB::beginTransaction();
            if (sl('Vm')->getVm()->config(true)['template'] == "1")
            {
                throw new \Exception("Can not update a vm if it is a template");
            }
            //Disk size
            $diskSize = $this->event->getConfig()['disk'];
            $masterHardDisk = sl('Vm')->getVm()->getMasterHardDisk();
            if ($diskSize && $masterHardDisk->getGb() != $diskSize)
            {
                $size = "+" . abs((int)$diskSize - $masterHardDisk->getGb()) . "G";
                $masterHardDisk->resize($size);
            }

            //init container
            $container = [];
            if ($hostname = $this->event->getVmModel()->name)
            {
                $container['name'] = $hostname;
            }
            //vcpus
            if ($this->event->getVmModel()->vcpus)
            {
                $container['vcpus'] = $this->event->getVmModel()->vcpus;
            }
            //cpulimit
            if ($this->event->getVmModel()->cpulimit)
            {
                $container['cpulimit'] = $this->event->getVmModel()->cpulimit;
            }
            // Boot order device
            if ($this->configuration()->getBootOrder())
            {
                $container['boot'] = $this->configuration()->getBootOrder();
            }
            //numa
            if ($this->configuration()->isNuma())
            {
                $container['numa'] = 1;
            }
            //Memory
            $container['memory'] = $this->event->getVmModel()->memory;
            //cpuunits
            if ($this->event->getVmModel()->cpuunits)
            {
                $container['cpuunits'] = $this->event->getVmModel()->cpuunits;
            }
            //Name servers
            $ns = [];
            for ($i = 0; $i <= 1; $i++)
            {
                $n = $this->event->getConfig()['nameserver'][$i];
                if (!empty($n) && !filter_var($n, FILTER_VALIDATE_IP))
                {
                    $n = gethostbyname($n);
                }
                if (!empty($n) && filter_var($n, FILTER_VALIDATE_IP))
                {
                    $ns[] = $n;
                }
            }
            if ($ns)
            {
                $container['nameserver'] = implode(" ", $ns);
            }
            //sockets
            if ($this->event->getVmModel()->sockets)
            {
                $container['sockets'] = $this->event->getVmModel()->sockets;
            }
            //cores
            if ($this->event->getVmModel()->cores)
            {
                $container['cores'] = $this->event->getVmModel()->cores;
            }
            //description
            $container['description'] =  htmlspecialchars($this->event->getConfig()['description'], ENT_QUOTES);
            //onboot
            if ($this->configuration()->isOnboot())
            {
                $container['onboot'] = 1;
            }
            //startup
            if ($this->configuration()->getStartup())
            {
                $container['startup'] = $this->configuration()->getStartup();
            }
            //CD-ROM
            $isoFile = $this->event->getConfig()['isoImage'];
            $cdrom   = sl('Vm')->getVm()->getCdrom()->first();
            if ($isoFile && $cdrom && $cdrom->getLocation() != $isoFile)
            {
                $cdrom->setLocation($isoFile);
                $container[$cdrom->getId()] = $cdrom->asConfig();
            }
            //acpi
            if ($this->configuration()->isAcpi())
            {
                $container['acpi'] = 1;
            }
            //agent
            if ($this->configuration()->isAgent())
            {
                $container['agent'] = 1;
            }
            //autostart
            if ($this->configuration()->isAutostart())
            {
                $container['autostart'] = 1;
            }
            //balloon
            if ($this->configuration()->getBalloon())
            {
                $container['balloon'] = $this->configuration()->getBalloon();
            }
            //shares
            if ($this->configuration()->getShares())
            {
                $container['shares'] = $this->configuration()->getShares();
            }
            //cdrom
            if ($this->configuration()->getCdrom())
            {
                $container['cdrom'] = $this->configuration()->getCdrom();
            }
            //cpu
            if ($this->configuration()->getCpu())
            {
                $container['cpu'] = $this->configuration()->getCpu();
            }
            //freeze
            if ($this->configuration()->isFreeze())
            {
                $container['freeze'] = 1;
            }
            //hotplug
            if ($this->configuration()->getHotplug())
            {
                $container['hotplug'] = $this->configuration()->getHotplug();
            }
            //keyboard
            if ($this->configuration()->getKeyboard())
            {
                $container['keyboard'] = $this->configuration()->getKeyboard();
            }
            //kvm
            if ($this->configuration()->isKvm())
            {
                $container['kvm'] = 1;
            }
            //localtime
            if ($this->configuration()->isLocaltime())
            {
                $container['localtime'] = 1;
            }
            //migrate_downtime
            if ($this->configuration()->getMigrateDowntime())
            {
                $container['migrate_downtime'] = $this->configuration()->getMigrateDowntime();
            }
            //migrate_speed
            if ($this->configuration()->getMigrateSpeed())
            {
                $container['migrate_speed'] = $this->configuration()->getMigrateSpeed();
            }
            //reboot
            if ($this->configuration()->isReboot())
            {
                $container['reboot'] = 1;
            }
            //startdate
            if ($this->configuration()->getStartdate())
            {
                $container['startdate'] = $this->configuration()->getStartdate();
            }
            //startup
            if ($this->configuration()->getStartup())
            {
                $container['startup'] = $this->configuration()->getStartup();
            }
            //tablet
            if ($this->configuration()->isTablet())
            {
                $container['tablet'] = 1;
            }
            //tdf
            if ($this->configuration()->isTdf())
            {
                $container['tdf'] = 1;
            }
            //watchdog
            if ($this->configuration()->getWatchdog())
            {
                $container['watchdog'] = $this->configuration()->getWatchdog();
            }
            //bootdisk
            if ($this->configuration()->getBootdisk())
            {
                $container['bootdisk'] = $this->configuration()->getBootdisk();
            }
            //scsihw
            if ($this->configuration()->getScsihw())
            {
                $container['scsihw'] = $this->configuration()->getScsihw();
            }
            //args
            if ($this->configuration()->getArgs())
            {
                $container['args'] = $this->configuration()->getArgs();
            }
            //vga
            $vga=[];
            if ($this->configuration()->getVga())
            {
                $vga[] = $this->configuration()->getVga();
            }
            if($container['vga']!="none" && $this->configuration()->getVgaMemory()){
                $vga[]="memory=".$this->configuration()->getVgaMemory();
            }
            if(!empty($vga)){
                $container['vga'] = implode(",",$vga);
            }
            //xterm.js Console
            if ($this->configuration()->isPermissionXtermjs())
            {
                $container['serial0'] = 'socket';
            }
            //cpu flags
            if ($this->configuration()->hasCpuFlags()  && version_compare($this->api()->getVersion(), "5.2", '>'))
            {
                $container['cpu'] .= ',flags=' . $this->configuration()->getCpuFlagsAsSource();
            }
            //bios
            if ($this->configuration()->getBios())
            {
                $container['bios'] = $this->configuration()->getBios();
            }
            //machine
            if ($this->configuration()->getMachine())
            {
                $container['machine'] = $this->configuration()->getMachine();
            }
            //Hard Disks
            $container += $this->updateHardDisks();
            $container += $this->createNetwork();
            $container += $this->updateNetwork();
            //Cloud-Init
            $container += $this->updateCloudInit();
            //Update container
            sl('Vm')->getVm()->updateConfig($container);
            DB::commit();
        }
        catch (\Exception $ex)
        {
            DB::rollBack();
            Api::commit();
            throw $ex;
        }

    }

    private function updateHardDisks()
    {
        $container = [];
        //IOPS & MB/s
        foreach (sl('Vm')->getVm()->getHardDisks() as $hardDisk)
        {
            $hash = $hardDisk->getHashCode();
            //Edit master hard disk
            if ($hardDisk->isMaster())
            {
                $hardDisk->setCache($this->configuration()->getDiskCache())
                    ->setMbps_rd($this->configuration()->getMbpsRd())
                    ->setMbps_wr($this->configuration()->getMbpsWr())
                    ->setDiscard($this->configuration()->isDiscard() ? "on" : null)
                    ->setIops_rd($this->configuration()->getIopsRd())
                    ->setIops_rd_max($this->configuration()->getIopsRdMax())
                    ->setIops_wr($this->configuration()->getIopsWr())
                    ->setIops_wr_max($this->configuration()->getIopsWrMax())
                    ->setReplicate($this->configuration()->isReplicate() ? 0 : null);
                if ($this->configuration()->isIoThread() && in_array($hardDisk->getType(), ['virtio', 'scsi']))
                {
                    $hardDisk->setIothread($this->configuration()->isIoThread());
                }
                $hardDisk->setSsd($this->configuration()->isSsd() ? 1 : null);
            }
            else
            {//additional disk
                $hardDisk->setMbps_rd($this->configuration()->getAdditionalDiskMbpsRd())
                    ->setMbps_wr($this->configuration()->getAdditionalDiskMbpsWr())
                    ->setIops_rd($this->configuration()->getAdditionalDiskIopsRd())
                    ->setIops_rd_max($this->configuration()->getAdditionalDiskIopsRdMax())
                    ->setIops_wr($this->configuration()->getAdditionalDiskIopsWr())
                    ->setIops_wr_max($this->configuration()->getAdditionalDiskIopsWrMax());
                $hardDisk->setSsd($this->configuration()->isAdditionalDiskSsd() ? 1 : null);
            }
            if ($hash != $hardDisk->getHashCode())
            {
                $container[$hardDisk->getId()] = $hardDisk->asConfig();
            }
        }
        return $container;
    }

    private function updateNetwork()
    {
        $container         = [];
        $updateNetworkRate = !$this->configuration()->getMinimumRate() || !$this->hosting()->isBandwidthOverageUsage();
        $rate      = null;
        if ($this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE,$this->configuration()->getRate()) &&
            $this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE,$this->configuration()->getRate())  != "-1")
        {
            $rate = $this->getWhmcsConfigOption(ConfigurableOption::NETWORK_RATE,$this->configuration()->getRate()) ;
        }
        $queues            = $this->configuration()->getQueues();
        foreach (sl('Vm')->getVm()->getNetworkDevices($this->configuration()->getBridge()) as $entity)
        {
            $hash = $entity->getHashCode();
            if ($updateNetworkRate && $entity->getRate() != $rate)
            {
                $entity->setRate($rate);
            }
            $entity->setQueues($queues);
            if ($hash != $entity->getHashCode())
            {
                $container[$entity->getId()] = $entity->asConfig();
            }
        }
        return $container;
    }

    private function deleteCloudInit()
    {
        $delete = [];
        if (!version_compare($this->api()->getVersion(), "5.2", '>='))
        {
            return $delete;
        }
        $vmConfig = sl('Vm')->getVm()->config();
        //ciuser
        //if ($this->configuration()->isPermissionUsername() &&F && $vmConfig['ciuser'])
	if ($this->configuration()->isPermissionUsername() && $vmConfig['ciuser'])
        {
            $delete[] = 'ciuser';
        }
        //cipassword
        if ($this->configuration()->isPermissionPassword() && (!$this->event->getVmModel()->getPassword()) && $vmConfig['cipassword'])
        {
            $delete[] = 'cipassword';
        }
        //sshkeys
        if (!$this->event->getConfig()['sshkeys'])
        {
            $delete[] = 'sshkeys';
        }
        //searchdomain
        if (!$this->event->getConfig()['searchdomain'] && !$this->event->getConfig()['searchdomain'] && $vmConfig['searchdomain'])
        {
            $delete[] = 'searchdomain';
        }
        //nameserver
        if (!$this->event->getConfig()['nameserver']  && $vmConfig['nameserver'] )
        {
            $delete[] = 'nameserver';
        }
        return $delete;
    }

    private function updateCloudInit()
    {
        $container = [];
        if (!$this->configuration()->isCloudInit())
        {
            return $container;
        }
        if (!version_compare($this->api()->getVersion(), "5.2", '>='))
        {
            return $container;
        }
        //ciuser
        if ($this->configuration()->isPermissionUsername() && $this->event->getConfig()['ciuser']) {
            $container['ciuser'] = $this->event->getConfig()['ciuser'];
        } elseif ($this->configuration()->getCiuser()) {
            $container['ciuser'] = $this->configuration()->getCiuser();
        }
        //cipassword
        if ($this->event->getVmModel()->getPassword() && $this->event->isChangeVmPassword()) {
            $container['cipassword'] = $this->event->getVmModel()->getPassword();
        }
        //sshkeys
        if ($this->configuration()->isPermissionSshkeys() && $this->event->getConfig()['sshkeys'])
        {
            $container['sshkeys'] = rawurlencode($this->event->getConfig()['sshkeys']);
        }
        //searchdomain
        if ($this->configuration()->isPermissionSearchdomain() && $this->event->getConfig()['searchdomain'])
        {
            $container['searchdomain'] = $this->event->getConfig()['searchdomain'];
        }
        //cicustom
        if($this->hasSnippet() &&  $files = $this->getSnippetFiles($this->event->getVmModel())){
            /**
             * @var File $file
             */
            $matchs=["user","meta",'network'];
            $custom = [];
            foreach ($files as $file){
                foreach ($matchs as $match){
                    if(preg_match("/{$match}/",  $file->getVolid())){
                        $custom[] = sprintf("{$match}=%s", $file->getVolid());
                    }
                }
            }
            $container['cicustom'] = implode(",",$custom );
        }
        elseif ($this->configuration()->getCicustom())
        {
            $container['cicustom'] = $this->configuration()->getCicustom();
        }
        return $container;
    }

    protected function  cloudInitDriveUpdate(){
        $cloudInitDrive = sl('Vm')->getVm()->getCdRomRepository()->getCloudInitDrive();
        if(!$this->configuration()->cloudInitStorage || $this->configuration()->cloudInitStorage == $cloudInitDrive->getStorage() ){
            return;
        }
        $cloudInitDrive->setStorage($this->configuration()->cloudInitStorage);
        $cloudInitDrive->unmount();
        $cloudInitDrive->mount();
    }
}