<?php
/**
 * Class Repository.
 *
 * @package Packetery
 */

declare(strict_types=1);

namespace Packetery\Module\CustomsDeclaration;

use Packetery\Core\CoreHelper;
use Packetery\Core\Entity\CustomsDeclaration;
use Packetery\Core\Entity\CustomsDeclarationItem;
use Packetery\Module\EntityFactory;
use Packetery\Module\Exception\DeleteErrorException;
use Packetery\Module\WpdbAdapter;

/**
 * Class Repository.
 */
class Repository {

	/**
	 * Wpdb adapter.
	 *
	 * @var WpdbAdapter
	 */
	private $wpdbAdapter;

	/**
	 * Entity factory.
	 *
	 * @var EntityFactory\CustomsDeclaration
	 */
	private $entityFactory;

	/**
	 * Constructor.
	 *
	 * @param WpdbAdapter                      $wpdbAdapter   Wpdb adapter.
	 * @param EntityFactory\CustomsDeclaration $entityFactory Entity factory.
	 */
	public function __construct( WpdbAdapter $wpdbAdapter, EntityFactory\CustomsDeclaration $entityFactory ) {
		$this->wpdbAdapter   = $wpdbAdapter;
		$this->entityFactory = $entityFactory;
	}

	/**
	 * Gets customs declaration ID by order ID.
	 *
	 * @param string $orderNumber Order ID.
	 * @return string|null
	 */
	private function getIdByOrderNumber( string $orderNumber ): ?string {
		return $this->wpdbAdapter->get_var(
			$this->wpdbAdapter->prepare(
				'SELECT `id` FROM `' . $this->wpdbAdapter->packeteryCustomsDeclaration . '` WHERE `order_id` = %d',
				$orderNumber
			)
		);
	}

	/**
	 * Gets customs declaration by order.
	 *
	 * @param string $orderNumber Order.
	 *
	 * @return CustomsDeclaration|null
	 */
	public function getByOrderNumber( string $orderNumber ): ?CustomsDeclaration {
		$customsDeclarationRow = $this->wpdbAdapter->get_row(
			sprintf(
				'SELECT 
					`id`,
					`order_id`,
					`ead`,
					`delivery_cost`,
					`invoice_number`,
					`invoice_issue_date`,
					`mrn`,
					`invoice_file_id`,
					`ead_file_id`,
					`invoice_file` IS NOT NULL AS `has_invoice_file_content`,
					`ead_file` IS NOT NULL AS `has_ead_file_content`
				FROM `%s`
				WHERE `order_id` = %d',
				$this->wpdbAdapter->packeteryCustomsDeclaration,
				$orderNumber
			),
			ARRAY_A
		);

		if ( $customsDeclarationRow === null ) {
			return null;
		}

		$customsDeclaration = $this->entityFactory->fromStandardizedStructure( $customsDeclarationRow, $orderNumber );

		$customsDeclaration->setInvoiceFile(
			function () use ( $orderNumber ): ?string {
				return $this->wpdbAdapter->get_var(
					$this->wpdbAdapter->prepare(
						'SELECT `invoice_file` FROM `' . $this->wpdbAdapter->packeteryCustomsDeclaration . '` WHERE `order_id` = %d',
						$orderNumber
					)
				);
			},
			(bool) $customsDeclarationRow['has_invoice_file_content']
		);

		$customsDeclaration->setEadFile(
			function () use ( $orderNumber ): ?string {
				return $this->wpdbAdapter->get_var(
					$this->wpdbAdapter->prepare(
						'SELECT `ead_file` FROM `' . $this->wpdbAdapter->packeteryCustomsDeclaration . '` WHERE `order_id` = %d',
						$orderNumber
					)
				);
			},
			(bool) $customsDeclarationRow['has_ead_file_content']
		);

		$customsDeclaration->setItems( $this->getItemsByCustomsDeclarationId( $customsDeclaration->getId() ) );

		return $customsDeclaration;
	}

	/**
	 * Gets customs declaration items by order.
	 *
	 * @param string|null $customsDeclarationId Customs declaration ID.
	 * @return CustomsDeclarationItem[]
	 */
	public function getItemsByCustomsDeclarationId( ?string $customsDeclarationId ): array {
		if ( $customsDeclarationId === null ) {
			return [];
		}

		$customsDeclarationItemRows = $this->getCustomsDeclarationItemRows( $customsDeclarationId );

		if ( $customsDeclarationItemRows === null ) {
			return [];
		}

		$customsDeclarationItems = [];
		foreach ( $customsDeclarationItemRows as $row ) {
			$customsDeclarationItems[] = $this->entityFactory->createItemFromStandardizedStructure( $row );
		}

		return $customsDeclarationItems;
	}

	public function getCustomsDeclarationItemRows( ?string $customsDeclarationId ): ?array {
		return $this->wpdbAdapter->get_results(
			sprintf(
				'SELECT
					`id`,
					`customs_declaration_id`,
					`customs_code`,
					`value`,
					`product_name_en`,
					`product_name`,
					`units_count`,
					`country_of_origin`,
					`weight`,
					`is_food_or_book`,
					`is_voc`
				FROM `%s` WHERE `customs_declaration_id` = %d',
				$this->wpdbAdapter->packeteryCustomsDeclarationItem,
				$customsDeclarationId
			),
			ARRAY_A
		);
	}

	/**
	 * Saves customs declaration.
	 *
	 * @param CustomsDeclaration $customsDeclaration Customs declaration.
	 * @param array              $fieldsToOmit Fields to omit.
	 * @return int|false The number of rows updated, or false on error.
	 */
	public function save( CustomsDeclaration $customsDeclaration, array $fieldsToOmit = [ 'invoice_file', 'ead_file' ] ) {
		if ( $customsDeclaration->getId() === null ) {
			$updatedRowCount = $this->wpdbAdapter->insertReplaceHelper(
				$this->wpdbAdapter->packeteryCustomsDeclaration,
				$this->declarationToDbArray( $customsDeclaration, $fieldsToOmit )
			);
			if ( $updatedRowCount !== false ) {
				$customsDeclaration->setId( $this->wpdbAdapter->getLastInsertId() );
			}
		} else {
			$updatedRowCount = $this->wpdbAdapter->update(
				$this->wpdbAdapter->packeteryCustomsDeclaration,
				$this->declarationToDbArray( $customsDeclaration, $fieldsToOmit ),
				[ 'id' => (int) $customsDeclaration->getId() ]
			);
		}

		if ( $customsDeclaration->getId() !== null ) {
			$omitInvoiceFile = in_array( 'invoice_file', $fieldsToOmit, true );
			if ( $omitInvoiceFile === false && $customsDeclaration->hasInvoiceFileContent() ) {
				$fileQueryResult = $this->wpdbAdapter->update(
					$this->wpdbAdapter->packeteryCustomsDeclaration,
					[ 'invoice_file' => $customsDeclaration->getInvoiceFile() ],
					[ 'id' => (int) $customsDeclaration->getId() ]
				);
				if ( $fileQueryResult === false ) {
					$updatedRowCount = false;
				}
			}

			if ( $omitInvoiceFile === false && $customsDeclaration->hasInvoiceFileContent() === false ) {
				$fileQueryResult = $this->wpdbAdapter->update(
					$this->wpdbAdapter->packeteryCustomsDeclaration,
					[ 'invoice_file' => null ],
					[ 'id' => (int) $customsDeclaration->getId() ]
				);
				if ( $fileQueryResult === false ) {
					$updatedRowCount = false;
				}
			}

			$omitEadFile = in_array( 'ead_file', $fieldsToOmit, true );
			if ( $omitEadFile === false && $customsDeclaration->hasEadFileContent() ) {
				$fileQueryResult = $this->wpdbAdapter->update(
					$this->wpdbAdapter->packeteryCustomsDeclaration,
					[ 'ead_file' => $customsDeclaration->getEadFile() ],
					[ 'id' => (int) $customsDeclaration->getId() ]
				);
				if ( $fileQueryResult === false ) {
					$updatedRowCount = false;
				}
			}

			if ( $omitEadFile === false && $customsDeclaration->hasEadFileContent() === false ) {
				$fileQueryResult = $this->wpdbAdapter->update(
					$this->wpdbAdapter->packeteryCustomsDeclaration,
					[ 'ead_file' => null ],
					[ 'id' => (int) $customsDeclaration->getId() ]
				);
				if ( $fileQueryResult === false ) {
					$updatedRowCount = false;
				}
			}
		}

		return $updatedRowCount;
	}

	/**
	 * Saves customs declaration item.
	 *
	 * @param CustomsDeclarationItem $customsDeclarationItem Customs declaration item.
	 * @return int|false The number of rows updated, or false on error.
	 */
	public function saveItem( CustomsDeclarationItem $customsDeclarationItem ) {
		if ( $customsDeclarationItem->getId() === null ) {
			$updatedRowCount = $this->wpdbAdapter->insert(
				$this->wpdbAdapter->packeteryCustomsDeclarationItem,
				$this->declarationItemToDbArray( $customsDeclarationItem )
			);
			$customsDeclarationItem->setId( $this->wpdbAdapter->getLastInsertId() );
		} else {
			$updatedRowCount = $this->wpdbAdapter->update(
				$this->wpdbAdapter->packeteryCustomsDeclarationItem,
				$this->declarationItemToDbArray( $customsDeclarationItem ),
				[ 'id' => (int) $customsDeclarationItem->getId() ]
			);
		}

		return $updatedRowCount;
	}

	/**
	 * @throws DeleteErrorException
	 */
	public function deleteItem( int $itemId ): void {
		$this->wpdbAdapter->delete( $this->wpdbAdapter->packeteryCustomsDeclarationItem, [ 'id' => $itemId ], '%d' );
	}

	/**
	 * @throws DeleteErrorException
	 */
	private function deleteItems( string $customsDeclarationId ): void {
		$items = $this->getItemsByCustomsDeclarationId( $customsDeclarationId );

		if ( count( $items ) === 0 ) {
			return;
		}

		foreach ( $items as $item ) {
			$this->deleteItem( (int) $item->getId() );
		}
	}

	/**
	 * Completely deletes Customs Declaration with all its items.
	 *
	 * @throws DeleteErrorException
	 */
	public function delete( string $orderId ): void {
		$customsDeclarationId = $this->getIdByOrderNumber( $orderId );

		if ( $customsDeclarationId === null ) {
			return;
		}

		$this->deleteItems( $customsDeclarationId );
		$this->wpdbAdapter->delete( $this->wpdbAdapter->packeteryCustomsDeclaration, [ 'id' => $customsDeclarationId ], '%d' );
	}

	/**
	 * Converts customs declaration to DB array.
	 *
	 * @param CustomsDeclaration $customsDeclaration Customs declaration.
	 * @param array              $fieldsToOmit Fields to omit.
	 * @return array<string, string|int|float>
	 */
	public function declarationToDbArray( CustomsDeclaration $customsDeclaration, array $fieldsToOmit = [] ): array {
		$data = [
			'id'                 => $customsDeclaration->getId(),
			'order_id'           => $customsDeclaration->getOrderId(),
			'ead'                => $customsDeclaration->getEad(),
			'delivery_cost'      => $customsDeclaration->getDeliveryCost(),
			'invoice_number'     => $customsDeclaration->getInvoiceNumber(),
			'invoice_issue_date' => $customsDeclaration->getInvoiceIssueDate()->format( CoreHelper::MYSQL_DATE_FORMAT ),
			'invoice_file_id'    => $customsDeclaration->getInvoiceFileId(),
			'mrn'                => $customsDeclaration->getMrn(),
			'ead_file_id'        => $customsDeclaration->getEadFileId(),
		];

		foreach ( $fieldsToOmit as $fieldToOmit ) {
			unset( $data[ $fieldToOmit ] );
		}

		return $data;
	}

	/**
	 * Converts customs declaration item to DB array.
	 *
	 * @param CustomsDeclarationItem $customsDeclarationItem Customs declaration item.
	 * @return array<string, string|int|float>
	 */
	public function declarationItemToDbArray( CustomsDeclarationItem $customsDeclarationItem ): array {
		return [
			'id'                     => $customsDeclarationItem->getId(),
			'customs_declaration_id' => $customsDeclarationItem->getCustomsDeclarationId(),
			'customs_code'           => $customsDeclarationItem->getCustomsCode(),
			'value'                  => $customsDeclarationItem->getValue(),
			'product_name_en'        => $customsDeclarationItem->getProductNameEn(),
			'product_name'           => $customsDeclarationItem->getProductName(),
			'units_count'            => $customsDeclarationItem->getUnitsCount(),
			'country_of_origin'      => strtoupper( $customsDeclarationItem->getCountryOfOrigin() ),
			'weight'                 => $customsDeclarationItem->getWeight(),
			'is_food_or_book'        => (int) $customsDeclarationItem->isFoodOrBook(),
			'is_voc'                 => (int) $customsDeclarationItem->isVoc(),
		];
	}

	/**
	 * Creates main table.
	 *
	 * @return bool
	 */
	public function createOrAlterTable(): bool {
		$createTableQuery = sprintf(
			'CREATE TABLE `%s` (
				`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
				`order_id` bigint(20) UNSIGNED NOT NULL,
				`ead` varchar(50) NOT NULL,
				`delivery_cost` decimal(13,2) UNSIGNED NOT NULL,
				`invoice_number` varchar(255) NOT NULL,
				`invoice_issue_date` date NOT NULL,
				`invoice_file` mediumblob NULL DEFAULT NULL,
				`invoice_file_id` varchar(255) NULL DEFAULT NULL,
				`mrn` varchar(32) NULL DEFAULT NULL,
				`ead_file` mediumblob NULL DEFAULT NULL,
				`ead_file_id` varchar(255) NULL DEFAULT NULL,
			PRIMARY KEY (`id`)
		) %s',
			$this->wpdbAdapter->packeteryCustomsDeclaration,
			$this->wpdbAdapter->get_charset_collate()
		);

		return $this->wpdbAdapter->dbDelta( $createTableQuery, $this->wpdbAdapter->packeteryCustomsDeclaration );
	}

	/**
	 * Creates item table.
	 *
	 * @return bool
	 */
	public function createOrAlterItemTable(): bool {
		$createItemTableQuery = sprintf(
			'CREATE TABLE `%s` (
				`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
				`customs_declaration_id` int(11) UNSIGNED NOT NULL,
				`customs_code` varchar(8) NOT NULL,
				`value` decimal(13,2) UNSIGNED NOT NULL,
				`product_name_en` varchar(255) NOT NULL,
				`product_name` varchar(255) NULL DEFAULT NULL,
				`units_count` int(11) UNSIGNED NOT NULL,
				`country_of_origin` char(2) NOT NULL,
				`weight` decimal(10,3) UNSIGNED NOT NULL,
				`is_food_or_book` tinyint(1) NOT NULL,
				`is_voc` tinyint(1) NOT NULL,
			PRIMARY KEY (`id`)
		) %s',
			$this->wpdbAdapter->packeteryCustomsDeclarationItem,
			$this->wpdbAdapter->get_charset_collate()
		);

		return $this->wpdbAdapter->dbDelta( $createItemTableQuery, $this->wpdbAdapter->packeteryCustomsDeclarationItem );
	}
}
