<?php


namespace ModulesGarden\ProxmoxAddon\App\Jobs\Vps;

use Illuminate\Database\Capsule\Manager as DB;
use MGProvision\Proxmox\v2\models\Node;
use ModulesGarden\ProxmoxAddon\App\Models\RangeVm;
use ModulesGarden\ProxmoxAddon\App\Models\TaskHistory;
use ModulesGarden\ProxmoxAddon\App\Models\VmModel;
use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\ActivityLog;
use ModulesGarden\ProxmoxAddon\App\Models\Whmcs\ToDoList;
use ModulesGarden\ProxmoxAddon\App\Repositories\RangeVmRepository;
use ModulesGarden\ProxmoxAddon\App\Repositories\Vps\ProductConfigurationRepository;
use ModulesGarden\ProxmoxAddon\App\Services\ApiService;
use ModulesGarden\ProxmoxAddon\App\Services\EmailService;
use ModulesGarden\ProxmoxAddon\App\Services\Vps\AdditionalDiskService;
use ModulesGarden\ProxmoxAddon\App\Services\Vps\AdditionalMountPointService;
use ModulesGarden\ProxmoxAddon\App\Services\Vps\AgentService;
use ModulesGarden\ProxmoxAddon\App\Services\Vps\ContainerService;
use ModulesGarden\ProxmoxAddon\App\Services\Vps\HostingService;
use ModulesGarden\ProxmoxAddon\Core\Models\Whmcs\Configuration;
use ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
use ModulesGarden\ProxmoxAddon\Core\Queue\Job;
use ModulesGarden\ProxmoxAddon\Core\UI\Traits\WhmcsParams;
use ModulesGarden\ProxmoxAddon\Core\Traits\Smarty;
use function ModulesGarden\ProxmoxAddon\Core\Helper\sl;

/**
 * Class BaseJob
 * @package ModulesGarden\ProxmoxAddon\App\Jobs\Vps
 * @method ProductConfigurationRepository configuration()
 */
class BaseJob extends Job
{
    use ApiService;
    use WhmcsParams;
    use HostingService;
    use Smarty;

    protected $params = [];

    /**
     * @var EmailService
     */
    protected $emailService;
    /**
     * @var ContainerService
     */
    protected $containerService;

    /**
     * @var AgentService
     */
    protected $agentService;
    /**
     * @var AdditionalDiskService
     */
    protected $additionalDiskService;

    /**
     * @var AdditionalMountPointService
     */
    protected $additionalMountPointService;

    protected function initServices()
    {
        $this->emailService     = new EmailService();
        $this->containerService = new ContainerService();
        $this->agentService = new AgentService();
        $this->additionalDiskService = new AdditionalDiskService();
        $this->additionalMountPointService  = new AdditionalMountPointService();

    }

    public function initParams()
    {
        if (!$this->model->rel_id)
        {
            new \InvalidArgumentException(sprintf("Job model: #%s rel_id cannot be empty", $this->model->id));
        }
        if (!function_exists('ModuleBuildParams'))
        {
            require_once ModuleConstants::getFullPathWhmcs('includes') . DIRECTORY_SEPARATOR . "modulefunctions.php";
        }
        $this->params = \ModuleBuildParams($this->model->rel_id);
        sl("whmcsParams")->setParams($this->params);
        \ModulesGarden\Servers\ProxmoxVps\Core\Helper\sl("whmcsParams")->setParams($this->params);
        $this->setHostingId($this->params['serviceid']);
        return $this;
    }

    protected function sleep($seconds = 60)
    {
        $this->model->setWaiting();
        $this->model->setRetryAfter(date("Y-m-d H:i:s", strtotime("+{$seconds} seconds")));
        $this->model->increaseRetryCount();
    }

    protected function vmidExistInWhmcs($vmid)
    {
        //vps
        $cfv   = 'tblcustomfieldsvalues';
        $cfn   = 'tblcustomfields';
        $h     = 'tblhosting';
        $query = DB::table($cfv)
            ->rightJoin($cfn, "{$cfn}.id", "=", "{$cfv}.fieldid")
            ->leftJoin($h, "{$h}.id", "=", "{$cfv}.relid")
            ->where("{$cfn}.fieldname", "like", "vmid%")
            ->where("{$cfv}.value", $vmid)
            ->whereIn("{$h}.domainstatus", ['Active', "Suspended"]);
        if ($query->count())
        {
            return true;
        }
        //cloud
        try
        {
            $query = VmModel::where("vmid", $vmid);
            return $query->count() > 0;
        }
        catch (\Exception $ex)
        { // table does not exist
        }
        return false;
    }

    protected function findFreeVmid($vmid)
    {
        for ($i = $vmid; $i <= 1000000; $i++)
        {
            if ($this->vmidExistInWhmcs($i))
            {
                continue;
            }
            try
            {
                $res = $this->api()->get("/cluster/nextid", ['vmid' => $i]);
            }
            catch (\Exception $ex)
            {
                continue;
            }
            if ($res == $i)
            {
                return $i;
            }
        }
        throw new \Exception("Unable to obtain vmid");
    }

    protected function isVmRange()
    {
        if (RangeVm::ofServerId($this->getWhmcsParamByKey("serverid"))->count())
        {
            return true;
        }
        return Configuration::where("setting", "proxmoxVPSMinimumVMID")->count() > 0;
    }

    protected function nextVmid()
    {
        $data = $this->api()->get("/cluster/nextid", []);
        $vmid = (int)$data ? (int)$data : 100;
        $vmid = $this->findFreeVmid($vmid);
        if ($this->isVmRange() || is_numeric($this->configuration()->getMinimumVmid()))
        {
            $rageVm = new RangeVmRepository($this->getWhmcsParamByKey('serverid'));
            if(is_numeric($this->configuration()->getMinimumVmid())){
                $rageVm->setMin($this->configuration()->getMinimumVmid());
            }
            if (!$rageVm->has() && !$rageVm->getMin())
            {
                return $vmid;
            }
            else
            {
                if (!$rageVm->getMax() && $rageVm->getMin())
                {
                    $from = (int)$rageVm->getMin();
                    $to   = (int)$rageVm->getMin() * 100;
                }
                else
                {
                    $from = (int)$rageVm->getMin();
                    $to   = (int)$rageVm->getMax();
                }
            }
            for ($i = $from; $i <= $to; $i++)
            {
                try
                {
                    if ($this->vmidExistInWhmcs($i))
                    {
                        continue;
                    }
                    $res = $this->api()->get("/cluster/nextid", ['vmid' => $i]);
                    if ((int)$res == $i)
                    {
                        $vmID = $i;
                        break;
                    }
                }
                catch (\Exception $ex)
                {
                    continue;
                }
            }
            if (!$vmID)
            {
                throw new \Exception("VM Ranges have been exited for this server. Please setup VM Ranges", 321);
            }
        }
        return $vmID ? $vmID : $vmid;
    }

    protected function createTaskHistory($taskId, $action)
    {
        $type = str_replace(["qemu", "lxc"], ["VM", "CT"], $this->vm()->getVirtualization());
        $task = new TaskHistory();
        $task->fill([
            'hosting_id' => $this->getWhmcsParamByKey("serviceid"),
            'upid'       => $taskId,
            'name'       => sprintf("%s %s - %s", $type, $this->vm()->getVmid(), $action),
            'vmid'       => $this->vm()->getVmid(),
            'node'       => $this->vm()->getNode(),
            'status'     => 0
        ])->save();
    }

    protected function getModelData()
    {
        return unserialize($this->model->data);
    }

    protected function putModelDataAndSave(array $newData)
    {

        $data = $this->getModelData();
        $data += $newData;
        $this->setModelDataAndSave($data);
        return $this;
    }

    protected function setModelDataAndSave(array $data)
    {
        $this->model->data = serialize($data);
        $this->model->save();
        return $this;
    }

    /**
     * @return \MGProvision\Proxmox\v2\models\Task
     * @throws \MGProvision\Proxmox\v2\ProxmoxApiException
     */
    protected function getTask()
    {
        $taskId = $this->getModelData()['taskId'];
        //Init API service
        if($this->getModelData()['templateNode']){
            $node = new Node($this->getModelData()['templateNode']);
        }else{
            $node = new Node($this->getModelData()['node']);
        }
        $node->setApi($this->api());
        return $node->task($taskId);
    }

    protected function isTask()
    {
        return !is_null($this->getModelData()['taskId']);
    }

    protected function isDone()
    {
        $taskId = $this->getModelData()['taskId'];
        if (!$taskId || !$this->getModelData()['node'])
        {
            return false;
        }
        $task = $this->getTask();
        if ($task->getStatus() == "running")
        {
            $this->log->success(sprintf("Waiting to finish. Task Id %s Node: %s ", $task->getUpid(), $task->getNode()));
            return false;
        }
        //Failed
        if ($task->getExitstatus() && $task->getExitstatus() != "OK")
        {
            $this->log->error($task->getExitstatus());
            $this->failed($task->getExitstatus());
            $data = $this->getModelData();
            unset($data['taskId'], $data['node']);
            $this->setModelDataAndSave($data);
            return false;
        }
        return true;
    }

    protected function isTaskRunning()
    {
        return $this->isTask() && $this->getTask()->isRunning();
    }

    protected function isFailed()
    {
        $task = $this->getTask();
        return $task->getExitstatus() && $task->getExitstatus() != "OK";
    }

    protected function failed($error)
    {

        if ((int)$this->model->retry_count != 21)
        {
            return;
        }
        if (!preg_match("/Create/", $this->model->job) || preg_match("/Clone/", $this->model->job))
        {
            return;
        }
        //create new entery on to do list
        if ($this->configuration()->isToDoList())
        {
            $title = sprintf('Creation Failed - Service ID: %s', $this->getWhmcsParamByKey('serviceid'));
            if (ToDoList::ofTitle($title)->pending()->count())
            {
                return;
            }
            $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" => $error,
                    "admin"       => 0,
                ]
            );
            $entity->save();
        }
        //send admin message
        if ($this->configuration()->getServiceCreationFailedTemplateId())
        {
            //init email template
            $this->emailService->template($this->configuration()->getServiceCreationFailedTemplateId());
            //check if already send
            $description = printf('Email Sent to Admin (%s) - Service ID: %s', $this->emailService->getVars()['messagename'], $this->getWhmcsParamByKey('serviceid'));
            if (ActivityLog::ofDescription($description)->today()->count() > 0)
            {
                return;
            }
            //email vars
            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'),
                "error_msg"        => $error,
                "whmcs_admin_link" => "<a href='{$adminAreaLink}/'>{$adminAreaLink}</a>",
            ];
            $this->emailService->vars($emailVars)->sendToAdmin();
            logActivity($description);
        }
    }

    /**
     * @return \ModulesGarden\ProxmoxAddon\App\Models\Job:
     */
    protected function getParentModel()
    {
        if (is_null($this->model->parent_id))
        {
            throw new \InvalidArgumentException("The Parent Id is not valid");
        }
        if ($this->parent)
        {
            return $this->parent;
        }
        return $this->parent = \ModulesGarden\ProxmoxAddon\App\Models\Job::ofId($this->model->parent_id)->firstOrFail();
    }

    protected function getParentModelData()
    {
        return unserialize($this->getParentModel()->data);
    }

}