<?php

// SPDX-FileCopyrightText: 2023 Icinga GmbH <https://icinga.com>
// SPDX-License-Identifier: GPL-3.0-or-later

namespace Icinga\Module\X509\Forms\Jobs;

use DateTime;
use Exception;
use Icinga\Application\Icinga;
use Icinga\Application\Web;
use Icinga\Authentication\Auth;
use Icinga\Module\X509\Common\Database;
use Icinga\Module\X509\Common\ScanType;
use Icinga\Module\X509\Model\X509Schedule;
use Icinga\User;
use Icinga\Util\Json;
use Icinga\Web\Notification;
use ipl\Html\Attributes;
use ipl\Html\BaseHtmlElement;
use ipl\Html\Contract\FormSubmitElement;
use ipl\Html\HtmlDocument;
use ipl\Html\HtmlElement;
use ipl\Html\Text;
use ipl\Validator\CallbackValidator;
use ipl\Web\Compat\CompatForm;
use ipl\Web\FormElement\ScheduleElement;
use Psr\Http\Message\RequestInterface;

use function ipl\Stdlib\get_php_type;

class ScheduleForm extends CompatForm
{
    /** @var int */
    protected $jobId;

    /** @var ?X509Schedule */
    protected $schedule;

    /** @var ScheduleElement */
    protected $scheduleElement;

    public function __construct(?X509Schedule $schedule = null)
    {
        $this->schedule = $schedule;

        if (method_exists($this, 'applyDefaultElementDecorators')) {
            $this->applyDefaultElementDecorators();
            $this->scheduleElement = $this->createElement('schedule', 'schedule_element');
        } else {
            $this->scheduleElement = new ScheduleElement('schedule_element');
        }

        /** @var Web $app */
        $app = Icinga::app();
        $this->scheduleElement->setIdProtector([$app->getRequest(), 'protectId']);
    }

    protected function isUpdating(): bool
    {
        return $this->schedule !== null;
    }

    public function setJobId(int $jobId): self
    {
        $this->jobId = $jobId;

        return $this;
    }

    /**
     * Get multipart updates
     *
     * @return array<int, BaseHtmlElement>
     */
    public function getPartUpdates(): array
    {
        /** @var RequestInterface $request */
        $request = $this->getRequest();

        return $this->scheduleElement->prepareMultipartUpdate($request);
    }

    public function hasBeenSubmitted(): bool
    {
        if (! $this->hasBeenSent()) {
            return false;
        }

        $button = $this->getPressedSubmitElement();

        return $button && ($button->getName() === 'submit' || $button->getName() === 'btn_remove');
    }

    protected function assemble(): void
    {
        $this->addElement('text', 'name', [
            'required'    => true,
            'label'       => $this->translate('Name'),
            'description' => $this->translate('Schedule name'),
        ]);

        $typeSwitcher = HtmlElement::create('fieldset', Attributes::create(['class' => 'type-switcher']));
        $descriptions = HtmlElement::create('div');
        $populatedScanType = $this->getPopulatedValue('scan_type') ?? ScanType::PARTIAL->value;
        $request = Icinga::app()->getRequest();

        foreach (ScanType::cases() as $type) {
            // Protect the id for the case that the form is open in multiple containers
            $protectedId = $request->protectId('scan_type-' . $type->value);
            $chosenType = $type->value === $populatedScanType;

            $descriptions->addHtml(HtmlElement::create('p', null, [
                // Render bold label for the chosen type
                $chosenType ? HtmlElement::create('strong', null, $type->label()) : Text::create($type->label()),
                Text::create(': ' . $type->description())
            ]));

            $radio = $this->createElement('input', 'scan_type', [
                'type'    => 'radio',
                'class'   => 'autosubmit',
                'id'      => $protectedId,
                'value'   => $type->value,
                'checked' => $chosenType
            ]);

            // Register only the chosen element
            if ($chosenType) {
                $this->registerElement($radio);
            }

            $typeSwitcher->addHtml(
                $radio,
                HtmlElement::create(
                    'label',
                    Attributes::create(['for' => $protectedId, 'title' => $type->description()]),
                    Text::create($type->label())
                )
            );
        }

        $this->addHtml(HtmlElement::create(
            'div',
            Attributes::create(['class' => 'control-group']),
            [
                HtmlElement::create(
                    'div',
                    Attributes::create(['class' => 'control-label-group']),
                    Text::create($this->translate('Scan Type'))
                ),
                $typeSwitcher
            ]
        ));

        $this->addHtml(HtmlElement::create(
            'div',
            Attributes::create(['class' => 'control-group no-flex-wrap']),
            [
                HtmlElement::create('div', Attributes::create(['class' => 'control-label-group no-flex-shrink'])),
                $descriptions
            ]
        ));

        if (ScanType::from($populatedScanType) !== ScanType::FULL) {
            $this->addElement('text', 'since_last_scan', [
                'required'    => false,
                'label'       => $this->translate('Since Last Scan'),
                'placeholder' => '-24 hours',
                'description' => $this->translate(
                    'Scan targets whose last scan is older than the specified date/time, which can also be an'
                    . ' English textual datetime description like "2 days". If you want to scan only unknown targets'
                    . ' you can set this to "null".'
                ),
                'validators'  => [
                    new CallbackValidator(function ($value, CallbackValidator $validator) {
                        if ($value !== null && $value !== 'null') {
                            try {
                                new DateTime($value);
                            } catch (Exception $_) {
                                $validator->addMessage($this->translate('Invalid textual date time'));

                                return false;
                            }
                        }

                        return true;
                    })
                ]
            ]);
        }

        $this->addHtml(HtmlElement::create('div', ['class' => 'schedule-element-separator']));
        $this->addElement($this->scheduleElement);

        $this->addElement('submit', 'submit', [
            'label' => $this->isUpdating() ? $this->translate('Update') : $this->translate('Schedule')
        ]);

        if ($this->isUpdating()) {
            $removeButton = $this->createElement('submit', 'btn_remove', [
                'class' => 'btn-remove',
                'label' => $this->translate('Remove Schedule'),
            ]);
            $this->registerElement($removeButton);

            $this->getElement('submit')->prependWrapper(
                (new HtmlDocument())->addHtml($removeButton)
            );
        }
    }

    protected function onSuccess(): void
    {
        /** @var X509Schedule $schedule */
        $schedule = $this->schedule;
        $conn = Database::get();
        /** @var FormSubmitElement $submitElement */
        $submitElement = $this->getPressedSubmitElement();
        if ($submitElement->getName() === 'btn_remove') {
            $conn->delete('x509_schedule', ['id = ?' => $schedule->id]);

            Notification::success($this->translate('Deleted schedule successfully'));
        } else {
            $config = $this->getValues();
            unset($config['name']);
            unset($config['schedule_element']);
            unset($config['scan_type']);

            $frequency = $this->scheduleElement->getValue();
            $config['type'] = get_php_type($frequency);
            $config['frequency'] = Json::encode($frequency);

            $scanType = ScanType::from($this->getValue('scan_type'));
            $config['full_scan'] = $scanType === ScanType::FULL ? 'y' : 'n';
            $config['rescan'] = $scanType === ScanType::RE ? 'y' : 'n';

            /** @var User $user */
            $user = Auth::getInstance()->getUser();
            if (! $this->isUpdating()) {
                $conn->insert('x509_schedule', [
                    'job_id' => $this->schedule ? $this->schedule->job_id : $this->jobId,
                    'name'   => $this->getValue('name'),
                    'author' => $user->getUsername(),
                    'config' => Json::encode($config),
                    'ctime'  => (new DateTime())->getTimestamp() * 1000.0,
                    'mtime'  => (new DateTime())->getTimestamp() * 1000.0
                ]);
                $message = $this->translate('Created schedule successfully');
            } else {
                $conn->update('x509_schedule', [
                    'name'   => $this->getValue('name'),
                    'config' => Json::encode($config),
                    'mtime'  => (new DateTime())->getTimestamp() * 1000.0
                ], ['id = ?' => $schedule->id]);
                $message = $this->translate('Updated schedule successfully');
            }

            Notification::success($message);
        }
    }
}
