<?php
namespace AIOSEO\Plugin\Pro\Migration;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

// phpcs:disable WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound

use AIOSEO\Plugin\Common\Migration as CommonMigration;
use AIOSEO\Plugin\Pro\Models;
use AIOSEO\Plugin\Addon\VideoSitemap;

/**
 * Migrates the term meta from V3.
 *
 * @since 4.0.0
 */
class Meta extends CommonMigration\Meta {
	/**
	 * Instance of the Video class of the Video Sitemap.
	 *
	 * @since 4.0.2
	 *
	 * @var VideoSitemap\VideoSitemap\Video
	 */
	private $videoSitemap = null;

	/**
	 * Instantiates the Video Sitemap video class if the addon is installed and active.
	 *
	 * @since 4.0.2
	 *
	 * @return void
	 */
	public function instantiateVideoSitemap() {
		if ( null !== $this->videoSitemap ) {
			return;
		}

		$videoSitemap = aioseo()->addons->getAddon( 'aioseo-video-sitemap' );
		if (
			! empty( $videoSitemap ) &&
			is_plugin_active( $videoSitemap->basename ) &&
			aioseo()->license->isAddonAllowed( 'aioseo-video-sitemap' ) &&
			class_exists( 'AIOSEO\\Plugin\\Addon\\VideoSitemap\\VideoSitemap' ) &&
			function_exists( 'aioseoVideoSitemap' )
		) {
			// The video sitemap has already been instantiated, let's grab it.
			$this->videoSitemap = property_exists( aioseoVideoSitemap(), 'video' ) ? aioseoVideoSitemap()->video : new VideoSitemap\VideoSitemap\Video();
		}
	}

	/**
	 * Migrates additional post meta data.
	 *
	 * @since 4.0.2
	 *
	 * @param  int  $postId The post ID.
	 * @return void
	 */
	public function migrateAdditionalPostMeta( $postId ) {
		parent::migrateAdditionalPostMeta( $postId );

		$this->instantiateVideoSitemap();
		if ( $this->videoSitemap ) {
			$post = get_post( $postId );
			$this->videoSitemap->scanPost( $post );
		}
	}

	/**
	 * Migrates the plugin meta data.
	 *
	 * @since 4.0.0
	 *
	 * @return void
	 */
	public function migrateMeta() {
		parent::migrateMeta();

		try {
			if ( as_next_scheduled_action( 'aioseo_migrate_term_meta' ) ) {
				return;
			}

			as_schedule_single_action( time() + 30, 'aioseo_migrate_term_meta', [], 'aioseo' );
		} catch ( \Exception $e ) {
			// Do nothing.
		}
	}

	/**
	 * Migrates the term meta data from V3.
	 *
	 * @since 4.0.0
	 *
	 * @return void
	 */
	public function migrateTermMeta() {
		if ( aioseo()->core->cache->get( 'v3_migration_in_progress_settings' ) ) {
			aioseo()->actionScheduler->scheduleSingle( 'aioseo_migrate_term_meta', 30, [], true );

			return;
		}

		$termsPerAction   = 50;
		$publicTaxonomies = implode( "', '", aioseo()->helpers->getPublicTaxonomies( true ) );
		$timeStarted      = gmdate( 'Y-m-d H:i:s', aioseo()->core->cache->get( 'v3_migration_in_progress_terms' ) );

		$termsToMigrate = aioseo()->core->db
			->start( 'terms' . ' as t' )
			->select( 't.term_id' )
			->leftJoin( 'aioseo_terms as at', '`t`.`term_id` = `at`.`term_id`' )
			->leftJoin( 'term_taxonomy as tt', '`t`.`term_id` = `tt`.`term_id`' )
			->whereRaw( "( at.term_id IS NULL OR at.updated < '$timeStarted' )" )
			->whereRaw( "( tt.taxonomy IN ( '$publicTaxonomies' ) )" )
			->orderBy( 't.term_id DESC' )
			->limit( $termsPerAction )
			->run()
			->result();

		if ( ! $termsToMigrate || ! count( $termsToMigrate ) ) {
			aioseo()->core->cache->delete( 'v3_migration_in_progress_terms' );

			return;
		}

		foreach ( $termsToMigrate as $term ) {
			$newTermMeta = $this->getMigratedTermMeta( $term->term_id );

			$term = Models\Term::getTerm( $term->term_id );
			$term->set( $newTermMeta );
			$term->save();

			$this->updateLocalizedTermMeta( $term->term_id, $newTermMeta );

			if ( $this->videoSitemap ) {
				$term = get_term( $term->term_id );
				$this->videoSitemap->scanTerm( $term );
			}
		}

		if ( count( $termsToMigrate ) === $termsPerAction ) {
			try {
				as_schedule_single_action( time() + 30, 'aioseo_migrate_term_meta', [], 'aioseo' );
			} catch ( \Exception $e ) {
				// Do nothing.
			}
		} else {
			aioseo()->core->cache->delete( 'v3_migration_in_progress_terms' );
		}
	}

	/**
	 * Returns the migrated term meta for a given term.
	 *
	 * @since 4.0.3
	 *
	 * @param  int   $termId The term ID.
	 * @return array $meta   The term meta.
	 */
	public function getMigratedTermMeta( $termId ) {
		if ( ! is_numeric( $termId ) ) {
			return [];
		}

		if ( null === self::$oldOptions ) {
			self::$oldOptions = get_option( 'aioseop_options' );
		}

		if ( empty( self::$oldOptions ) ) {
			return [];
		}

		$termMeta = aioseo()->core->db
			->start( 'termmeta' . ' as tm' )
			->select( '`tm`.`meta_key`, `tm`.`meta_value`' )
			->where( 'tm.term_id', $termId )
			->whereLike( 'tm.meta_key', '_aioseop_%', true )
			->run()
			->result();

		$mappedMeta = [
			'_aioseop_title'              => 'title',
			'_aioseop_description'        => 'description',
			'_aioseop_custom_link'        => 'canonical_url',
			'_aioseop_sitemap_exclude'    => '',
			'_aioseop_disable'            => '',
			'_aioseop_noindex'            => 'robots_noindex',
			'_aioseop_nofollow'           => 'robots_nofollow',
			'_aioseop_sitemap_priority'   => 'priority',
			'_aioseop_sitemap_frequency'  => 'frequency',
			'_aioseop_keywords'           => 'keywords',
			'_aioseop_opengraph_settings' => ''
		];

		$meta = [
			'term_id' => $termId,
		];

		if ( ! $termMeta || ! count( $termMeta ) ) {
			return $meta;
		}

		foreach ( $termMeta as $record ) {
			$name  = $record->meta_key;
			$value = $record->meta_value;

			if ( ! in_array( $name, array_keys( $mappedMeta ), true ) ) {
				continue;
			}

			switch ( $name ) {
				case '_aioseop_description':
					$meta[ $mappedMeta[ $name ] ] = aioseo()->helpers->sanitizeOption( aioseo()->migration->helpers->macrosToSmartTags( $value ) );
					break;
				case '_aioseop_title':
					if ( ! empty( $value ) ) {
						$meta[ $mappedMeta[ $name ] ] = $this->getTermTitle( $termId, $value );
					}
					break;
				case '_aioseop_sitemap_exclude':
					if ( empty( $value ) ) {
						break;
					}
					$this->migrateSitemapExcludedTerm( $termId );
					break;
				case '_aioseop_disable':
					if ( empty( $value ) ) {
						break;
					}
					$this->migrateExcludedTerm( $termId );
					break;
				case '_aioseop_noindex':
				case '_aioseop_nofollow':
					if ( 'on' === (string) $value ) {
						$meta['robots_default']       = false;
						$meta[ $mappedMeta[ $name ] ] = true;
					} elseif ( 'off' === (string) $value ) {
						$meta['robots_default'] = false;
					}
					break;
				case '_aioseop_keywords':
					$meta[ $mappedMeta[ $name ] ] = aioseo()->migration->helpers->oldKeywordsToNewKeywords( $value );
					break;
				case '_aioseop_opengraph_settings':
					$meta += $this->convertOpenGraphMeta( $value );
					break;
				case '_aioseop_sitemap_priority':
				case '_aioseop_sitemap_frequency':
					if ( empty( $value ) ) {
						$meta[ $mappedMeta[ $name ] ] = 'default';
						break;
					}
					$meta[ $mappedMeta[ $name ] ] = $value;
					break;
				default:
					$meta[ $mappedMeta[ $name ] ] = esc_html( wp_strip_all_tags( strval( $value ) ) );
					break;
			}
		}

		return $meta;
	}

	/**
	 * Migrates a given sitemap excluded term from V3.
	 *
	 * @since 4.0.3
	 *
	 * @param  int  $termId The term ID.
	 * @return void
	 */
	private function migrateSitemapExcludedTerm( $termId ) {
		$term = get_term( $termId );
		if ( ! is_object( $term ) ) {
			return;
		}

		aioseo()->options->sitemap->general->advancedSettings->enable = true;
		$excludedTerms = aioseo()->options->sitemap->general->advancedSettings->excludeTerms;

		foreach ( $excludedTerms as $excludedTerm ) {
			$excludedTerm = json_decode( $excludedTerm );
			if ( $excludedTerm->value === $termId ) {
				return;
			}
		}

		$excludedTerm = [
			'value' => $term->term_id,
			'type'  => $term->taxonomy,
			'label' => $term->name,
			'link'  => get_term_link( $term, $term->taxonomy )
		];

		$excludedTerms[] = wp_json_encode( $excludedTerm );
		aioseo()->options->sitemap->general->advancedSettings->excludeTerms = $excludedTerms;
	}

	/**
	 * Migrates a given disabled term from V3.
	 *
	 * @since 4.0.3
	 *
	 * @param  int  $termId The term ID.
	 * @return void
	 */
	private function migrateExcludedTerm( $termId ) {
		$term = get_term( $termId );
		if ( ! is_object( $term ) ) {
			return;
		}

		$excludedTerms = aioseo()->options->deprecated->searchAppearance->advanced->excludeTerms;
		foreach ( $excludedTerms as $excludedTerm ) {
			$excludedTerm = json_decode( $excludedTerm );
			if ( $excludedTerm->value === $termId ) {
				return;
			}
		}

		$excludedTerm = [
			'value' => $term->term_id,
			'type'  => $term->taxonomy,
			'label' => $term->name,
			'link'  => get_term_link( $term, $term->taxonomy )
		];

		$excludedTerms[] = wp_json_encode( $excludedTerm );
		aioseo()->options->deprecated->searchAppearance->advanced->excludeTerms = $excludedTerms;

		$deprecatedOptions = aioseo()->internalOptions->internal->deprecatedOptions;
		if ( ! in_array( 'excludeTerms', $deprecatedOptions, true ) ) {
			array_push( $deprecatedOptions, 'excludeTerms' );
			aioseo()->internalOptions->internal->deprecatedOptions = $deprecatedOptions;
		}
	}

	/**
	 * Updates the traditional term meta table with the new data.
	 *
	 * @since 4.1.0
	 *
	 * @param  int   $termId  The term ID.
	 * @param  array $newMeta The new meta data.
	 * @return void
	 */
	protected function updateLocalizedTermMeta( $termId, $newMeta ) {
		$localizedFields = [
			'title',
			'description',
			'keywords',
			'og_title',
			'og_description',
			'og_article_section',
			'og_article_tags',
			'twitter_title',
			'twitter_description'
		];

		foreach ( $newMeta as $k => $v ) {
			if ( ! in_array( $k, $localizedFields, true ) ) {
				continue;
			}

			if ( in_array( $k, [ 'keywords', 'og_article_tags' ], true ) ) {
				$v = ! empty( $v ) ? aioseo()->helpers->jsonTagsToCommaSeparatedList( $v ) : '';
			}

			update_term_meta( $termId, "_aioseo_{$k}", $v );
		}
	}

	/**
	 * Returns the title as it was in V3.
	 *
	 * @since 4.0.0
	 *
	 * @param  int    $termId   The term ID.
	 * @param  string $seoTitle The old SEO title.
	 * @return string           The title.
	 */
	protected function getTermTitle( $termId, $seoTitle = '' ) {
		$term = get_term( $termId );
		if ( ! isset( $term->term_id ) ) {
			return '';
		}

		$taxonomy   = 'post_tag' !== $term->taxonomy ? $term->taxonomy : 'tag';
		$oldOptions = get_option( 'aioseo_options_v3' );

		$titleFormat = isset( $oldOptions[ "aiosp_{$taxonomy}_title_format" ] ) ? $oldOptions[ "aiosp_{$taxonomy}_title_format" ] : '';
		if ( ! $titleFormat ) {
			$titleFormat = isset( $oldOptions[ "aiosp_{$taxonomy}_tax_title_format" ] ) ? $oldOptions[ "aiosp_{$taxonomy}_tax_title_format" ] : '';
		}

		$seoTitle = aioseo()->helpers->pregReplace( '/(%category_title%|%tag_title%|%taxonomy_title%|%tag%)/', $seoTitle, $titleFormat );

		return aioseo()->migration->helpers->macrosToSmartTags( $seoTitle );
	}
}