<?php
/**
 * Class PnpSubmenu
 *
 * @package DhlPolska\Admin\Menu
 */

namespace DhlPolska\Admin\Menu;

use DhlPolska\Admin\DhlPolskaMenu;
use DhlPolska\Download\BinaryDownloadResponse;
use DhlPolska\Exception\ExceptionMessageResolver;
use DhlPolska\Exception\PnpDownloadFailedException;
use DhlPolskaVendor\Octolize\DhlPolska\API\WebAPI;
use DhlPolskaVendor\WPDesk\PluginBuilder\Plugin\Hookable;

/**
 * Register DHL Polska PNP submenu.
 */
class PnpSubmenu implements Hookable {
	public const PAGE_SLUG = 'dhl-polska-pnp';
	public const ACTION    = 'dhl_polska_generate_pnp';

	private const QUERY_NONCE_ACTION = 'dhl_polska_pnp_query';
	private const QUERY_NONCE_NAME   = 'dhl_polska_pnp_nonce';

	private WebAPI $webapi;

	private BinaryDownloadResponse $binary_download_response;

	public function __construct(
		WebAPI $webapi,
		?BinaryDownloadResponse $binary_download_response = null
	) {
		$this->webapi                   = $webapi;
		$this->binary_download_response = $binary_download_response ?? new BinaryDownloadResponse();
	}

	/**
	 * Hooks.
	 */
	public function hooks() {
		add_action( 'admin_menu', [ $this, 'register_submenu' ] );
		add_action( 'admin_post_' . self::ACTION, [ $this, 'handle_generate_pnp' ] );
	}

	/**
	 * @return void
	 */
	public function register_submenu() {
		add_submenu_page(
			DhlPolskaMenu::MENU_SLUG,
			__( 'Potwierdzenie nadania', 'dhl-polska' ),
			__( 'Potwierdzenie nadania', 'dhl-polska' ),
			'manage_woocommerce',
			self::PAGE_SLUG,
			[ $this, 'render_page' ],
			2
		);
	}

	/**
	 * @return void
	 */
	public function render_page() {
		$menu_slug     = DhlPolskaMenu::MENU_SLUG;
		$action        = self::ACTION;
		$pnp_types     = $this->get_pnp_types();
		$selected_date = $this->get_pnp_date_from_request();
		$selected_type = $this->get_pnp_type_from_request( $pnp_types );
		$notice        = $this->get_pnp_notice();

		include __DIR__ . '/../views/html-pnp-page.php';
	}

	/**
	 * @return void
	 */
	public function handle_generate_pnp() {
		if ( ! current_user_can( 'manage_woocommerce' ) ) {
			wp_die( esc_html__( 'Brak uprawnień.', 'dhl-polska' ) );
		}

		check_admin_referer( self::ACTION );

		$date  = $this->get_post_value( 'shipment_date' );
		$type  = strtoupper( $this->get_post_value( 'shipment_type' ) );
		$types = $this->get_pnp_types();

		if ( '' === $date || ! $this->is_valid_date( $date ) ) {
			$this->redirect_with_notice( 'error', __( 'Podaj poprawną datę nadania.', 'dhl-polska' ), $date, $type );
		}

		if ( '' === $type || ! isset( $types[ $type ] ) ) {
			$this->redirect_with_notice( 'error', __( 'Wybierz poprawny typ przesyłki.', 'dhl-polska' ), $date, $type );
		}

		try {
			$pnp_data = $this->webapi->get_pnp( $date, $type );
			$this->download_pnp_file( $pnp_data, $date, $type );
		} catch ( \Exception $exception ) {
			$this->redirect_with_notice( 'error', ExceptionMessageResolver::resolve_throwable( $exception, PnpDownloadFailedException::class ), $date, $type );
		}
	}

	/**
	 * @return array<string, string>
	 */
	private function get_pnp_types(): array {
		return [
			'ALL'                 => __( 'ALL - wszystkie rodzaje przesyłek', 'dhl-polska' ),
			'EX'                  => __( 'EX - krajowe ekspresowe do 31,5kg', 'dhl-polska' ),
			'DR'                  => __( 'DR - krajowe drobnicowe', 'dhl-polska' ),
			'2EUROPE'             => __( '2EUROPE - przesyłki eksportowe', 'dhl-polska' ),
			'PR'                  => __( 'PR - przesyłki Premium', 'dhl-polska' ),
		];
	}

	/**
	 * @param string $date .
	 *
	 * @return bool
	 */
	private function is_valid_date( string $date ): bool {
		$date_object = \DateTime::createFromFormat( 'Y-m-d', $date );
		if ( ! $date_object ) {
			return false;
		}

		return $date_object->format( 'Y-m-d' ) === $date;
	}

	/**
	 * @return string
	 */
	private function get_pnp_date_from_request(): string {
		$date = sanitize_text_field( $this->get_signed_query_value( 'shipment_date' ) );
		if ( '' !== $date && $this->is_valid_date( $date ) ) {
			return $date;
		}

		return (string) current_time( 'Y-m-d' );
	}

	/**
	 * @param array<string, string> $types .
	 *
	 * @return string
	 */
	private function get_pnp_type_from_request( array $types ): string {
		$type = strtoupper( sanitize_text_field( $this->get_signed_query_value( 'shipment_type' ) ) );
		if ( '' !== $type && isset( $types[ $type ] ) ) {
			return $type;
		}

		return (string) array_key_first( $types );
	}

	/**
	 * @return array{type: string, message: string}|null
	 */
	private function get_pnp_notice(): ?array {
		$notice_type = $this->get_valid_notice_type( $this->get_signed_query_value( 'pnp_notice' ) );
		$message     = sanitize_text_field( $this->get_signed_query_value( 'pnp_message' ) );

		if ( '' === $notice_type || '' === $message ) {
			return null;
		}

		return [
			'type'    => $notice_type,
			'message' => $message,
		];
	}

	/**
	 * @param string $type .
	 * @param string $message .
	 *
	 * @return void
	 */
	private function redirect_with_notice( string $type, string $message, string $date = '', string $shipment_type = '' ): void {
		$notice_type = $this->get_valid_notice_type( $type );
		if ( '' === $notice_type ) {
			$notice_type = 'error';
		}

		$url_args = [
			'page'                 => self::PAGE_SLUG,
			'pnp_notice'           => $notice_type,
			'pnp_message'          => sanitize_text_field( $message ),
			self::QUERY_NONCE_NAME => wp_create_nonce( self::QUERY_NONCE_ACTION ),
		];

		if ( '' !== $date && $this->is_valid_date( $date ) ) {
			$url_args['shipment_date'] = $date;
		}

		$types = $this->get_pnp_types();
		if ( '' !== $shipment_type && isset( $types[ $shipment_type ] ) ) {
			$url_args['shipment_type'] = $shipment_type;
		}

		$url = add_query_arg( $url_args, admin_url( 'admin.php' ) );

		wp_safe_redirect( $url );
		exit;
	}

	private function get_signed_query_value( string $key ): string {
		if ( ! $this->has_valid_query_nonce() ) {
			return '';
		}

		return $this->get_query_value( $key );
	}

	private function has_valid_query_nonce(): bool {
		$nonce = sanitize_text_field( $this->get_query_value( self::QUERY_NONCE_NAME ) );
		if ( '' === $nonce ) {
			return false;
		}

		return false !== wp_verify_nonce( $nonce, self::QUERY_NONCE_ACTION );
	}

	private function get_query_value( string $key ): string {
		$value = filter_input( INPUT_GET, $key, FILTER_UNSAFE_RAW );
		if ( ! is_string( $value ) ) {
			return '';
		}

		return sanitize_text_field( $value );
	}

	private function get_post_value( string $key ): string {
		$value = filter_input( INPUT_POST, $key, FILTER_UNSAFE_RAW );
		if ( ! is_string( $value ) ) {
			return '';
		}

		return sanitize_text_field( $value );
	}

	private function get_valid_notice_type( string $notice_type ): string {
		$notice_type          = sanitize_key( $notice_type );
		$allowed_notice_types = [ 'error', 'success', 'warning', 'info' ];

		if ( in_array( $notice_type, $allowed_notice_types, true ) ) {
			return $notice_type;
		}

		return '';
	}

	/**
	 * @param array{file_name: string, file_data: string, mime_type: string} $pnp_data .
	 * @param string $date .
	 * @param string $type .
	 *
	 * @return array{name: string, mime_type: string, content: string}
	 */
	private function prepare_pnp_download( array $pnp_data, string $date, string $type ): array {
		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
		$content = base64_decode( $pnp_data['file_data'], true );
		if ( false === $content || '' === $content ) {
			throw new PnpDownloadFailedException();
		}

		$file_name = $this->binary_download_response->sanitize_file_name( $pnp_data['file_name'], sprintf( 'pnp-%s-%s.pdf', $date, strtolower( $type ) ) );

		if ( ! preg_match( '/\\.pdf$/i', $file_name ) ) {
			$file_name .= '.pdf';
		}

		$mime_type = $this->binary_download_response->sanitize_mime_type( $pnp_data['mime_type'], 'application/pdf' );

		return [
			'name'      => $file_name,
			'mime_type' => $mime_type,
			'content'   => $content,
		];
	}

	/**
	 * @param array{file_name: string, file_data: string, mime_type: string} $pnp_data .
	 * @param string $date .
	 * @param string $type .
	 *
	 * @return void
	 */
	private function download_pnp_file( array $pnp_data, string $date, string $type ): void {
		$payload = $this->prepare_pnp_download( $pnp_data, $date, $type );

		$this->binary_download_response->send( $payload['name'], $payload['mime_type'], $payload['content'] );
	}
}
