<?php
/**********************************************************************
 * ProxmoxVPS developed. (26.03.19)
 * *
 *
 *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
 *  CONTACT                        ->       contact@modulesgarden.com
 *
 *
 * This software is furnished under a license and may be used and copied
 * only  in  accordance  with  the  terms  of such  license and with the
 * inclusion of the above copyright notice.  This software  or any other
 * copies thereof may not be provided or otherwise made available to any
 * other person.  No title to and  ownership of the  software is  hereby
 * transferred.
 *
 *
 **********************************************************************/

namespace ModulesGarden\Servers\ProxmoxVps\App\Http\Actions;

use Illuminate\Database\Capsule\Manager as DB;
use ModulesGarden\ProxmoxAddon\App\Events\Vps\LxcUpdateEvent;
use ModulesGarden\ProxmoxAddon\App\Events\Vps\QemuUpdateEvent;
use ModulesGarden\ProxmoxAddon\App\Jobs\Vps\Agent\ConfigureNetworkJob;
use ModulesGarden\ProxmoxAddon\App\Jobs\Vps\LoadBalancer as LoadBalancer;
use ModulesGarden\ProxmoxAddon\App\Jobs\Vps\RebootVmJob;
use ModulesGarden\ProxmoxAddon\App\Jobs\Vps\Reinstall\BackupVmJob;
use ModulesGarden\ProxmoxAddon\App\Jobs\Vps\Reinstall\CreateVmJob;
use ModulesGarden\ProxmoxAddon\App\Jobs\Vps\Reinstall\DeleteVmJob;
use ModulesGarden\ProxmoxAddon\App\Libs\Format;
use ModulesGarden\ProxmoxAddon\App\Models\Job;
use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\ToDoList;
use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\Upgrade;
use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
use ModulesGarden\ProxmoxAddon\App\Services\EmailService;
use ModulesGarden\ProxmoxAddon\App\Services\LoadBalancerService;
use ModulesGarden\ProxmoxAddon\App\Services\Utility;
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\Servers\ProxmoxVps\App\Helpers\AppParams;
use ModulesGarden\Servers\ProxmoxVps\App\Helpers\ProxmoxAddonValidator;
use ModulesGarden\Servers\ProxmoxVps\Core\App\Controllers\Instances\AddonController;
use ModulesGarden\Servers\ProxmoxVps\Core\UI\Traits\WhmcsParams;
use function ModulesGarden\ProxmoxAddon\Core\Helper\fire;
use function ModulesGarden\ProxmoxAddon\Core\Helper\queue;
use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;
use ModulesGarden\Servers\ProxmoxVps\App\Enum\ConfigurableOption;


class ChangePackage extends AddonController
{
    use WhmcsParams;
    use ApiService;
    use ProductService;
    use UserService;
    use HostingService;
    /**
     * @var NetworkService
     */
    private $networkService;
    /**
     * @var IpSetIpFilterService
     */
    private $ipSetIpFilterService;
    /**
     * @var EmailService
     */
    private $emailService;

    /**
     * ChangePackage constructor.
     * @param $networkService
     */
    public function __construct()
    {
        $this->networkService       = new NetworkService();
        $this->ipSetIpFilterService = new IpSetIpFilterService();
        $this->emailService         = new EmailService();
    }

    public function execute($params = null)
    {
        if(!ProxmoxAddonValidator::isInstalled()){
            return ProxmoxAddonValidator::failAsString();
        }
        (new AppParams())->initFromWhmcsParams();
        try
        {
            $this->manuallyUpgrade();
            //Disk
            $this->diskSizeValidation();
            $this->createReinstallJob();
            //Add IP Addresses
            if (!Utility::isIpManagerProxmoxVPSIntegration())
            {
                list($requestIPv4, $requestIPv6) = $this->networkService->getIpAddressesRequest();

                $this->networkService->hasIp($requestIPv4, $requestIPv6, $this->vm()->getNode())
                    ->addIp($requestIPv4, $requestIPv6, $this->vm()->getNode());
            }
            //Unassing ip addresses and  delete network
            if(!Utility::isIpManagerProxmoxVPSIntegration())
            {
                $this->networkService->unassignIpAddressesAndDeleteNetwork();
            }

            //Load Balancer
            if (!Job::waiting()->ofHostingId($this->getWhmcsParamByKey("serviceid"))->ofJob(LoadBalancer\UpgradeVmJob::class)->count() && $this->configuration()->isLoadBalancer() && in_array($this->configuration()->getLoadBalancerOnUpgrade(), ["block", "migrate"]))
            {
                $loadBalancer = new LoadBalancerService($this->getWhmcsParamByKey('serverid'));
                $loadBalancer->setApi($this->api());
                //Disk
                $disk = $this->configuration()->getDiskSize();
                Utility::unitFormat($disk, "gb", 'bytes');
                if ($this->getWhmcsConfigOption(ConfigurableOption::DISK_SIZE))
                {
                    $disk = $this->getWhmcsConfigOption(ConfigurableOption::DISK_SIZE);
                    Utility::unitFormat($disk, $this->configuration()->getDiskUnit(), 'bytes');
                }
                //RAM
                $ram = $this->configuration()->getMemory();
                Utility::unitFormat($ram, "mb", 'bytes');
                if ($this->getWhmcsConfigOption(ConfigurableOption::MEMORY))
                {
                    $ram = $this->getWhmcsConfigOption(ConfigurableOption::MEMORY);
                    Utility::unitFormat($ram, $this->configuration()->getMemoryUnit(), 'bytes');
                }
                if ($this->configuration()->isQemu())
                {
                    $socket = $this->getWhmcsConfigOption(ConfigurableOption::SOCKETS,$this->configuration()->getSockets() );
                    $cores  = $this->getWhmcsConfigOption(ConfigurableOption::CORES_PER_SOCKET, $this->configuration()->getCores()) ;
                    $cpu    = $socket * $cores;
                }
                else if ($this->configuration()->isLxc())
                {
                    $cpu = $this->getWhmcsConfigOption(ConfigurableOption::CORES, $this->configuration()->getCores()) ;
                }
                $loadBalancer->setExcludeVmid($this->vm()->getVmid());
                //Upgrade on current node
                if ($this->configuration()->getLoadBalancerOnUpgrade() == "block")
                {
                    $loadBalancerNodes = $loadBalancer->findByNode($this->vm()->getNode());
                    $loadBalancerNodes = $loadBalancerNodes->findByRam($ram)
                        ->findByCpu($cpu)
                        ->findByDisk($disk);
                    if ($loadBalancerNodes->isEmpty())
                    {
                        throw new \Exception("Load Balancer: Cannot find free resources on node: " . $this->vm()->getNode());
                    }
                }
                else
                {
                    if ($this->configuration()->getLoadBalancerOnUpgrade() == "migrate")
                    { //Allow to upgrade process, after previous migration vm to the server with free space.
                        $loadBalancerNodes = $loadBalancer->findByNode($this->vm()->getNode());
                        $loadBalancerNodes = $loadBalancerNodes->findByRam($ram)
                            ->findByCpu($cpu)
                            ->findByDisk($disk);
                        if ($loadBalancerNodes->isEmpty())
                        {
                            $loadBalancerNodes = $loadBalancer->findByVmCreate();
                            $nodesForUser      = $loadBalancer->findNotUser($this->getWhmcsParamByKey('userid'));
                            if (!$nodesForUser->isEmpty())
                            {
                                $loadBalancerNodes = $nodesForUser;
                            }
                            $targetNode = $loadBalancerNodes->findByRam($ram)
                                ->findByCpu($cpu)
                                ->findByDisk($disk)
                                ->findByVms(1)
                                ->nodeLowLoad();
                            if ($this->configuration()->isLoadBalancerShutdownOnUpgrade())
                            {
                                $job = queue(LoadBalancer\ShutdownVmJob::class, ['hostingId' => $this->getWhmcsParamByKey('serviceid'), "targetNode" => $targetNode], null, "hosting", $this->getWhmcsParamByKey("serviceid"));
                            }
                            $job = queue(LoadBalancer\MigrateVmJob::class, ['hostingId' => $this->getWhmcsParamByKey('serviceid'), "targetNode" => $targetNode], $job->id, "hosting", $this->getWhmcsParamByKey("serviceid"));
                            queue(LoadBalancer\UpgradeVmJob::class, ['hostingId' => $this->getWhmcsParamByKey('serviceid'), "targetNode" => $targetNode], $job->id, "hosting", $this->getWhmcsParamByKey("serviceid"));
                            return true;
                        }
                    }
                }
            }
            if ($this->configuration()->isQemu())
            {
                $evnet = new QemuUpdateEvent($this->vm());
                $evnet->setChangeVmPassword(false);
                fire($evnet);
            }
            elseif ($this->configuration()->isLxc())
            {
                fire(new LxcUpdateEvent($this->vm()));
            }
            //hosting limit
            $this->saveUsageLimit();
            //reboot
            if ($this->configuration()->isRebootVmAfterChangePackage() &&
                $this->vm()->isRunning() &&
                !Job::waiting()->ofJob(RebootVmJob::class)->ofHostingId($this->getWhmcsParamByKey("serviceid"))->count())
            {
                $parentId = queue(RebootVmJob::class, ['hostingId' => $this->getWhmcsParamByKey('serviceid')], null, "hosting", $this->getWhmcsParamByKey("serviceid"));
            }
            if($this->configuration()->isAgentConfigureNetwork()){
                $parentId = queue(ConfigureNetworkJob::class, ['hostingId' => $this->getWhmcsParamByKey('serviceid')], $parentId, "hosting", $this->getWhmcsParamByKey("serviceid"));
            }
            //IP Filter
            if ($this->configuration()->isIpsetIpFilter())
            {
                $this->ipSetIpFilterService->create();
            }
            return "success";
        }
        catch (\Exception $ex)
        {
            return $ex->getMessage();
        }
    }

    private function manuallyUpgrade()
    {
        $scriptFile = substr($_SERVER['SCRIPT_NAME'], strrpos($_SERVER['SCRIPT_NAME'], DIRECTORY_SEPARATOR) + 1);
        if (!$this->configuration()->getUpgradeNotificationTemplateId() || $scriptFile == "clientsservices.php")
        {
            return;
        }
        if ($this->configuration()->isToDoList())
        {
            $title = sprintf('Manually Upgrade - Service ID: %s', $this->getWhmcsParamByKey('serviceid'));
            if (!ToDoList::ofTitle($title)->pending()->count())
            {
                $entity = new ToDoList();
                $entity->fill(
                    [
                        'date'        => date("Y-m-d H:i:s"),
                        "duedate"     => date("Y-m-d H:i:s"),
                        'title'       => $title,
                        "status"      => "Pending",
                        "description" => "An upgrade order has received its payment, automatic upgrades  has been disabled and requires manually processing.",
                        "admin"       => 0,
                    ]
                );
                $entity->save();
            }
        }
        //Send Admin notification
        $this->emailService->template($this->configuration()->getUpgradeNotificationTemplateId());
        global $customadminpath;
        $adminDir      = $customadminpath ? $customadminpath : "admin";
        $adminAreaLink = $GLOBALS['CONFIG']['SystemURL'] . "/{$adminDir}";
        $hosting       = $GLOBALS['CONFIG']['SystemURL'] . "/{$adminDir}/clientsservices.php?id=" . $this->getWhmcsParamByKey('serviceid');
        $emailVars     = [
            "client_id"        => $this->getWhmcsParamByKey('clientsdetails')['id'],
            "service_id"       => $this->getWhmcsParamByKey('serviceid'),
            "service_product"  => "<a href='{$hosting}/'>{$hosting}</a>",
            "service_domain"   => $this->getWhmcsParamByKey('domain'),
            "whmcs_admin_link" => "<a href='{$adminAreaLink}/'>{$adminAreaLink}</a>",
        ];
        $this->emailService->vars($emailVars)->sendToAdmin();
        throw new \Exception("Automaticall upgrade  has been disabled by admin");
    }

    private function createReinstallJob()
    {
        if (!$this->getWhmcsConfigOption(ConfigurableOption::OS_TEMPLATE))
        {
            return;
        }
        if (!Upgrade::ofHostingId($this->getWhmcsParamByKey('serviceid'))->pending()->today()->count())
        {
            return;
        }

        $pg       = "tblproductconfiggroups";
        $pl       = "tblproductconfiglinks";
        $po       = "tblproductconfigoptions";
        $optionId = DB::table($po)
            ->select("{$po}.id")
            ->leftJoin($pg, "{$pg}.id", "=", "{$po}.gid")
            ->leftJoin($pl, "{$pl}.gid", "=", "{$pg}.id")
            ->where("{$pl}.pid", $this->getWhmcsParamByKey("pid"))
            ->where("{$po}.optionname", "like", "VM Template|%")
            ->value("id");
        if (!$optionId)
        {
            return;
        }
        if (!Upgrade::ofHostingId($this->getWhmcsParamByKey('serviceid'))->pending()->today()->ofOptionId($optionId)->count())
        {
            return;
        }
        if ($this->configuration()->isQemu())
        {
            if ($this->getWhmcsConfigOption(ConfigurableOption::OS_TEMPLATE)=== "0")
            {
                return;
            }
        }
        $arguments = [
            'hostingId'  => $this->getWhmcsParamByKey('serviceid'),
            "osTemplate" => $this->getWhmcsConfigOption(ConfigurableOption::OS_TEMPLATE),
            "password"   => encrypt($this->getWhmcsParamByKey("password"))
        ];
        if ($this->configuration()->isBackupVmBeforeReinstall())
        {
            $job = queue(BackupVmJob::class, $arguments, null, "hosting", $this->getWhmcsParamByKey("serviceid"));
        }
        $job = queue(DeleteVmJob::class, $arguments, $job->id, "hosting", $this->getWhmcsParamByKey("serviceid"));
        $job = queue(CreateVmJob::class, $arguments, $job->id, "hosting", $this->getWhmcsParamByKey("serviceid"));
    }

    protected function diskSizeValidation(){
        $diskSize = $this->configuration()->getDiskSize();
        if ($this->getWhmcsConfigOption(ConfigurableOption::DISK_SIZE))
        {
            $diskSize = $this->getWhmcsConfigOption(ConfigurableOption::DISK_SIZE);
            Utility::unitFormat($diskSize, $this->configuration()->getDiskUnit(), 'bytes');
        }else{
            Utility::unitFormat($diskSize, "gb", 'bytes');
        }
        $masterHddSize = $this->vm()->getMasterHddSize();
        Utility::unitFormat($masterHddSize, "gb", 'bytes');
        if ($diskSize < $masterHddSize)
        {
            throw new \Exception(sprintf('Downgrading disk size  is restricted. Request Disk size: %s, VM disk size: %s',
                Format::convertBytes($diskSize), Format::convertBytes($masterHddSize)));
        }
    }
}