<?php
/**
 * Handles statuses in admin.
 *
 * @package flo-sync
 */

namespace NMFS\Sync;

use Exception;
use NMFS\Sync\Items\Articles\Article_Sync_Meta_Helpers;
use WC_Product;
use WP_Post;

defined( 'ABSPATH' ) || exit;

/**
 * Class Admin_Status
 */
class Admin_Status {

	/**
	 * Setup hooks.
	 */
	public static function setup(): void {
		add_action( 'admin_head', array( __CLASS__, 'admin_styles' ) );
		add_action( 'admin_notices', array( __CLASS__, 'admin_notices_product' ) );
		add_action( 'woocommerce_variation_options', array( __CLASS__, 'admin_notice_edit_variation' ), 10, 3 );
		add_filter( 'manage_product_posts_columns', array( __CLASS__, 'manage_product_posts_columns' ), 20 );
		add_filter( 'manage_product_posts_custom_column', array( __CLASS__, 'manage_product_posts_custom_column' ), 10, 2 );
	}

	/**
	 * Add extra admin styles.
	 */
	public static function admin_styles(): void {
		echo '<style>
			.fixed .column-apps_product_sync_status {
				text-align: center;
				width: 4%;
			}
			.mf-sync-ok {
				color: #7ad03a;
			}
			.mf-sync-error {
				color: #a44;
			}
		</style>';
	}

	/**
	 * Sets sync status of product using Apps response
	 *
	 * @param int    $product_id Post id.
	 * @param string $response   Json response from Apps as a string.
	 */
	public static function set_sync_status_from_response( int $product_id, string $response ): void {
		$error_message = null;

		try {
			$response_json = json_decode( $response );
			$success       = $response_json->list[0]->Success ? 'OK' : 'ERROR';
			if ( 'ERROR' === $success ) {
				$error_message = $response_json->list[0]->ExtraInformation[0];
			}
		} catch ( Exception ) {
			$success       = 'ERROR';
			$error_message = __( 'Unknown error', 'flo-sync' );
		}

		Article_Sync_Meta_Helpers::update_sync_status_private( $product_id, $success );
		Article_Sync_Meta_Helpers::update_sync_error( $product_id, $error_message ?? '' );
		Article_Sync_Meta_Helpers::update_sync_last( $product_id, date_i18n( 'Y-m-d H:i:s' ) );
	}

	/**
	 * Sets sync status of product to ok.
	 *
	 * @param int $product_id Post id.
	 */
	public static function set_sync_status_ok( int $product_id ): void {
		Article_Sync_Meta_Helpers::update_sync_status_private( $product_id, 'OK' );
		Article_Sync_Meta_Helpers::update_sync_error( $product_id, '' );
		Article_Sync_Meta_Helpers::update_sync_last( $product_id, date_i18n( 'Y-m-d H:i:s' ) );
	}

	/**
	 * Show errors on product edit page.
	 */
	public static function admin_notices_product(): void {
		if ( ! function_exists( 'get_current_screen' ) ) {
			return;
		}

		if ( 'product' !== get_current_screen()->id ) {
			return;
		}

		global $post;
		if ( ! $post->ID ) {
			return;
		}

		$sync_status = self::get_product_sync_statuses( $post->ID, $statuses );

		if ( 'ERROR' === $sync_status ) {
			self::print_admin_notice_errors( $statuses );
		}
	}

	/**
	 * Show admin notice on variable product edit page.
	 *
	 * @param int     $loop           Number in loop.
	 * @param array   $variation_data Array of variation data.
	 * @param WP_Post $variation      Variation post.
	 *
	 * @return void
	 * @noinspection PhpUnusedParameterInspection WP action/filter function.
	 */
	public static function admin_notice_edit_variation( int $loop, array $variation_data, WP_Post $variation ): void {
		$sync_status = self::get_product_sync_statuses( $variation->ID, $statuses );

		if ( 'ERROR' === $sync_status ) {
			self::print_admin_notice_errors( $statuses );
		}
	}

	/**
	 * Filters columns in the product list.
	 *
	 * @param array $columns Current columns.
	 *
	 * @return array
	 */
	public static function manage_product_posts_columns( array $columns ): array {
		return array_merge( $columns, array( 'apps_product_sync_status' => 'Apps' ) );
	}

	/**
	 * Filters column content in product list.
	 *
	 * @param string $column_key Key of current column.
	 * @param int    $post_id    ID of current post.
	 *
	 * @return void
	 */
	public static function manage_product_posts_custom_column( string $column_key, int $post_id ): void {
		if ( 'apps_product_sync_status' === $column_key ) {
			$sync_status = self::get_product_sync_statuses( $post_id, $statuses );

			$title = array();
			if ( 'OK' === $sync_status ) {
				foreach ( $statuses as $s ) {
					// translators: %1$s: Datetime of last sync, %2$s: ID in Hub.
					$title[] = sprintf( __( '[%1$s] OK (id %2$d)', 'flo-sync' ), $s['last'], $s['id'] );
					if ( count( $title ) > 2 ) {
						$title[] = '..';
						break;
					}
				}

				printf(
					'<span title="%s" class="mf-sync-ok"><span class="dashicons dashicons-yes-alt"></span></span>',
					esc_attr( implode( ', ', $title ) )
				);
			} elseif ( 'ERROR' === $sync_status ) {
				foreach ( $statuses as $i => $s ) {
					if ( 'ERROR' === $s['status'] ) {
						$title[ $i ] = sprintf( '[%s] %s', $s['last'], $s['error_message'] );
						if ( count( $title ) > 3 ) {
							$title[ $i ] .= '..';
							break;
						}
					}
				}

				$message = implode( ', ', $title );
				if ( count( $title ) > 1 ) {
					$message = __( 'Multiple errors. Edit product to view them', 'flo-sync' );
				}

				printf(
					'<span title="%s" class="mf-sync-error"><span class="dashicons dashicons-warning"></span></span>',
					esc_attr( $message )
				);
			} else {
				printf( '<span>&nbsp;</span>' );
			}
		}
	}

	/**
	 * Prints admin notice with errors.
	 *
	 * @param array $statuses Array of statuses.
	 *
	 * @return void
	 */
	private static function print_admin_notice_errors( array $statuses ): void {
		$title = array();
		foreach ( $statuses as $i => $s ) {
			if ( 'ERROR' === $s['status'] ) {
				$title[ $i ] = sprintf( '[%s] %s (id %d)', $s['last'], $s['error_message'], $s['id'] );
				if ( count( $title ) > 3 ) {
					$title[ $i ] .= '..';
					break;
				}
			}
		}

		printf(
			'<div class="%1$s"><p><strong>%2$s</strong></p><p>%3$s</p></div>',
			'notice notice-error',
			esc_html( _n( 'Northmill Hub sync error', 'Northmill Hub sync errors', count( $title ), 'flo-sync' ) ),
			nl2br( esc_html( implode( "\n", $title ) ) )
		);
	}

	/**
	 * Get sync status of product.
	 *
	 * @param int        $post_id  Post id.
	 * @param null|array $statuses Filled with all the statuses. Passed by reference.
	 *
	 * @return string Status summary: 'OK', 'ERROR' or empty
	 */
	private static function get_product_sync_statuses( int $post_id, ?array &$statuses = array() ): string {
		if ( is_null( $statuses ) ) {
			$statuses = array();
		}

		$sync_status = '';

		try {
			$product = wc_get_product( $post_id );
			if ( ! is_a( $product, 'WC_Product' ) ) {
				return '';
			}

			// Get all statuses.
			if ( in_array( $product->get_type(), array( 'simple', 'variation' ), true ) ) {
				$statuses[] = self::get_product_sync_status( $post_id );
			} elseif ( 'variable' === $product->get_type() ) {
				$variations       = self::get_product_variations( $product );
				$variations_count = count( $variations );
				for ( $i = 0; $i < $variations_count; $i++ ) {
					$statuses[ $i ] = self::get_product_sync_status( $variations[ $i ]['variation_id'] );
				}
			}

			if ( ! empty( $statuses ) ) {
				$ok_statuses = array_filter(
					$statuses,
					function ( $s ) {
						return 'OK' === $s['status'];
					}
				);

				$error_statuses = array_filter(
					$statuses,
					function ( $s ) {
						return 'ERROR' === $s['status'];
					}
				);

				if ( count( $ok_statuses ) === count( $statuses ) ) {
					$sync_status = 'OK';
				} elseif ( count( $error_statuses ) > 0 ) {
					$sync_status = 'ERROR';
				}
			}
		} catch ( Exception ) {
			$statuses = array();
		}

		return $sync_status;
	}

	/**
	 * Get sync status for a product.
	 *
	 * @param int $post_id WC Product ID.
	 *
	 * @return array
	 */
	private static function get_product_sync_status( int $post_id ): array {
		return array(
			'id'            => $post_id,
			'status'        => Article_Sync_Meta_Helpers::get_sync_status_private( $post_id ),
			'error_message' => Article_Sync_Meta_Helpers::get_sync_error( $post_id ),
			'last'          => Article_Sync_Meta_Helpers::get_sync_last( $post_id ),
		);
	}

	/**
	 * Get product variations.
	 *
	 * @param WC_Product $product Variable product to get variations from.
	 *
	 * @return array
	 */
	private static function get_product_variations( WC_Product $product ): array {
		if ( ! is_a( $product, 'WC_Product_Variable' ) ) {
			return array();
		}

		try {
			return $product->get_available_variations();
		} catch ( Exception ) {
			return array();
		}
	}
}
