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

namespace DhlPolska\WooCommerceSettings\Templates;

use DhlPolska\SanitizeTrait;
use DhlPolska\Shipment\ShipmentData;
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_SERVICE_1722        = '_dhl_polska_template_service_1722';
	private const META_SERVICE_SOBOTA      = '_dhl_polska_template_service_sobota';
	private const META_SERVICE_NAD_SOBOTA  = '_dhl_polska_template_service_nad_sobota';
	private const META_SERVICE_PDI         = '_dhl_polska_template_service_pdi';
	private const META_SERVICE_ROD         = '_dhl_polska_template_service_rod';
	private const META_SERVICE_POD         = '_dhl_polska_template_service_pod';
	private const META_SERVICE_AGEVER      = '_dhl_polska_template_service_agever';
	private const META_SERVICE_NST         = '_dhl_polska_template_service_nst';
	private const META_SERVICE_EURO_RETURN = '_dhl_polska_template_service_euro_return';
	private const META_TYPE                = '_dhl_polska_template_type';
	private const SERVICE_META_MAP         = [
		ShipmentData::TEMPLATE_SERVICE_1722        => self::META_SERVICE_1722,
		ShipmentData::TEMPLATE_SERVICE_SOBOTA      => self::META_SERVICE_SOBOTA,
		ShipmentData::TEMPLATE_SERVICE_NAD_SOBOTA  => self::META_SERVICE_NAD_SOBOTA,
		ShipmentData::TEMPLATE_SERVICE_INSURANCE   => self::META_INSURANCE,
		ShipmentData::TEMPLATE_SERVICE_COD         => self::META_COD,
		ShipmentData::TEMPLATE_SERVICE_PDI         => self::META_SERVICE_PDI,
		ShipmentData::TEMPLATE_SERVICE_ROD         => self::META_SERVICE_ROD,
		ShipmentData::TEMPLATE_SERVICE_POD         => self::META_SERVICE_POD,
		ShipmentData::TEMPLATE_SERVICE_AGEVER      => self::META_SERVICE_AGEVER,
		ShipmentData::TEMPLATE_SERVICE_NST         => self::META_SERVICE_NST,
		ShipmentData::TEMPLATE_SERVICE_EURO_RETURN => self::META_SERVICE_EURO_RETURN,
	];

	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'   => $this->get_services_meta( $post_id ),
		];

		$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 );

		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'] );

		foreach ( self::SERVICE_META_MAP as $service_key => $meta_key ) {
			$value = ! empty( $template['services'][ $service_key ] ) ? '1' : '0';
			update_post_meta( $post_id, $meta_key, $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<string, bool>
	 */
	private function normalize_services( array $services ): array {
		$normalized_services = [];

		foreach ( array_keys( self::SERVICE_META_MAP ) as $service_key ) {
			$normalized_services[ (string) $service_key ] = $this->get_bool_value( $services[ $service_key ] ?? false );
		}

		return $normalized_services;
	}

	/**
	 * @param int $post_id .
	 *
	 * @return array<string, mixed>
	 */
	private function get_services_meta( int $post_id ): array {
		$services = [];

		foreach ( self::SERVICE_META_MAP as $service_key => $meta_key ) {
			$services[ (string) $service_key ] = get_post_meta( $post_id, $meta_key, true );
		}

		return $services;
	}

	/**
	 * @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() );
	}
}
