<?php
// Copyright (c) Pickware GmbH. All rights reserved.
// This file is part of software that is released under a proprietary license.
// You must not copy, modify, distribute, make publicly available, or execute
// its contents or parts thereof without express permission by the copyright
// holder, unless otherwise permitted by law.

namespace Shopware\Plugins\ViisonCommon\Classes\Subscribers;

use Shopware\Plugins\ViisonCommon\Classes\Installation\SQLHelper;
use Shopware\Plugins\ViisonCommon\Classes\Installation\AttributeConfiguration\InstallationHelper as AttributeConfigurationInstallationHelper;
use Shopware\Plugins\ViisonCommon\Classes\Installation\AttributeConfiguration\UninstallationHelper as AttributeConfigurationUninstallationHelper;

/**
 * Add a date field to an order's attributes that can be used to track whether a specific activity (i.e. process step)
 * has already been performed for the order (and when) or not.
 */
// phpcs:ignore VIISON.Classes.AbstractClassName
abstract class OrderActivityPerformedDateSubscriber extends AbstractBaseSubscriber
{
    /** @var string $attributeName the name of the date attribute */
    private $attributeName;

    /**
     * @param \Shopware_Components_Plugin_Bootstrap $pluginBootstrap
     * @param string $attributeName the name of the date attribute
     */
    public function __construct(\Shopware_Components_Plugin_Bootstrap $pluginBootstrap, $attributeName)
    {
        parent::__construct($pluginBootstrap);
        $this->attributeName = $attributeName;
    }

    /**
     * @inheritdoc
     */
    public static function getSubscribedEvents()
    {
        return [
            'Shopware_Controllers_Backend_AttributeData::loadDataAction::after' => 'onAfterLoadAttributeData',
            'Shopware_Controllers_Backend_AttributeData::saveDataAction::before' => 'onBeforeSaveAttributeData',
        ];
    }

    /**
     * Hook loading of Attributes, since viison_drop_shipping_mails_dispatch_date is added to the view via
     * Shopware\Models\Attribute\Configuration we have to manipulate the attributes directly.
     * Add custom message to viison_drop_shipping_mails_dispatch_date if no date is set (yet)
     *
     * @param \Enlight_Hook_HookArgs $args
     */
    public function onAfterLoadAttributeData(\Enlight_Hook_HookArgs $args)
    {
        $attributeTable = $args->getSubject()->Request()->getParam('_table');
        // Only manipulate order attributes
        if ($attributeTable != 's_order_attributes') {
            return;
        }

        // Add custom message to viison_drop_shipping_mails_dispatch_date if empty
        $viewAssigned = $args->getSubject()->View()->getAssign();
        $data = $viewAssigned['data'];
        $orderId = $data['__attribute_orderID'];

        // Check if the date is set. For some reason, a null column value will return '0000-00-00 00:00:00' (probably a
        // DBAL bug), so we need to do a silly workaround dance here. I imagine it kind of looks like the Macarena.
        $fieldValue = $data[$this->getAttributeFieldName()];
        $dateTime = self::getValidDate($fieldValue);
        if (!$dateTime) {
            $viewAssigned['data'][$this->getAttributeFieldName()] = $this->getOrderHasNoDateMessage($orderId);
        } else {
            // overwrite with parsed date so the date is formatted properly in the backend
            $viewAssigned['data'][$this->getAttributeFieldName()] = $dateTime;
        }
        $args->getSubject()->View()->assign($viewAssigned);
    }

    /**
     * Make sure the attribute value is never written so the date is not overwritten with placeholder text (i.e. nulled)
     *
     * @param \Enlight_Hook_HookArgs $args
     */
    public function onBeforeSaveAttributeData(\Enlight_Hook_HookArgs $args)
    {
        /** @var \Enlight_Controller_Request_RequestHttp $request */
        $request = $args->getSubject()->Request();
        $request->setPost($this->getAttributeFieldName(), null);
    }

    /**
     * Template method that needs to return an explanatory text for why the activity was not performed yet.
     *
     * @param int $orderId
     * @return string
     */
    abstract protected function getOrderHasNoDateMessage($orderId);

    public function install($attributeLabelName, $helpText = '')
    {
        // create the attribute column
        $sqlHelper = new SQLHelper($this->get('db'));
        $sqlHelper->addColumnIfNotExists('s_order_attributes', $this->getColumnName(), 'datetime');

        $this->get('models')->generateAttributeModels([
            's_order_attributes'
        ]);

        // show the attribute in the backend for Shopware versions new enough to support this (i.e. >= 5.2)
        $attributeConfigurationInstallationHelper = new AttributeConfigurationInstallationHelper($this->get('models'));
        $attributeConfigurationInstallationHelper->createAttributeConfigurationUnlessExists(
            's_order_attributes',
            $this->getColumnName(),
            'string',
            $attributeLabelName,
            $helpText
        );
    }

    public function uninstall()
    {
        // remove the attribute configuration
        $attributeConfigurationUninstallationHelper = new AttributeConfigurationUninstallationHelper($this->get('models'));
        $attributeConfigurationUninstallationHelper->removeAttributeConfigurationsIfExist(
            ['s_order_attributes' => [$this->getColumnName()]]
        );

        // remove the attribute column
        $sqlHelper = new SQLHelper($this->get('db'));
        $sqlHelper->dropColumnIfExists('s_order_attributes', $this->getColumnName());

        $this->get('models')->generateAttributeModels([
            's_order_attributes'
        ]);
    }

    private function getAttributeFieldName()
    {
        return '__attribute_' . $this->getColumnName();
    }

    private function getColumnName()
    {
        return 'viison_' . $this->attributeName . '_order_activity_performed_date';
    }

    /**
     * Fix a date string as returned by DBAL so it's either null or a DateTime after 1900-01-01.
     *
     * @param string|\DateTime $stringDateValue a date string or DateTime. Usually this is a nonsensical value like '0000-00-00 00:00:00'
     *  which is returned by DBAL when the database column has the value null, or a date in this century.
     * @return \DateTime|null
     */
    public static function getValidDate($stringDateValue)
    {
        $dateTime = $stringDateValue instanceof \DateTime ? $stringDateValue : new \DateTime($stringDateValue);
        if (!$stringDateValue || $dateTime < new \DateTime('1900-01-01 00:00:00')) {
            return null;
        } else {
            return $dateTime;
        }
    }
}
