<?php

namespace Fotogenic_AI;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Preset storage uses one plugin-owned custom table and bounded route queries.

final class Presets {
	public const SCHEMA_VERSION = 2;
	private const TABLE_NAME = 'fotogenic_ai_presets';
	private const SCHEMA_VERSION_OPTION = 'fotogenic_ai_presets_schema_version';
	private const MAX_PRESETS = 100;
	private const MAX_NAME_LENGTH = 60;
	private static ?Presets $instance = null;
	private bool $db_ready = false;

	private function __construct() {}

	public static function instance(): Presets {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}

		return self::$instance;
	}

	/**
	 * Install and physically verify the current route-indexed schema.
	 */
	public function ensure_schema(): bool {
		if ( $this->db_ready ) {
			return true;
		}
		$stored_version = (int) get_option( self::SCHEMA_VERSION_OPTION, 0 );
		if ( $stored_version > self::SCHEMA_VERSION ) {
			return false;
		}
		if ( self::SCHEMA_VERSION === $stored_version && $this->schema_is_current() ) {
			$this->db_ready = true;
			return true;
		}

		global $wpdb;
		$table   = $this->get_table_name();
		$charset = $wpdb->get_charset_collate();
		$sql     = "CREATE TABLE {$table} (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			preset_uuid char(36) NOT NULL,
			name varchar(120) NOT NULL,
			payload_json longtext NOT NULL,
			format_status varchar(16) NOT NULL DEFAULT 'pending',
			workflow varchar(32) NULL,
			source_type varchar(16) NULL,
			scope varchar(16) NULL,
			created_at datetime NOT NULL,
			updated_at datetime NOT NULL,
			created_by bigint(20) unsigned NULL,
			PRIMARY KEY  (id),
			UNIQUE KEY preset_uuid (preset_uuid),
			KEY route_updated (format_status, workflow, source_type, scope, updated_at),
			KEY updated_at (updated_at)
		) {$charset};";
		require_once ABSPATH . 'wp-admin/includes/upgrade.php';
		dbDelta( $sql );
		if ( ! $this->schema_is_current() ) {
			return false;
		}
		if ( ! update_option( self::SCHEMA_VERSION_OPTION, self::SCHEMA_VERSION, false )
			&& self::SCHEMA_VERSION !== (int) get_option( self::SCHEMA_VERSION_OPTION, 0 ) ) {
			return false;
		}
		$this->db_ready = true;
		return true;
	}

	/** @return array|\WP_Error */
	public function list_universal_presets( string $workflow, string $source, string $scope ) {
		if ( ! $this->ensure_schema() ) {
			return $this->storage_error( 'fotogenic_ai_preset_storage_unavailable', __( 'Preset storage is temporarily unavailable.', 'fotogenic-ai' ) );
		}
		global $wpdb;
		$table = $this->get_table_name();
		$rows  = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT preset_uuid, name, payload_json FROM {$table}
				WHERE format_status = %s AND workflow = %s AND source_type = %s AND scope = %s
				ORDER BY updated_at DESC LIMIT %d",
				'current',
				$workflow,
				$source,
				$scope,
				self::MAX_PRESETS + 1
			),
			ARRAY_A
		);
		if ( ! is_array( $rows ) || $this->database_query_failed() ) {
			return $this->storage_error( 'fotogenic_ai_preset_read_failed', __( 'The saved presets could not be read.', 'fotogenic-ai' ) );
		}
		$truncated = count( $rows ) > self::MAX_PRESETS;
		$presets   = [];
		foreach ( array_slice( $rows, 0, self::MAX_PRESETS ) as $row ) {
			$uuid = sanitize_text_field( (string) ( $row['preset_uuid'] ?? '' ) );
			$name = sanitize_text_field( (string) ( $row['name'] ?? '' ) );
			if ( '' === $uuid || '' === $name ) {
				continue;
			}
			$snapshot = $this->normalize_current_row( (object) $row );
			if ( is_wp_error( $snapshot ) ) {
				return $this->storage_error( 'fotogenic_ai_preset_read_failed', __( 'The saved presets could not be read.', 'fotogenic-ai' ) );
			}
			$presets[] = [
				'uuid'        => $uuid,
				'name'        => $name,
				'description' => $this->preset_description( $snapshot ),
			];
		}

		return [
			'presets'   => $presets,
			'truncated' => $truncated,
		];
	}

	private function preset_description( array $snapshot ): string {
		$recipe  = (array) ( $snapshot['recipe'] ?? [] );
		$count   = is_int( $recipe['count'] ?? null ) ? $recipe['count'] : 0;
		$quality = is_string( $recipe['quality'] ?? null ) ? sanitize_text_field( $recipe['quality'] ) : '';
		if ( $count < 1 || '' === $quality ) {
			return __( 'Saved settings for this workflow.', 'fotogenic-ai' );
		}

		return sprintf(
			/* translators: 1: output count, 2: image quality. */
			_n( '%1$d image · %2$s · saved settings', '%1$d images · %2$s · saved settings', $count, 'fotogenic-ai' ),
			$count,
			$quality
		);
	}

	/** @return array|\WP_Error */
	public function get_universal_preset_data( string $preset_uuid, string $workflow, string $source, string $scope ) {
		$row = $this->get_current_row( $preset_uuid, $workflow, $source, $scope );
		if ( is_wp_error( $row ) ) {
			return $row;
		}
		if ( null === $row ) {
			return new \WP_Error(
				'fotogenic_ai_preset_not_found',
				__( 'The selected preset is no longer available.', 'fotogenic-ai' ),
				[ 'status' => 404 ]
			);
		}
		$normalized = $this->normalize_current_row( $row );
		if ( is_wp_error( $normalized ) || ! $this->route_matches( $normalized, $workflow, $source, $scope ) ) {
			return new \WP_Error(
				'fotogenic_ai_incompatible_preset',
				__( 'This preset uses an incompatible format and was not applied.', 'fotogenic-ai' ),
				[ 'status' => 409 ]
			);
		}

		return $normalized;
	}

	/**
	 * @param mixed $raw_snapshot Raw preset snapshot.
	 * @return array|\WP_Error
	 */
	public function save_universal_preset( string $preset_uuid, string $name, $raw_snapshot, int $user_id ) {
		$normalized = ( new Workflow_Draft_Normalizer() )->normalize_recipe_snapshot( $raw_snapshot );
		if ( is_wp_error( $normalized ) ) {
			return $normalized;
		}
		$preset_uuid = sanitize_text_field( $preset_uuid );
		$name        = trim( sanitize_text_field( $name ) );
		if ( ( '' === $preset_uuid && '' === $name ) || ( '' !== $name && ( strlen( $name ) < 2 || strlen( $name ) > self::MAX_NAME_LENGTH ) ) ) {
			return new \WP_Error(
				'fotogenic_ai_invalid_preset_name',
				__( 'Enter a preset name between 2 and 60 characters.', 'fotogenic-ai' ),
				[ 'status' => 400 ]
			);
		}
		if ( '' !== $preset_uuid ) {
			$existing = $this->get_current_row( $preset_uuid, $normalized['workflow'], $normalized['sourceType'], $normalized['scope'] );
			if ( is_wp_error( $existing ) ) {
				return $existing;
			}
			if ( null === $existing ) {
				return new \WP_Error(
					'fotogenic_ai_preset_not_found',
					__( 'The selected preset is no longer available.', 'fotogenic-ai' ),
					[ 'status' => 404 ]
				);
			}
			if ( '' === $name ) {
				$name = trim( sanitize_text_field( (string) ( $existing->name ?? '' ) ) );
				if ( strlen( $name ) < 2 || strlen( $name ) > self::MAX_NAME_LENGTH ) {
					return $this->storage_error( 'fotogenic_ai_preset_read_failed', __( 'The saved preset could not be read.', 'fotogenic-ai' ) );
				}
			}
		}

		$storage_payload = $normalized;
		if ( [] === $storage_payload['recipe'] ) {
			$storage_payload['recipe'] = new \stdClass();
		}
		$saved = $this->persist( $preset_uuid, $name, $storage_payload, $user_id );
		if ( is_wp_error( $saved ) ) {
			return $saved;
		}
		$proof = $this->get_universal_preset_data(
			(string) $saved['uuid'],
			$normalized['workflow'],
			$normalized['sourceType'],
			$normalized['scope']
		);
		if ( is_wp_error( $proof ) || $normalized !== $proof ) {
			return $this->storage_error( 'fotogenic_ai_preset_save_failed', __( 'The preset could not be saved.', 'fotogenic-ai' ), 500 );
		}

		return [
			'uuid'     => (string) $saved['uuid'],
			'name'     => (string) $saved['name'],
			'snapshot' => $proof,
		];
	}

	/** @return bool|\WP_Error */
	public function delete_universal_preset( string $preset_uuid, string $workflow, string $source, string $scope ) {
		$row = $this->get_current_row( $preset_uuid, $workflow, $source, $scope );
		if ( is_wp_error( $row ) ) {
			return $row;
		}
		if ( null === $row ) {
			return new \WP_Error( 'fotogenic_ai_preset_not_found', __( 'The selected preset is no longer available.', 'fotogenic-ai' ), [ 'status' => 404 ] );
		}

		global $wpdb;
		$table   = $this->get_table_name();
		$deleted = $wpdb->delete(
			$table,
			[
				'preset_uuid'  => sanitize_text_field( $preset_uuid ),
				'format_status'=> 'current',
				'workflow'     => $workflow,
				'source_type'  => $source,
				'scope'        => $scope,
			]
		);
		if ( 1 !== (int) $deleted || $this->database_query_failed() ) {
			return $this->storage_error( 'fotogenic_ai_preset_delete_failed', __( 'The preset could not be removed.', 'fotogenic-ai' ), 500 );
		}

		return null === $this->get_current_row( $preset_uuid, $workflow, $source, $scope );
	}

	/** @return array|\WP_Error */
	private function persist( string $preset_uuid, string $name, array $snapshot, int $user_id ) {
		if ( ! $this->ensure_schema() ) {
			return $this->storage_error( 'fotogenic_ai_preset_storage_unavailable', __( 'Preset storage is temporarily unavailable.', 'fotogenic-ai' ) );
		}
		global $wpdb;
		$table = $this->get_table_name();
		$now   = gmdate( 'Y-m-d H:i:s' );
		$json  = wp_json_encode( $snapshot, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
		if ( ! is_string( $json ) ) {
			return $this->storage_error( 'fotogenic_ai_preset_save_failed', __( 'The preset could not be saved.', 'fotogenic-ai' ), 500 );
		}
		$data = [
			'name'          => $name,
			'payload_json'  => $json,
			'format_status' => 'current',
			'workflow'      => (string) $snapshot['workflow'],
			'source_type'   => (string) $snapshot['sourceType'],
			'scope'         => (string) $snapshot['scope'],
			'updated_at'    => $now,
		];
		if ( '' === $preset_uuid ) {
			$preset_uuid        = wp_generate_uuid4();
			$data['preset_uuid'] = $preset_uuid;
			$data['created_at']  = $now;
			$data['created_by']  = $user_id > 0 ? $user_id : null;
			$written = $wpdb->insert( $table, $data );
		} else {
			$written = $wpdb->update( $table, $data, [ 'preset_uuid' => $preset_uuid ] );
		}
		if ( false === $written || $this->database_query_failed() ) {
			return $this->storage_error( 'fotogenic_ai_preset_save_failed', __( 'The preset could not be saved.', 'fotogenic-ai' ), 500 );
		}

		return [
			'uuid' => $preset_uuid,
			'name' => $name,
		];
	}

	/** @return object|null|\WP_Error */
	private function get_current_row( string $preset_uuid, string $workflow, string $source, string $scope ) {
		if ( ! $this->ensure_schema() ) {
			return $this->storage_error( 'fotogenic_ai_preset_storage_unavailable', __( 'Preset storage is temporarily unavailable.', 'fotogenic-ai' ) );
		}
		global $wpdb;
		$table = $this->get_table_name();
		$row   = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT preset_uuid, name, payload_json FROM {$table}
				WHERE preset_uuid = %s AND format_status = %s AND workflow = %s AND source_type = %s AND scope = %s LIMIT 1",
				sanitize_text_field( $preset_uuid ),
				'current',
				$workflow,
				$source,
				$scope
			)
		);
		if ( $this->database_query_failed() ) {
			return $this->storage_error( 'fotogenic_ai_preset_read_failed', __( 'The saved preset could not be read.', 'fotogenic-ai' ) );
		}

		return is_object( $row ) ? $row : null;
	}

	/** @return array|\WP_Error */
	private function normalize_current_row( object $row ) {
		$decoded = json_decode( (string) ( $row->payload_json ?? '' ), false, 64 );
		if ( ! ( $decoded instanceof \stdClass ) ) {
			return new \WP_Error( 'fotogenic_ai_incompatible_preset' );
		}

		return ( new Workflow_Draft_Normalizer() )->normalize_recipe_snapshot( $decoded );
	}

	private function route_matches( array $snapshot, string $workflow, string $source, string $scope ): bool {
		return $workflow === ( $snapshot['workflow'] ?? '' )
			&& $source === ( $snapshot['sourceType'] ?? '' )
			&& $scope === ( $snapshot['scope'] ?? '' );
	}

	private function schema_is_current(): bool {
		global $wpdb;
		$table = $this->get_table_name();
		if ( $table !== (string) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $table ) ) ) ) {
			return false;
		}
		$columns = array_map( 'strval', (array) $wpdb->get_col( "SHOW COLUMNS FROM {$table}", 0 ) );
		$current_columns = [ 'id', 'preset_uuid', 'name', 'payload_json', 'format_status', 'workflow', 'source_type', 'scope', 'created_at', 'updated_at', 'created_by' ];
		sort( $columns );
		sort( $current_columns );
		if ( $current_columns !== $columns ) {
			return false;
		}
		$index_names = array_values( array_unique( array_map( 'strval', (array) $wpdb->get_col( "SHOW INDEX FROM {$table}", 2 ) ) ) );
		$current_indexes = [ 'PRIMARY', 'preset_uuid', 'route_updated', 'updated_at' ];
		sort( $index_names );
		sort( $current_indexes );
		if ( $current_indexes !== $index_names ) {
			return false;
		}
		$index_rows = (array) $wpdb->get_results(
			$wpdb->prepare( "SHOW INDEX FROM {$table} WHERE Key_name = %s", 'route_updated' ),
			ARRAY_A
		);
		usort( $index_rows, static fn( array $a, array $b ): int => (int) $a['Seq_in_index'] <=> (int) $b['Seq_in_index'] );
		return [ 'format_status', 'workflow', 'source_type', 'scope', 'updated_at' ] === array_map(
			static fn( array $row ): string => (string) ( $row['Column_name'] ?? '' ),
			$index_rows
		);
	}

	private function get_table_name(): string {
		global $wpdb;
		return $wpdb->prefix . self::TABLE_NAME;
	}

	private function database_query_failed(): bool {
		global $wpdb;
		return '' !== trim( (string) $wpdb->last_error );
	}

	private function storage_error( string $code, string $message, int $status = 503 ): \WP_Error {
		return new \WP_Error( $code, $message, [ 'status' => $status ] );
	}
}
