<?php

namespace WPDRMS\ASP\AI\Service;

use Exception;
use RuntimeException;
use WPDRMS\ASP\AI\AIService;
use WPDRMS\ASP\Index\ORM\IndexTable;
use WPDRMS\ASP\Statistics\ORM\Search as StatSearch;
use WPDRMS\PluginCore\Traits\SingletonTrait;

if ( ! defined( 'ABSPATH' ) ) {
	die( '-1' );
}

class AIKeywordSuggestionsService {
	use SingletonTrait;

	private function __construct() {}

	/**
	 * Generates suggestions for one or more instances. Returns per-instance result summaries.
	 *
	 * @param mixed[] $instances  Array of instance objects from wd_asp()->instances->get()
	 * @param int     $max
	 * @return array<int, array<string, mixed>>
	 */
	public function generate( array $instances, int $max ): array {
		$results = array();
		foreach ( $instances as $instance ) {
			$results[] = $this->generateForInstance( $instance, $max );
		}
		return $results;
	}

	/**
	 * Returns all instance suggestion statuses.
	 *
	 * @return array<int, array<string, mixed>>
	 */
	public function getStatus(): array {
		$all_instances = wd_asp()->instances->get();
		$results       = array();

		foreach ( (array) $all_instances as $instance ) {
			$asp_id            = (int) ( $instance['id'] ?? 0 );
			$list              = get_option( "asp_ai_suggestions_{$asp_id}" );
			$generated         = get_option( "asp_ai_suggestions_{$asp_id}_generated_at" );
			$used_global_stats = (bool) get_option( "asp_ai_suggestions_{$asp_id}_used_global_stats", false );

			if ( empty( $list ) || ! is_array( $list ) ) {
				continue;
			}

			$results[] = array(
				'asp_id'            => $asp_id,
				'asp_name'          => (string) ( $instance['name'] ?? '' ),
				'count'             => count( $list ),
				'suggestions'       => array_values( $list ),
				'generated_at'      => $generated ? (string) $generated : null,
				'used_global_stats' => $used_global_stats,
			);
		}

		usort(
			$results,
			static function ( $a, $b ) {
				return (int) $a['asp_id'] - (int) $b['asp_id'];
			}
		);

		return $results;
	}

	/**
	 * Clears stored suggestions for a single instance.
	 *
	 * @param int $asp_id
	 */
	public function clear( int $asp_id ): void {
		delete_option( "asp_ai_suggestions_{$asp_id}" );
		delete_option( "asp_ai_suggestions_{$asp_id}_generated_at" );
		delete_option( "asp_ai_suggestions_{$asp_id}_used_global_stats" );
	}

	/**
	 * Removes a single word from an instance's suggestion list.
	 *
	 * @param int    $asp_id
	 * @param string $word
	 * @return array{count: int}
	 */
	public function removeWord( int $asp_id, string $word ): array {
		$list = get_option( "asp_ai_suggestions_{$asp_id}" );
		if ( ! is_array( $list ) ) {
			$list = array();
		}

		$list = array_values(
			array_filter(
				$list,
				static function ( $s ) use ( $word ) {
					return strcasecmp( (string) $s, $word ) !== 0;
				}
			)
		);

		update_option( "asp_ai_suggestions_{$asp_id}", $list );

		return array( 'count' => count( $list ) );
	}

	/**
	 * @param mixed $instance
	 * @param int   $max
	 * @return array<string, mixed>
	 */
	private function generateForInstance( $instance, int $max ): array {
		global $wpdb;

		$asp_id     = (int) ( $instance['id'] ?? 0 );
		$asp_name   = (string) ( $instance['name'] ?? '' );
		$data       = is_array( $instance['data'] ) ? $instance['data'] : array();
		$engine     = (string) ( $data['search_engine'] ?? 'regular' );
		$post_types = is_array( $data['customtypes'] ?? null ) ? array_filter( $data['customtypes'] ) : array();
		$blog_id    = (int) get_current_blog_id();

		// A) Index terms (index engine only)
		$index_terms = array();
		if ( $engine === 'index' ) {
			$table     = IndexTable::getTableName();
			$pt_clause = '';
			if ( ! empty( $post_types ) ) {
				// post_types are sanitize_key()-ed (alphanumeric + hyphens) — safe for direct quoting.
				$pt_in     = "'" . implode( "','", array_map( 'esc_sql', $post_types ) ) . "'";
				$pt_clause = " AND post_type IN ({$pt_in})"; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			}
			$sql         = $wpdb->prepare(
				"SELECT term, SUM(content + title + comment + tag + link + author + excerpt + customfield) AS occ FROM `{$table}` WHERE blogid = %d AND LENGTH(term) > 2 {$pt_clause} GROUP BY term ORDER BY occ DESC LIMIT 100", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
				$blog_id
			);
			$index_terms = $wpdb->get_results( $sql, ARRAY_A ) ?? array(); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching
		}

		// B) Statistics phrases (filtered by asp_id; fall back to global if sparse)
		$stat_table = StatSearch::getTableName();
		$stat_rows  = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->prepare(
				"SELECT phrase, COUNT(*) AS cnt FROM `{$stat_table}` WHERE asp_id = %d AND phrase != '' AND found_results > 0 GROUP BY phrase ORDER BY cnt DESC LIMIT 50", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
				$asp_id
			),
			ARRAY_A
		) ?? array();

		$used_global_stats = false;
		if ( count( $stat_rows ) < 5 ) {
			// Not enough instance-specific data — use global stats
			$stat_rows         = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching
				"SELECT phrase, COUNT(*) AS cnt FROM `{$stat_table}` WHERE phrase != '' AND found_results > 0 GROUP BY phrase ORDER BY cnt DESC LIMIT 50", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
				ARRAY_A
			) ?? array();
			$used_global_stats = true;
		}
		$stat_phrases = $stat_rows;

		// C) Fallback: post titles + taxonomy terms when index is empty or engine=regular
		$content_samples = array();
		if ( count( $index_terms ) < 20 ) {
			$pt_clause_posts = '';
			if ( ! empty( $post_types ) ) {
				// post_types are sanitize_key()-ed (alphanumeric + hyphens) — safe for direct quoting.
				$pt_in_posts     = "'" . implode( "','", array_map( 'esc_sql', $post_types ) ) . "'";
				$pt_clause_posts = " AND post_type IN ({$pt_in_posts})"; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			}
			$title_rows = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching
				"SELECT DISTINCT post_title FROM `{$wpdb->posts}` WHERE post_status = 'publish' {$pt_clause_posts} LIMIT 100", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
				ARRAY_A
			) ?? array();
			foreach ( $title_rows as $row ) {
				if ( ! empty( $row['post_title'] ) ) {
					$content_samples[] = $row['post_title'];
				}
			}

			$term_rows = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching
				"SELECT DISTINCT t.name FROM `{$wpdb->terms}` t INNER JOIN `{$wpdb->term_taxonomy}` tt ON t.term_id = tt.term_id WHERE tt.taxonomy IN ('category','post_tag') ORDER BY tt.count DESC LIMIT 50",
				ARRAY_A
			) ?? array();
			foreach ( $term_rows as $row ) {
				if ( ! empty( $row['name'] ) ) {
					$content_samples[] = $row['name'];
				}
			}
		}

		$prompt      = $this->buildSuggestionsPrompt( $asp_name, $asp_id, $engine, $post_types, $index_terms, $stat_phrases, $content_samples, $max );
		$data_result = AIService::instance()->generate( 'ai_suggestions', 'ai_suggestions', $prompt, 7 * DAY_IN_SECONDS );

		$raw         = AIService::extractJsonPayload( (string) ( $data_result['result'] ?? '' ) );
		$suggestions = json_decode( $raw, true );

		if ( ! is_array( $suggestions ) ) {
			$suggestions = array();
		}
		$suggestions = array_values( array_filter( array_map( 'sanitize_text_field', $suggestions ) ) );

		$generated_at = current_time( 'mysql' );
		update_option( "asp_ai_suggestions_{$asp_id}", $suggestions );
		update_option( "asp_ai_suggestions_{$asp_id}_generated_at", $generated_at );
		update_option( "asp_ai_suggestions_{$asp_id}_used_global_stats", $used_global_stats );

		return array(
			'asp_id'            => $asp_id,
			'asp_name'          => $asp_name,
			'count'             => count( $suggestions ),
			'generated_at'      => $generated_at,
			'used_global_stats' => $used_global_stats,
		);
	}

	/**
	 * @param string               $asp_name
	 * @param int                  $asp_id
	 * @param string               $engine
	 * @param string[]             $post_types
	 * @param array<string, mixed> $index_terms
	 * @param array<string, mixed> $stat_phrases
	 * @param string[]             $content_samples
	 * @param int                  $max
	 * @return string
	 */
	private function buildSuggestionsPrompt( string $asp_name, int $asp_id, string $engine, array $post_types, array $index_terms, array $stat_phrases, array $content_samples, int $max ): string {
		return (string) wp_json_encode(
			array(
				'instance'        => array(
					'name'       => $asp_name,
					'id'         => $asp_id,
					'engine'     => $engine === 'index' ? 'index table (pre-built)' : 'standard MySQL',
					'post_types' => $post_types,
				),
				'count'           => $max,
				'index_terms'     => array_slice( $index_terms, 0, 100 ),
				'popular_phrases' => $stat_phrases,
				'content_samples' => array_slice( $content_samples, 0, 80 ),
			)
		);
	}
}
