<?php
/**
 * Historial de versiones por entidad.
 *
 * @package Logistica
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Snapshots versionados reutilizables por cualquier módulo.
 */
class Logistica_Version_History {

	/**
	 * @param array<string, mixed> $entry Datos de la versión.
	 * @return int ID de versión o 0.
	 */
	public static function save( $entry ) {
		global $wpdb;

		$table = Logistica_Database::table( 'versiones' );
		if ( ! $table ) {
			return 0;
		}

		$module_key  = sanitize_key( (string) ( $entry['module_key'] ?? '' ) );
		$entity_type = sanitize_key( (string) ( $entry['entity_type'] ?? '' ) );
		$entity_id   = sanitize_text_field( (string) ( $entry['entity_id'] ?? '' ) );

		if ( '' === $module_key || '' === $entity_type || '' === $entity_id ) {
			return 0;
		}

		$next_version = self::next_version_no( $module_key, $entity_type, $entity_id );

		$row = array(
			'module_key'  => $module_key,
			'entity_type' => $entity_type,
			'entity_id'   => $entity_id,
			'version_no'  => $next_version,
			'summary'     => sanitize_text_field( (string) ( $entry['summary'] ?? '' ) ),
			'payload'     => self::encode_payload( $entry['payload'] ?? null ),
			'created_by'  => max( 0, (int) ( $entry['created_by'] ?? get_current_user_id() ) ),
			'created_at'  => current_time( 'mysql', true ),
		);

		$inserted = $wpdb->insert( $table, $row );
		return $inserted ? (int) $wpdb->insert_id : 0;
	}

	/**
	 * @param string $module_key  Módulo.
	 * @param string $entity_type Tipo.
	 * @param string $entity_id   ID.
	 * @return int
	 */
	private static function next_version_no( $module_key, $entity_type, $entity_id ) {
		global $wpdb;

		$table = Logistica_Database::table( 'versiones' );
		$max   = (int) $wpdb->get_var(
			$wpdb->prepare(
				"SELECT MAX(version_no) FROM {$table} WHERE module_key = %s AND entity_type = %s AND entity_id = %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
				$module_key,
				$entity_type,
				$entity_id
			)
		);

		return max( 1, $max + 1 );
	}

	/**
	 * @param array<string, mixed> $args Filtros y paginación.
	 * @return array{items: array<int, array<string, mixed>>, total: int}
	 */
	public static function query( $args = array() ) {
		global $wpdb;

		$table = Logistica_Database::table( 'versiones' );
		if ( ! $table ) {
			return array(
				'items' => array(),
				'total' => 0,
			);
		}

		$where  = array( '1=1' );
		$params = array();

		if ( ! empty( $args['module_key'] ) ) {
			$where[]  = 'module_key = %s';
			$params[] = sanitize_key( (string) $args['module_key'] );
		}

		if ( ! empty( $args['entity_type'] ) ) {
			$where[]  = 'entity_type = %s';
			$params[] = sanitize_key( (string) $args['entity_type'] );
		}

		if ( ! empty( $args['entity_id'] ) ) {
			$where[]  = 'entity_id = %s';
			$params[] = sanitize_text_field( (string) $args['entity_id'] );
		}

		if ( ! empty( $args['search'] ) ) {
			$like     = '%' . $wpdb->esc_like( sanitize_text_field( (string) $args['search'] ) ) . '%';
			$where[]  = '(summary LIKE %s OR entity_id LIKE %s OR module_key LIKE %s)';
			$params[] = $like;
			$params[] = $like;
			$params[] = $like;
		}

		$where_sql = implode( ' AND ', $where );
		$count_sql = "SELECT COUNT(*) FROM {$table} WHERE {$where_sql}"; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		if ( $params ) {
			$count_sql = $wpdb->prepare( $count_sql, $params ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
		}
		$total = (int) $wpdb->get_var( $count_sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared

		$page      = max( 1, (int) ( $args['page'] ?? 1 ) );
		$page_size = max( 1, min( 100, (int) ( $args['page_size'] ?? 25 ) ) );
		$offset    = ( $page - 1 ) * $page_size;

		$list_sql = "SELECT * FROM {$table} WHERE {$where_sql} ORDER BY created_at DESC, id DESC LIMIT %d OFFSET %d"; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$list_params = array_merge( $params, array( $page_size, $offset ) );
		$rows = $wpdb->get_results( $wpdb->prepare( $list_sql, $list_params ), ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared

		$items = array();
		if ( is_array( $rows ) ) {
			foreach ( $rows as $row ) {
				$items[] = self::format_row( $row );
			}
		}

		return array(
			'items' => $items,
			'total' => $total,
		);
	}

	/**
	 * @param int $version_id ID de versión.
	 * @return array<string, mixed>|null
	 */
	public static function get( $version_id ) {
		global $wpdb;

		$table = Logistica_Database::table( 'versiones' );
		$version_id = (int) $version_id;
		if ( ! $table || $version_id <= 0 ) {
			return null;
		}

		$row = $wpdb->get_row(
			$wpdb->prepare( "SELECT * FROM {$table} WHERE id = %d", $version_id ), // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			ARRAY_A
		);

		return is_array( $row ) ? self::format_row( $row ) : null;
	}

	/**
	 * @param int $version_id ID de versión a restaurar.
	 * @return array<string, mixed>|WP_Error
	 */
	public static function restore( $version_id ) {
		$version = self::get( $version_id );
		if ( ! $version ) {
			return new WP_Error( 'version_not_found', __( 'Versión no encontrada.', 'logistica' ) );
		}

		$payload = json_decode( (string) ( $version['payload_raw'] ?? '' ), true );
		if ( ! is_array( $payload ) ) {
			$payload = array();
		}

		/**
		 * Restauración de versión delegada al módulo dueño de la entidad.
		 *
		 * @param string               $module_key  Módulo.
		 * @param string               $entity_type Tipo.
		 * @param string               $entity_id   ID.
		 * @param array<string, mixed> $payload     Snapshot.
		 * @param array<string, mixed> $version     Metadatos de versión.
		 */
		do_action( 'logistica_lifecycle_restore_version', (string) $version['module_key'], (string) $version['entity_type'], (string) $version['entity_id'], $payload, $version );

		Logistica_Audit::log(
			array(
				'module_key'  => (string) $version['module_key'],
				'entity_type' => (string) $version['entity_type'],
				'entity_id'   => (string) $version['entity_id'],
				'action'      => 'version_restore',
				'summary'     => sprintf(
					/* translators: %d: version number */
					__( 'Restauración de versión %d', 'logistica' ),
					(int) ( $version['version_no'] ?? 0 )
				),
				'meta'        => array(
					'version_id' => (int) ( $version['id'] ?? 0 ),
					'version_no' => (int) ( $version['version_no'] ?? 0 ),
				),
			)
		);

		return $version;
	}

	/**
	 * @param array<string, mixed> $row Fila DB.
	 * @return array<string, mixed>
	 */
	public static function format_row( $row ) {
		$user_id = (int) ( $row['created_by'] ?? 0 );
		$user    = $user_id > 0 ? get_userdata( $user_id ) : false;

		return array(
			'id'             => (int) ( $row['id'] ?? 0 ),
			'module_key'     => (string) ( $row['module_key'] ?? '' ),
			'module_label'   => Logistica_Module_Registry::module_label( (string) ( $row['module_key'] ?? '' ) ),
			'entity_type'    => (string) ( $row['entity_type'] ?? '' ),
			'entity_label'   => Logistica_Module_Registry::entity_label( (string) ( $row['module_key'] ?? '' ), (string) ( $row['entity_type'] ?? '' ) ),
			'entity_id'      => (string) ( $row['entity_id'] ?? '' ),
			'version_no'     => (int) ( $row['version_no'] ?? 0 ),
			'summary'        => (string) ( $row['summary'] ?? '' ),
			'payload_raw'    => (string) ( $row['payload'] ?? '' ),
			'created_by'     => $user_id,
			'created_by_name'=> $user ? (string) $user->display_name : '',
			'created_at'     => (string) ( $row['created_at'] ?? '' ),
		);
	}

	/**
	 * @param mixed $payload Datos a serializar.
	 * @return string|null
	 */
	private static function encode_payload( $payload ) {
		if ( null === $payload || '' === $payload ) {
			return null;
		}

		if ( is_string( $payload ) ) {
			return $payload;
		}

		return wp_json_encode( $payload );
	}
}
