<?php
/**
 * Class TemplatesRepository
 *
 * @package DhlPolska\WooCommerceSettings\Templates
 */

namespace DhlPolska\WooCommerceSettings\Templates;

use DhlPolska\SanitizeTrait;
use DhlPolska\Shipment\ShipmentTypes;
use DhlPolska\ShipmentTemplates\ShipmentTemplatePostType;
use DhlPolska\WooCommerceSettings\WooCommerceDHLPolskaSettings;

/**
 * Shipment templates storage.
 */
class TemplatesRepository {
	use SanitizeTrait;

	private const DEFAULT_DIMENSION_CM = 10;
	private const DEFAULT_TYPE         = ShipmentTypes::TYPE_PACKAGE;
	private const MINIMUM_WEIGHT       = 0.01;
	private const MINIMUM_DIMENSION_CM = 1;
	private const META_WEIGHT          = '_dhl_polska_template_weight';
	private const META_LENGTH          = '_dhl_polska_template_length';
	private const META_WIDTH           = '_dhl_polska_template_width';
	private const META_HEIGHT          = '_dhl_polska_template_height';
	private const META_COD             = '_dhl_polska_template_cod';
	private const META_INSURANCE       = '_dhl_polska_template_insurance';
	private const META_TYPE            = '_dhl_polska_template_type';

	private WooCommerceDHLPolskaSettings $settings;

	private ShipmentTypes $shipment_types;

	public function __construct( WooCommerceDHLPolskaSettings $settings, ?ShipmentTypes $shipment_types = null ) {
		$this->settings       = $settings;
		$this->shipment_types = $shipment_types ?? new ShipmentTypes();
	}

	/**
	 * @return array<string, array<string, mixed>>
	 */
	public function get_templates(): array {
		$posts = get_posts(
			[
				'post_type'      => ShipmentTemplatePostType::POST_TYPE,
				'post_status'    => [ 'publish', 'draft', 'pending', 'private' ],
				'posts_per_page' => -1,
				'orderby'        => 'title',
				'order'          => 'ASC',
			]
		);

		$templates = [];

		foreach ( $posts as $post ) {
			/** @phpstan-ignore-next-line */
			if ( ! is_object( $post ) || ! isset( $post->ID ) ) {
				continue;
			}

			$post_id = (int) $post->ID;

			$templates[ (string) $post_id ] = $this->build_template_data(
				$post_id,
				(string) ( $post->post_title ?? '' ) /** @phpstan-ignore-line */
			);
		}

		return $templates;
	}

	/**
	 * @param string $template_id .
	 *
	 * @return array<string, mixed>|null
	 */
	public function get_template( string $template_id ): ?array {
		$post = get_post( (int) $template_id );

		if ( ! $post || ShipmentTemplatePostType::POST_TYPE !== $post->post_type ) {
			return null;
		}

		return $this->build_template_data(
			(int) $post->ID,
			(string) $post->post_title
		);
	}

	/**
	 * @param int $post_id .
	 *
	 * @return array<string, mixed>
	 */
	public function get_template_meta( int $post_id ): array {
		$template = [
			'weight'     => get_post_meta( $post_id, self::META_WEIGHT, true ),
			'type'       => get_post_meta( $post_id, self::META_TYPE, true ),
			'dimensions' => [
				'length' => get_post_meta( $post_id, self::META_LENGTH, true ),
				'width'  => get_post_meta( $post_id, self::META_WIDTH, true ),
				'height' => get_post_meta( $post_id, self::META_HEIGHT, true ),
			],
			'services'   => [
				'cod'       => get_post_meta( $post_id, self::META_COD, true ),
				'insurance' => get_post_meta( $post_id, self::META_INSURANCE, true ),
			],
		];

		$changed = false;

		return $this->normalize_template_meta( $template, $changed );
	}

	/**
	 * @param int   $post_id .
	 * @param array $data    .
	 *
	 * @return void
	 */
	public function save_template_meta( int $post_id, array $data ): void {
		$changed         = false;
		$template        = $this->normalize_template_meta( $data, $changed );
		$cod_value       = $template['services']['cod'] ? '1' : '0';
		$insurance_value = $template['services']['insurance'] ? '1' : '0';

		update_post_meta( $post_id, self::META_WEIGHT, $template['weight'] );
		update_post_meta( $post_id, self::META_TYPE, $template['type'] );
		update_post_meta( $post_id, self::META_LENGTH, $template['dimensions']['length'] );
		update_post_meta( $post_id, self::META_WIDTH, $template['dimensions']['width'] );
		update_post_meta( $post_id, self::META_HEIGHT, $template['dimensions']['height'] );
		update_post_meta( $post_id, self::META_COD, $cod_value );
		update_post_meta( $post_id, self::META_INSURANCE, $insurance_value );
	}

	/**
	 * @param int    $post_id .
	 * @param string $name    .
	 *
	 * @return array<string, mixed>
	 */
	private function build_template_data( int $post_id, string $name ): array {
		$template = $this->get_template_meta( $post_id );

		return [
			'id'         => (string) $post_id,
			'name'       => $name,
			'weight'     => $template['weight'],
			'type'       => $template['type'],
			'dimensions' => $template['dimensions'],
			'services'   => $template['services'],
		];
	}

	/**
	 * @param array $template .
	 * @param bool  $changed  .
	 *
	 * @return array<string, mixed>
	 */
	private function normalize_template_meta( array $template, bool &$changed ): array {
		$weight = $template['weight'] ?? '';
		if ( '' === $weight ) {
			$weight  = $this->settings->get_default_weight();
			$changed = true;
		}
		$weight     = $this->sanitize_weight( $weight, $changed );
		$type       = (string) ( $template['type'] ?? '' );
		$type       = $this->sanitize_type( $type, $changed );
		$dimensions = is_array( $template['dimensions'] ?? null ) ? $template['dimensions'] : [];
		$dimensions = $this->normalize_dimensions( $dimensions, $changed );
		$services   = is_array( $template['services'] ?? null ) ? $template['services'] : [];
		$services   = $this->normalize_services( $services );

		return [
			'weight'     => $weight,
			'type'       => $type,
			'dimensions' => $dimensions,
			'services'   => $services,
		];
	}

	/**
	 * @param array $dimensions .
	 * @param bool  $changed .
	 *
	 * @return array{length: int, width: int, height: int}
	 */
	private function normalize_dimensions( array $dimensions, bool &$changed ): array {
		$length = $dimensions['length'] ?? '';
		$width  = $dimensions['width'] ?? '';
		$height = $dimensions['height'] ?? '';

		if ( '' === $length ) {
			$length  = self::DEFAULT_DIMENSION_CM;
			$changed = true;
		}

		if ( '' === $width ) {
			$width   = self::DEFAULT_DIMENSION_CM;
			$changed = true;
		}

		if ( '' === $height ) {
			$height  = self::DEFAULT_DIMENSION_CM;
			$changed = true;
		}

		return [
			'length' => $this->sanitize_dimension( $length, $changed ),
			'width'  => $this->sanitize_dimension( $width, $changed ),
			'height' => $this->sanitize_dimension( $height, $changed ),
		];
	}

	/**
	 * @param array $services .
	 *
	 * @return array{cod: bool, insurance: bool}
	 */
	private function normalize_services( array $services ): array {
		return [
			'cod'       => $this->get_bool_value( $services['cod'] ?? false ),
			'insurance' => $this->get_bool_value( $services['insurance'] ?? false ),
		];
	}

	/**
	 * @param mixed $weight  .
	 * @param bool  $changed .
	 *
	 * @return float
	 */
	private function sanitize_weight( $weight, bool &$changed ): float {
		$formatted = $this->get_float_value( (string) $weight );

		if ( $formatted < self::MINIMUM_WEIGHT ) {
			$formatted = self::MINIMUM_WEIGHT;
			$changed   = true;
		}

		return $formatted;
	}

	/**
	 * @param mixed $dimension .
	 * @param bool  $changed   .
	 *
	 * @return int
	 */
	private function sanitize_dimension( $dimension, bool &$changed ): int {
		$dimension = absint( $dimension );

		if ( $dimension < self::MINIMUM_DIMENSION_CM ) {
			$dimension = self::MINIMUM_DIMENSION_CM;
			$changed   = true;
		}

		return $dimension;
	}

	/**
	 * @param string $type .
	 * @param bool   $changed .
	 *
	 * @return string
	 */
	private function sanitize_type( string $type, bool &$changed ): string {
		if ( '' === $type ) {
			$type    = self::DEFAULT_TYPE;
			$changed = true;
		}

		$legacy_map = [
			'koperta' => ShipmentTypes::TYPE_ENVELOPE,
			'paczka'  => ShipmentTypes::TYPE_PACKAGE,
			'paleta'  => ShipmentTypes::TYPE_PALLET,
		];

		if ( isset( $legacy_map[ $type ] ) ) {
			$type    = $legacy_map[ $type ];
			$changed = true;
		}

		if ( ! in_array( $type, $this->get_allowed_types(), true ) ) {
			$type    = self::DEFAULT_TYPE;
			$changed = true;
		}

		return $type;
	}

	/**
	 * @return string[]
	 */
	private function get_allowed_types(): array {
		return array_keys( $this->shipment_types->get_type_labels() );
	}
}
