<?php

namespace Wb4Wp\Managers\Save_Site;

use Exception;
use Wb4Wp\Helpers\Object_Helper;
use Wb4Wp\Helpers\Provider_Helper;
use Wb4Wp\Managers\Raygun_Manager;

/**
 * Class Save_Navigation_Manager
 * @package Wb4Wp\Managers\Save_Site
 */
final class Save_Navigation_Manager {

	private static $provider_name;

	private static function get_provider_name() {
		if ( ! isset( self::$provider_name ) ) {
			self::$provider_name = Provider_Helper::get_provider_name();
		}

		return self::$provider_name;
	}

	/**
	 * @param array $navigation_items
	 * @param array $pages
	 * @param array $saved_pages
	 *
	 * @return bool True if successful, False otherwise.
	 */
	public static function save_navigation( $navigation_items, $pages, $saved_pages ) {
		$current_navigation_items_removed = self::remove_current_navigation_items();

		$next_navigation = self::prepare_next_navigation( $navigation_items, $pages, $saved_pages );
		if ( empty( $next_navigation ) ) {
			return true;
		}

		$new_navigation_id = null;
		if ( false === $current_navigation_items_removed ) {
			$new_navigation_id = self::create_new_navigation();
			if ( false === $new_navigation_id ) {
				return false;
			}

			$locations          = get_theme_mod( 'nav_menu_locations', array() );
			$locations['wb4wp'] = $new_navigation_id;
			$theme_mod_updated  = set_theme_mod( 'nav_menu_locations', $locations );
			if ( false === $theme_mod_updated ) {
				return false;
			}
		}

		return self::update_navigation( $new_navigation_id, $next_navigation );
	}

	/**
	 * Removes the navigation items from the current navigation, identified by the provider name.
	 *
	 * @return bool True if the navigation exists and its items were removed, False otherwise.
	 */
	private static function remove_current_navigation_items() {
		global $wpdb;

		$provider_name = self::get_provider_name();

		$number_of_rows_deleted = $wpdb->query(
			$wpdb->prepare(
				"
					DELETE     tr, p, pm
					FROM       $wpdb->terms AS t
					INNER JOIN $wpdb->term_taxonomy AS tt
					ON         t.term_id = tt.term_id
					INNER JOIN $wpdb->term_relationships AS tr
					ON         tt.term_taxonomy_id = tr.term_taxonomy_id
					INNER JOIN $wpdb->posts AS p
					ON         tr.object_id = p.ID
					INNER JOIN $wpdb->postmeta as pm
					ON         p.ID = pm.post_id
					WHERE      t.name = %s;
				",
				$provider_name
			)
		);

		return $number_of_rows_deleted > 0;
	}

	/**
	 * @param array $navigation_items
	 * @param array $pages
	 * @param array $saved_pages
	 *
	 * @return array
	 */
	private static function prepare_next_navigation( $navigation_items, $pages, $saved_pages ) {
		$next_navigation = array();

		foreach ( $navigation_items as $navigation_item ) {
			$next_navigation_item = self::prepare_next_navigation_item( $navigation_item, $pages, $saved_pages );
			if ( empty( $next_navigation_item ) ) {
				continue;
			}
			$next_navigation[ $navigation_item['id'] ] = $next_navigation_item;
		}

		return $next_navigation;
	}

	/**
	 * @param array $navigation_item
	 * @param array $site_description_pages
	 * @param array $published_pages_pages
	 *
	 * @return array|false The navigation item or False.
	 */
	private static function prepare_next_navigation_item( $navigation_item, $site_description_pages, $published_pages_pages ) {
		$id                 = Object_Helper::get_property( $navigation_item['id'] );
		$page_id            = Object_Helper::get_property( $navigation_item['pageId'], '' );
		$parent_id          = Object_Helper::get_property( $navigation_item['parentId'], 0 );
		$show_in_navigation = Object_Helper::get_property( $navigation_item['menuItem']['showInNavigation'], true );

		if ( 'hidden' === $parent_id || false === $show_in_navigation ) {
			return false;
		}

		if ( empty( $page_id ) ) {
			$title = $navigation_item['menuItem']['title'];
			$wp_id = Object_Helper::get_property( $navigation_item['wpID'] );

			if ( empty( $wp_id ) ) {
				$next_navigation_item = array(
					'menu-item-title' => $title,
					'menu-item-url'   => $navigation_item['menuItem']['uriPath'],
					'menu-item-type'  => 'custom',
				);
			} else {
				$next_navigation_item = array(
					'menu-item-title'     => $title,
					'menu-item-object-id' => $wp_id,
					'menu-item-object'    => 'page',
					'menu-item-type'      => 'post_type',
				);
			}
		} else {
			$site_description_page_object = current(
				array_filter(
					$site_description_pages,
					function ( $page ) use ( $page_id ) {
						// Keep double equal, sometimes id is string or interger.
						return Object_Helper::get_property( $page['id'] ) == $page_id;
					}
				)
			);

			if ( empty( $site_description_page_object ) ) {
				self::log_to_raygun( "Couldn't create navigation item with ID '{$id}': no site description entry found." );
			}

			$show_in_navigation = Object_Helper::get_property( $site_description_page_object['showInNavigation'], true );
			if ( false === $show_in_navigation ) {
				return false;
			}

			$published_pages_page_object = current(
				array_filter(
					$published_pages_pages,
					function ( $saved_page ) use ( $page_id ) {
						// Keep double equal, sometimes id is string or interger.
						return Object_Helper::get_property( $saved_page['ee_id'] ) == $page_id;
					}
				)
			);

			if ( empty( $published_pages_page_object ) ) {
				self::log_to_raygun( "Couldn't create navigation item with ID '{$id}': no published pages entry found." );
			}

			$next_navigation_item = array(
				'menu-item-title'     => $site_description_page_object['title'],
				'menu-item-object-id' => $published_pages_page_object['wp_id'],
				'menu-item-object'    => 'page',
				'menu-item-type'      => 'post_type',
			);
		}

		if ( isset( $parent_id ) ) {
			$next_navigation_item['menu-item-parent-id'] = (int) $parent_id;
		}

		$next_navigation_item['menu-item-position'] = $navigation_item['order'];

		return $next_navigation_item;
	}

	/**
	 * @return int|false The new navigation's ID if successful, False otherwise.
	 */
	private static function create_new_navigation() {
		global $wpdb;

		$provider_name      = Provider_Helper::get_provider_name();
		$provider_name_slug = sanitize_title( $provider_name );

		$queries = array(
			'BEGIN',
			$wpdb->prepare(
				"
					INSERT INTO $wpdb->terms ( `name`, `slug`, `term_group` )
					VALUES      ( %s, %s, 0 )
				",
				$provider_name,
				$provider_name_slug
			),
			'SET @term_id = LAST_INSERT_ID()',
			"
				INSERT INTO $wpdb->term_taxonomy ( `term_id`, `taxonomy`, `description`, `parent`, `count` )
				VALUES      ( @term_id, 'nav_menu', '', 0, 0 );
			",
		);

		foreach ( $queries as $query ) {
			if ( false === $wpdb->query( $query ) ) { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
				$wpdb->query( 'ROLLBACK' );

				return false;
			}
		}

		$term_id_results = $wpdb->get_results( 'SELECT @term_id', ARRAY_N );
		$wpdb->query( 'COMMIT' );

		return (int) current( current( $term_id_results ) );
	}

	/**
	 * @param int|null $navigation_id
	 * @param array $next_navigation
	 *
	 * @return bool True if successful, False otherwise.
	 */
	private static function update_navigation( $navigation_id, $next_navigation ) {
		global $wpdb;

		$queries = array(
			'BEGIN',
			self::prepare_set_term_taxonomy_id_statement( $navigation_id ),
			'SET @post_0_id = 0',
		);

		$term_relationship_entries = array();
		$postmeta_entries          = array();
		foreach ( $next_navigation as $id => $next_navigation_item ) {
			self::prepare_navigation_item_insert_queries(
				$id,
				$next_navigation_item,
				$queries,
				$term_relationship_entries,
				$postmeta_entries
			);
		}

		$queries[] = "INSERT INTO $wpdb->term_relationships ( `object_id`, `term_taxonomy_id`, `term_order` ) VALUES " . implode( ",\n", $term_relationship_entries );

		$queries[] = "INSERT INTO $wpdb->postmeta ( post_id, meta_key, meta_value ) VALUES " . implode( ",\n", $postmeta_entries );

		$success = true;
		foreach ( $queries as $query ) {
			if ( false === $wpdb->query( $query ) ) { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
				$success = false;
			}
		}

		if ( $success ) {
			$wpdb->query( 'COMMIT' );
		} else {
			$wpdb->query( 'ROLLBACK' );
		}

		return $success;
	}

	/**
	 * @param int|null $navigation_id
	 *
	 * @return string|void
	 */
	private static function prepare_set_term_taxonomy_id_statement( $navigation_id ) {
		global $wpdb;

		if ( isset( $navigation_id ) ) {
			return $wpdb->prepare(
				"
					SET @term_taxonomy_id = (
						SELECT     tt.term_taxonomy_id
						FROM       $wpdb->term_taxonomy AS tt
						WHERE      tt.term_id = %d
					)
				",
				$navigation_id
			);
		} else {
			return $wpdb->prepare(
				"
					SET @term_taxonomy_id = (
						SELECT     tt.term_taxonomy_id
						FROM       $wpdb->term_taxonomy AS tt
						INNER JOIN $wpdb->terms AS t
						ON         tt.term_id = t.term_id
						WHERE      t.name = %s
					)
				",
				self::get_provider_name()
			);
		}
	}

	/**
	 * Prepares navigation item insert queries. Only to be used within the context of this class, as it implicitly
	 * depends on other SQL statements.
	 *
	 * @param int $id
	 * @param array $navigation_item_data
	 * @param array $queries
	 * @param array $term_relationship_entries
	 * @param array $postmeta_entries
	 */
	private static function prepare_navigation_item_insert_queries( $id, $navigation_item_data, &$queries, &$term_relationship_entries, &$postmeta_entries ) {
		global $wpdb;
		$current_time = current_time( 'mysql' );
		$post_parent  = (int) Object_Helper::get_property( $navigation_item_data['menu-item-parent-id'], 0 );

		$post_title = '';
		$post_name  = '';
		if ( 'custom' === $navigation_item_data['menu-item-type'] ) {
			$post_title = $navigation_item_data['menu-item-title'];
			$post_name  = sanitize_title( $post_title );
		}

		$queries[] = $wpdb->prepare(
			"
				INSERT INTO $wpdb->posts (
				                      `post_author`,
				                      `post_date`,
				                      `post_date_gmt`,
				                      `post_content`,
				                      `post_title`,
				                      `post_excerpt`,
				                      `post_status`,
				                      `comment_status`,
				                      `ping_status`,
				                      `post_name`,
				                      `to_ping`,
				                      `pinged`,
				                      `post_modified`,
				                      `post_modified_gmt`,
				                      `post_content_filtered`,
				                      `post_parent`,
				                      `menu_order`,
				                      `post_type`,
				                      `comment_count`
				) VALUES (
				          0,
				          %s,
				          %s,
				          '',
				          %s,
				          '',
				          'publish',
				          'closed',
				          'closed',
				          %s,
				          '',
				          '',
				          %s,
				          %s,
				          '',
				          @post_%d_id,
				          %d,
				          'nav_menu_item',
				          0
				)
			",
			$current_time,
			$current_time,
			$post_title,
			$post_name,
			$current_time,
			$current_time,
			$post_parent,
			$navigation_item_data['menu-item-position']
		);
		$queries[] = "SET @post_{$id}_id = LAST_INSERT_ID()";

		$term_relationship_entries[] = "( @post_{$id}_id, @term_taxonomy_id, 0 )";

		$navigation_item_post_meta_data = self::map_navigation_item_data_to_post_meta_data( $navigation_item_data );
		foreach ( $navigation_item_post_meta_data as $post_meta_key => $post_meta_value ) {
			$postmeta_entries[] = self::prepare_navigation_item_post_meta_insert_query( $id, $post_meta_key, $post_meta_value );
		}
	}

	private static function prepare_navigation_item_post_meta_insert_query( $navigation_item_id, $post_meta_key, $post_meta_value ) {
		switch ( $post_meta_key ) {
			case '_menu_item_menu_item_parent':
				return sprintf(
					"( @post_%d_id, '%s', @post_%d_id )",
					$navigation_item_id,
					$post_meta_key,
					(int) $post_meta_value
				);
			case '_menu_item_object_id':
				return sprintf(
					"( @post_%d_id, '%s', %d )",
					$navigation_item_id,
					$post_meta_key,
					(int) $post_meta_value
				);
			default:
				return sprintf(
					"( @post_%d_id, '%s', '%s' )",
					$navigation_item_id,
					$post_meta_key,
					$post_meta_value
				);
		}
	}

	private static function log_to_raygun( $message ) {
		Raygun_Manager::get_instance()->exception_handler( new Exception( $message ) );
	}

	/**
	 * @param array $navigation_item_data
	 *
	 * @return array
	 */
	private static function map_navigation_item_data_to_post_meta_data( $navigation_item_data ) {
		$post_date = current_time( 'mysql', 1 );
		$defaults  = array(
			'menu-item-object-id'     => 0,
			'menu-item-object'        => '',
			'menu-item-parent-id'     => 0,
			'menu-item-position'      => 0,
			'menu-item-type'          => 'custom',
			'menu-item-title'         => '',
			'menu-item-url'           => '',
			'menu-item-description'   => '',
			'menu-item-attr-title'    => '',
			'menu-item-target'        => '',
			'menu-item-classes'       => '',
			'menu-item-xfn'           => '',
			'menu-item-status'        => '',
			'menu-item-post-date'     => $post_date,
			'menu-item-post-date-gmt' => $post_date,
		);

		$navigation_item_data = array_merge( $defaults, $navigation_item_data );

		if ( 'custom' === $navigation_item_data['menu-item-type'] ) {
			$navigation_item_data['menu-item-url'] = trim( $navigation_item_data['menu-item-url'] );
		} else {
			$navigation_item_data['menu-item-url'] = '';
		}

		return array(
			'_menu_item_type'             => sanitize_key( $navigation_item_data['menu-item-type'] ),
			'_menu_item_menu_item_parent' => $navigation_item_data['menu-item-parent-id'],
			'_menu_item_object_id'        => $navigation_item_data['menu-item-object-id'],
			'_menu_item_object'           => sanitize_key( $navigation_item_data['menu-item-object'] ),
			'_menu_item_target'           => sanitize_key( $navigation_item_data['menu-item-target'] ),
			'_menu_item_classes'          => serialize( array_map( 'sanitize_html_class', explode( ' ', $navigation_item_data['menu-item-classes'] ) ) ),
			// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
			'_menu_item_xfn'              => implode( ' ', array_map( 'sanitize_html_class', explode( ' ', $navigation_item_data['menu-item-xfn'] ) ) ),
			'_menu_item_url'              => esc_url_raw( $navigation_item_data['menu-item-url'] ),
		);
	}

}
