<?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\Document\RenderedDocument;

use Shopware\Components\Model\ModelManager;
use Shopware\Models\Attribute\Document as DocumentAttribute;
use Shopware\Models\Order\Document\Document as OrderDocument;
use Shopware\Models\Document\Document as DocumentType;
use Shopware\Models\Order\Order;
use Shopware\Plugins\ViisonCommon\Classes\Util\Document as DocumentUtil;
use Shopware\Plugins\ViisonCommon\Classes\Exceptions\DocumentException;
use Shopware\Plugins\ViisonCommon\Components\FileStorage\FileStorage;

/**
 * Immutable class comprising the HTML and PDF rendering results of a DocumentRenderingContext.
 */
class RenderedDocumentWithDocumentType extends RenderedDocument
{
    /**
     * @var ModelManager
     */
    protected $modelManager;

    /**
     * @var DocumentType
     */
    protected $documentType;

    /**
     * @var FileStorage
     */
    private $documentFileStorage;

    /**
     * @internal Is only called by DocumentRenderingContext
     *
     * @param string $html
     * @param string $pdf
     * @param DocumentType $documentType
     * @param ModelManager $modelManager
     * @param FileStorage $documentFileStorage
     */
    public function __construct($html, $pdf, DocumentType $documentType, ModelManager $modelManager, FileStorage $documentFileStorage)
    {
        parent::__construct($html, $pdf);
        $this->modelManager = $modelManager;
        $this->documentType = $documentType;
        $this->documentFileStorage = $documentFileStorage;
    }

    /**
     * Saves the PDF to disk and assigns it as a new order document to a Shopware $order. If no $documentNumber is given
     * the next document number in the document type's number sequence will be used.
     *
     * @param Order $order The order to assign the document to
     * @param float $documentAmount The amount to store for the document (e.g. invoice amount or voucher value)
     * @param string|null $documentNumber null = use next document number in sequence
     * @param \DateTime|null $documentCreationDate If not given, the current date is used
     * @return OrderDocument
     */
    public function savePdfAsOrderDocument(
        Order $order,
        $documentAmount,
        $documentNumber = null,
        \DateTime $documentCreationDate = null
    ) {
        if ($documentNumber === null) {
            $documentNumber = DocumentUtil::getNextDocumentNumber($this->documentType);
        }

        $orderDocument = new OrderDocument();
        $orderDocument->setCustomerId($order->getCustomer()->getId());
        $orderDocument->setOrder($order);
        $orderDocument->setType(DocumentUtil::getDocumentTypeForOrderDocumentModel($this->documentType));
        $orderDocument->setDocumentId($documentNumber);
        $orderDocument->setHash(DocumentUtil::generateDocumentHash());

        // Ensure that attributes are existing for the newly created document
        $attributes = new DocumentAttribute();
        $orderDocument->setAttribute($attributes);

        return $this->updateAndSaveOrderDocument(
            $orderDocument,
            $documentAmount,
            $documentCreationDate
        );
    }

    /**
     * Saves the PDF to disk and assigns it as order document to a Shopware $order. If a document with the same document
     * type exists for this order, the document (and file) will be updated (overwritten). A new document is created
     * otherwise.
     *
     * @param Order $order
     * @param float $documentAmount
     * @param string|null $documentNumber
     * @param \DateTime|null $documentCreationDate
     * @return OrderDocument
     * @throws DocumentException
     */
    public function savePdfAsOrderDocumentWithOverwrite(
        Order $order,
        $documentAmount,
        $documentNumber = null,
        \DateTime $documentCreationDate = null
    ) {
        $existingDocumentsOfSameType = $this->getExistingDocumentsOfSameType($order);

        // Create one new document if no document of this type exists
        if (count($existingDocumentsOfSameType) === 0) {
            return $this->savePdfAsOrderDocument(
                $order,
                $documentAmount,
                $documentNumber,
                $documentCreationDate
            );
        }

        // Overwrite the one existing document with this type
        if (count($existingDocumentsOfSameType) === 1) {
            return $this->updateAndSaveOrderDocument(
                $existingDocumentsOfSameType[0],
                $documentAmount,
                $documentCreationDate
            );
        }

        // Throw exception becasue we don't want to handle multiple overwritings
        throw DocumentException::multipleDocumentsExistForOverwriting(
            $this->documentType,
            $order
        );
    }

    /**
     * Updates some values of the OrderDocument with given values and saves the document as PDF. Only certain values
     * are updated as Shopware does it in its Document component. We need this private function to update (overwrite)
     * existing documents. (See Shopware_Components_Document::saveDocument())
     *
     * @param OrderDocument $orderDocument
     * @param float $documentAmount
     * @param \DateTime|null $documentCreationDate
     * @return OrderDocument
     */
    private function updateAndSaveOrderDocument(
        OrderDocument $orderDocument,
        $documentAmount,
        \DateTime $documentCreationDate = null
    ) {
        // Only update amount and date
        $orderDocument->setAmount($documentAmount);
        $orderDocument->setDate(($documentCreationDate) ?: new \DateTime());

        $fileName = DocumentUtil::getDocumentFileName($orderDocument);
        $this->savePdfToFileStorage($this->documentFileStorage, $fileName);

        $this->modelManager->persist($orderDocument);
        $this->modelManager->flush($orderDocument);

        return $orderDocument;
    }

    /**
     * Returns an array of all documents of the given order that have the same type as $this->documentType.
     *
     * @param Order $order
     * @return OrderDocument[]
     */
    private function getExistingDocumentsOfSameType(Order $order)
    {
        $existingDocumentsOfSameType = $this->modelManager
            ->getRepository('Shopware\\Models\\Order\\Document\\Document')
            ->findBy(
                [
                    'order' => $order,
                    'type' => $this->documentType,
                ]
            );

        return $existingDocumentsOfSameType;
    }
}
