<?php

namespace ModulesGarden\ProxmoxAddon\App\Listeners\Vps;

use Illuminate\Database\Capsule\Manager as DB;
use MGProvision\Proxmox\v2\Api;
use MGProvision\Proxmox\v2\models\File;
use ModulesGarden\ProxmoxAddon\App\Events\Vps\QemuUpdateEvent;
use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
use ModulesGarden\ProxmoxAddon\App\Services\EmailService;
use ModulesGarden\ProxmoxAddon\App\Services\Utility;
use ModulesGarden\ProxmoxAddon\App\Services\Vps\AdditionalDiskService;
use ModulesGarden\ProxmoxAddon\App\Services\Vps\ContainerService;
use ModulesGarden\ProxmoxAddon\App\Services\Vps\HostingService;
use ModulesGarden\ProxmoxAddon\App\Services\Vps\IpSetIpFilterService;
use ModulesGarden\ProxmoxAddon\App\Services\Vps\NetworkService;
use ModulesGarden\ProxmoxAddon\App\Services\Vps\ProductService;
use ModulesGarden\ProxmoxAddon\App\Services\Vps\UserService;
use ModulesGarden\ProxmoxAddon\App\Enum\Vps\ConfigurableOption;
use ModulesGarden\ProxmoxAddon\App\Enum\Vps\CustomField;
use ModulesGarden\ProxmoxAddon\App\Traits\Vps\SnippetTrait;
use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;

class QemuUpdateListener
{
    use WhmcsParams;
    use ApiService;
    use UserService;
    use ProductService;
    use HostingService;
    use SnippetTrait;

    /**
     * @var IpSetIpFilterService
     */
    private $ipSetFilterService;
    private $emailService;
    private $containerService;
    private $networkService;
    /**
     * @var AdditionalDiskService
     */
    protected $additionalDiskService;
    /**
     * @var QemuUpdateEvent
     */
    private $event;

    /**
     * VmCreatedListener constructor.
     */
    public function __construct()
    {
        $this->ipSetFilterService = new IpSetIpFilterService();
        $this->emailService       = new EmailService();
        $this->containerService   = new ContainerService();
        $this->networkService     = new NetworkService();
        $this->additionalDiskService = new AdditionalDiskService();
    }

    public function handle(QemuUpdateEvent $event)
    {
        $this->event    = $event;
        $configToDelete = $this->deleteCloudInit();
        if (!empty($configToDelete))
        {
            $this->vm()->deleteConfig(implode(",", $configToDelete));
        }
        if($this->vm()->hasCloudInit()){
            $this->cloudInitDriveUpdate();
        }
        try
        {
            Api::beginTransaction();
            DB::beginTransaction();
            if ($this->vm()->config(true)['template'] == "1")
            {
                throw new \Exception("Can not update a vm if it is a template");
            }
            //Disk size
            $diskSize = $this->configuration()->getDiskSize();
            if($this->getWhmcsConfigOption(ConfigurableOption::STORAGE)){
                list($storage,$diskSize) = explode(":", $this->getWhmcsConfigOption(ConfigurableOption::STORAGE),2);
                Utility::unitFormat($diskSize, $this->configuration()->getDiskUnit(), 'gb');
            }
            elseif ($this->isWhmcsConfigOption(ConfigurableOption::DISK_SIZE))
            {
                $diskSize = $this->getWhmcsConfigOption(ConfigurableOption::DISK_SIZE);
                Utility::unitFormat($diskSize, $this->configuration()->getDiskUnit(), 'gb');
            }
            $masterHardDisk = $this->vm()->getMasterHardDisk();
            if ($diskSize && $masterHardDisk->getGb() < $diskSize)
            {
                $size = "+" . abs((int)$diskSize - $masterHardDisk->getGb()) . "G";
                $masterHardDisk->resize($size);
            }
            //init container
            $container = [];
            if ($hostname = $this->containerService->hostname())
            {
                $container['name'] = $hostname;
            }
            //vcpus
            if ($this->getWhmcsConfigOption(ConfigurableOption::VCPUS, $this->configuration()->getVcpus()))
            {
                $container['vcpus'] = $this->getWhmcsConfigOption(ConfigurableOption::VCPUS, $this->configuration()->getVcpus()) ;
            }
            //cpulimit
            if ($this->getWhmcsConfigOption(ConfigurableOption::CPU_LIMIT, $this->configuration()->getCpulimit() ))
            {
                $container['cpulimit'] =$this->getWhmcsConfigOption(ConfigurableOption::CPU_LIMIT, $this->configuration()->getCpulimit() );
            }
            // Boot order device
            if ($this->configuration()->getBootOrder())
            {
                $container['boot'] = $this->configuration()->getBootOrder();
            }
            //numa
            if ($this->configuration()->isNuma())
            {
                $container['numa'] = 1;
            }
            //Memory
            if ($this->isWhmcsConfigOption(ConfigurableOption::MEMORY))
            {
                $container['memory'] = $this->getWhmcsConfigOption(ConfigurableOption::MEMORY);
                Utility::unitFormat($container['memory'], $this->configuration()->getMemoryUnit(), 'mb');
            }
            else if ($this->configuration()->getMemory()) {
                $container['memory'] = $this->configuration()->getMemory();
            }
            //cpuunits
            if ($this->getWhmcsConfigOption(ConfigurableOption::CPU_UNITS, $this->configuration()->getCpuunits()))
            {
                $container['cpuunits'] = $this->getWhmcsConfigOption(ConfigurableOption::CPU_UNITS, $this->configuration()->getCpuunits());
            }
            //Name servers
            if ($this->configuration()->isCloudInitServiceNameservers())
            {
                $ns = [];
                for ($i = 1; $i <= 2; $i++)
                {
                    $n = trim($this->hosting()->{"ns{$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->getWhmcsConfigOption(ConfigurableOption::SOCKETS, $this->configuration()->getSockets()))
            {
                $container['sockets'] = $this->getWhmcsConfigOption(ConfigurableOption::SOCKETS, $this->configuration()->getSockets());
            }
            //cores
            if ($this->getWhmcsConfigOption(ConfigurableOption::CORES_PER_SOCKET, $this->configuration()->getCores()) )
            {
                $container['cores'] = $this->getWhmcsConfigOption(ConfigurableOption::CORES_PER_SOCKET, $this->configuration()->getCores());
            }
            //description
            if ($this->configuration()->getDescription())
            {
                $container['description'] = $this->containerService->description();
            }
            //onboot
            if ($this->configuration()->isOnboot())
            {
                $container['onboot'] = 1;
            }
            //startup
            if ($this->configuration()->getStartup())
            {
                $container['startup'] = $this->configuration()->getStartup();
            }
            //CD-ROM
            $isoFile = $this->getWhmcsConfigOption(ConfigurableOption::ISO_IMAGE, $this->configuration()->getIsoImage());
            $cdrom   = $this->vm()->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;
            }else{
                $container['kvm'] = 0;
            }
            //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();
            //Network create & update
            $container += $this->networkService->buildQemu();
            $container += $this->updateNetwork();
            //Cloud-Init
            $container += $this->updateCloudInit();
            //Update container
            $this->vm()->updateConfig($container);
            DB::commit();
            if($this->configuration()->isAdditionalDisk() && $this->additionalDiskService->hasDisk()){
                $this->additionalDiskService->resize();
            }elseif ($this->configuration()->isAdditionalDisk() && !$this->additionalDiskService->hasDisk()){
                $this->additionalDiskService->create();
            }
        }
        catch (\Exception $ex)
        {
            DB::rollBack();
            Api::commit();
            throw $ex;
        }

    }

    private function updateHardDisks()
    {
        $container = [];
        //IOPS & MB/s
        foreach ($this->vm()->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 ($this->vm()->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;
        }
        $config = $this->vm()->config();
        //ciuser
        if (!$this->getWhmcsCustomField(CustomField::CI_USER) && $this->configuration()->isCloudInitServiceUsername() && !$this->getWhmcsParamByKey('username') && $config['ciuser'])
        {
            $delete[] = 'ciuser';
        }
        //cipassword
        if ($this->configuration()->isCloudInitServicePassword() && (!$this->getWhmcsParamByKey('password') && !$this->getWhmcsCustomField('cipassword')) && $config['cipassword'])
        {
            $delete[] = 'cipassword';
        }
        //sshkeys
        if (!$this->getWhmcsCustomField(CustomField::SSH_PUBLIC_KEY))
        {
            $delete[] = 'sshkeys';
        }
        //searchdomain
        if (!$this->configuration()->getSearchdomain() && $config['searchdomain'])
        {
            $delete[] = 'searchdomain';
        }
        //nameserver
        if ($this->configuration()->isCloudInitServiceNameservers() && !trim($this->hosting()->ns1) && !trim($this->hosting()->ns2))
        {
            $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->getWhmcsCustomField(CustomField::CI_USER)) {
            $container['ciuser'] = $this->getWhmcsCustomField(CustomField::CI_USER);
        } elseif ($this->configuration()->getCiuser()) {
            $container['ciuser'] = $this->configuration()->getCiuser();
        } elseif ($this->configuration()->isCloudInitServiceUsername()) {
            $container['ciuser'] = $this->getWhmcsParamByKey('username');
        }
        //cipassword
        if ($this->configuration()->isCloudInitServicePassword() && $this->event->isChangeVmPassword())
        {
            $container['cipassword'] = $this->getWhmcsCustomField('cipassword') ? $this->getWhmcsCustomField('cipassword') : $this->getWhmcsParamByKey('password');
        }
        //sshkeys
        if ($this->getWhmcsCustomField(CustomField::SSH_PUBLIC_KEY))
        {
            $container['sshkeys'] = rawurlencode($this->getWhmcsCustomField(CustomField::SSH_PUBLIC_KEY));
        }
        //searchdomain
        if ($this->configuration()->getSearchdomain())
        {
            $container['searchdomain'] = $this->configuration()->getSearchdomain();
        }

        //cicustom
        if($this->hasSnippet() &&  $files = $this->getSnippetFiles()){
            /**
             * @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->getWhmcsConfigOption(ConfigurableOption::CICUSTOM) ){
            $container['cicustom'] = $this->getWhmcsConfigOption(ConfigurableOption::CICUSTOM) ;
        }
        elseif ($this->configuration()->getCicustom())
        {
            $container['cicustom'] = $this->configuration()->getCicustom();
        }
        return $container;
    }

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

}