<?php

namespace Velis\Bpm\Workflow\Handler;

use Exception;
use Psr\SimpleCache\InvalidArgumentException;
use ReflectionException;
use Velis\App;
use Velis\Bpm\Ticket\Log;
use Velis\Bpm\Ticket\Post;
use Velis\Bpm\Ticket\Ticket;
use Velis\Bpm\Workflow\Handler;
use Velis\Label;
use Velis\Lang;
use Velis\Model\DataObject\NoColumnsException;

/**
 * Ticket observer workflow handler
 * @author Olek Procki <olo@velis.pl>
 */
class TicketLabel extends Handler
{
    use TicketContextPostTrait;

    public const LABEL_ADD     = '+';
    public const LABEL_REMOVE  = '-';

    public const LABEL_TYPE_ACCESSIBLE_FOR_TICKET = 'ticket';

    /**
     * Can be overridden by subclass
     * @var bool
     */
    protected $_sendNotification;

    /**
     * Ticket instance
     * @var Ticket
     */
    protected $_ticket;

    /**
     * Label instance
     * @var Label
     */
    protected $_label;


    /**
     * Handler execution method
     *
     * @param Ticket|mixed $subject
     * @param mixed $additionalData
     * @throws InvalidArgumentException
     * @throws NoColumnsException
     */
    public function run($subject, $additionalData)
    {
        if ($subject instanceof Ticket) {
            $user = $subject->getInvokedByUser();
        }

        $user ??= App::$user;

        if (!$user->id()) {
            $this->error($subject, $additionalData, 'Insufficient context user data');
            return;
        }

        if ($subject instanceof Ticket) {
            $this->_ticket = $subject;
        } elseif (isset($subject['ticket_id'])) {
            $this->_ticket = new Ticket($subject->ticket_id);
        } else {
            $this->error($subject, $additionalData, 'Subject is not an instance of Ticket and doesn\'t provide ticket_id property');

            return;
        }

        $this->_additionalData = $additionalData;

        $labelClass = App::$config->settings->labelClass ?: Label::class;
        $this->_label = $labelClass::get($this['label_id']);
        if (!$this->_label) {
            $this->error($subject, $additionalData, 'Label not found');

            return;
        }

        // Perform no action in case label is already added or removed
        if (!$this->_isActionRequired()) {
            return;
        }

        $commit = self::$_db->startTrans();

        try {
            $post = $this->_getPost();
            $log = Log::factory($this->_ticket, $post);

            $log->append([
                'ticket_log_action_id' => Log::EDIT,
            ]);

            $this->_ticket->append([
                'user_id' => $user->id()
            ]);

            switch ($this['label_action']) {
                case self::LABEL_ADD:
                    $this->_label->mark($this->_ticket, 'ticket', false);
                    $log->addLabel($this['label_id']);
                    break;
                case self::LABEL_REMOVE:
                    $this->_label->unmark($this->_ticket, 'ticket', false);
                    $log->removeLabel($this['label_id']);
                    break;
            }

            if ($labelLog = $log->labelLog()) {
                $log['description'] = "* " . implode("\n* ", $labelLog);
                $log->add(true);

                if (!$post->id()) {
                    $post->on('add', function ($post) use ($log) {
                        /** @var Post $post */
                        $post->connectLogs([$log]);
                    });
                }

                if ($this->_sendNotification) {
                    $log->notify();
                }
            }

            if ($commit) {
                self::$_db->commit();
            }
            $this->success($subject);
        } catch (Exception $e) {
            if ($commit) {
                self::$_db->rollback();
            }
            $this->error($subject, $additionalData, $e);
        }
    }


    /**
     * Returns true if label needs to be added/removed
     * @return bool
     */
    protected function _isActionRequired()
    {
        switch ($this['label_action']) {
            case self::LABEL_ADD:
                return !$this->_ticket->hasLabel($this->_label);
            case self::LABEL_REMOVE:
                return $this->_ticket->hasLabel($this->_label);
            default:
                return false;
        }
    }


    /**
     * Returns parameters reflection info
     * @return array
     * @throws ReflectionException
     */
    public static function getParamsReflection()
    {
        $labelsArray = array_filter(
            App::$user->getLabels(),
            fn ($el) => $el->label_type_id === self::LABEL_TYPE_ACCESSIBLE_FOR_TICKET
        );

        return [
            'label_id' => [
                'label'   => Lang::get('GENERAL_LABEL'),
                'options' => $labelsArray,
            ],
            'label_action' => [
                'label'   => trim(Lang::get('GENERAL_ADD')) . '/' . trim(Lang::get('GENERAL_DELETE')),
                'options' => [
                    self::LABEL_ADD    => trim(Lang::get('GENERAL_ADD')),
                    self::LABEL_REMOVE => trim(Lang::get('GENERAL_DELETE'))
                ]
            ]
        ];
    }
}
