<?php
/**
 * Where a synced contact's identity is remembered.
 *
 * @package TextTango
 */

declare( strict_types=1 );

namespace TextTango\Contacts;

use TextTango\Install\Schema;
use WC_Order;

defined( 'ABSPATH' ) || exit;

/**
 * Keeps the link between a WordPress customer and a TextTango contact (spec §10).
 *
 * Two homes, because customers come in two kinds. A registered customer's UUID
 * lives in usermeta. A guest has no account, so theirs lives on the order they
 * consented on — and on many stores guests are most of the list, so leaving them
 * out would mean quietly ignoring consent they actively gave.
 */
final class ContactRepository {

	/**
	 * Usermeta holding the TextTango contact UUID.
	 */
	public const META_UUID = 'texttango_contact_uuid';

	/**
	 * Usermeta recording when the contact was last pushed.
	 */
	public const META_SYNCED_AT = 'texttango_contact_synced_at';

	/**
	 * Order meta holding a guest's contact UUID.
	 */
	public const ORDER_META_UUID = '_texttango_contact_uuid';

	/**
	 * The stored UUID for a user.
	 *
	 * @param int $user_id User id.
	 */
	public static function uuid_for_user( int $user_id ): string {
		return $user_id > 0 ? (string) get_user_meta( $user_id, self::META_UUID, true ) : '';
	}

	/**
	 * Remember a user's contact UUID.
	 *
	 * @param int    $user_id User id.
	 * @param string $uuid    Contact UUID, or '' to forget it.
	 */
	public static function set_uuid_for_user( int $user_id, string $uuid ): void {
		if ( $user_id <= 0 ) {
			return;
		}

		if ( '' === $uuid ) {
			delete_user_meta( $user_id, self::META_UUID );
			delete_user_meta( $user_id, self::META_SYNCED_AT );

			return;
		}

		update_user_meta( $user_id, self::META_UUID, $uuid );
		update_user_meta( $user_id, self::META_SYNCED_AT, gmdate( 'Y-m-d H:i:s' ) );
	}

	/**
	 * The stored UUID for a guest order.
	 *
	 * @param WC_Order $order Order.
	 */
	public static function uuid_for_order( WC_Order $order ): string {
		return (string) $order->get_meta( self::ORDER_META_UUID, true );
	}

	/**
	 * Remember a guest order's contact UUID.
	 *
	 * @param WC_Order $order Order.
	 * @param string   $uuid  Contact UUID, or '' to forget it.
	 */
	public static function set_uuid_for_order( WC_Order $order, string $uuid ): void {
		if ( '' === $uuid ) {
			$order->delete_meta_data( self::ORDER_META_UUID );
		} else {
			$order->update_meta_data( self::ORDER_META_UUID, $uuid );
		}

		$order->save();
	}

	/**
	 * Pull a contact UUID out of a v2 response, wherever it sits.
	 *
	 * A created contact reports its id at the top of the envelope; a search
	 * returns a list. Both shapes are read here so callers do not have to care
	 * which endpoint answered.
	 *
	 * @param array<string, mixed> $response Decoded API response.
	 */
	public static function uuid_for_number( int $row_id ): string {
		$row = self::consent_row( $row_id );

		return null === $row ? '' : (string) ( $row['contact_uuid'] ?? '' );
	}

	/**
	 * Remember the contact behind a bare number, or forget it.
	 *
	 * Stored on the consent row rather than in meta, because a number on the
	 * consent list has no user and no order to hang meta from — which is the
	 * whole reason that table exists.
	 *
	 * @param int    $row_id Consent list row id.
	 * @param string $uuid   Contact UUID, or '' to forget.
	 */
	public static function set_uuid_for_number( int $row_id, string $uuid ): void {
		global $wpdb;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table.
		$wpdb->update(
			Schema::consents_table_name(),
			array(
				'contact_uuid' => $uuid,
				'synced_at'    => '' === $uuid ? null : gmdate( 'Y-m-d H:i:s' ),
			),
			array( 'id' => $row_id ),
			array( '%s', '%s' ),
			array( '%d' )
		);
	}

	/**
	 * One row of the consent list.
	 *
	 * @param int $row_id Consent list row id.
	 * @return array<string, mixed>|null
	 */
	public static function consent_row( int $row_id ): ?array {
		global $wpdb;

		$table = Schema::consents_table_name();

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table.
		$row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE id = %d", $row_id ), ARRAY_A );

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

	/**
	 * Pull the contact UUID out of an API response.
	 *
	 * @param array<string, mixed> $response Decoded response body.
	 */
	public static function extract_uuid( array $response ): string {
		$data = $response['data'] ?? null;

		if ( is_array( $data ) && isset( $data['id'] ) && is_scalar( $data['id'] ) ) {
			return (string) $data['id'];
		}

		// A list: take the first match, which is the only one a number can have
		// within a single group.
		if ( is_array( $data ) ) {
			foreach ( $data as $item ) {
				if ( is_array( $item ) && isset( $item['id'] ) && is_scalar( $item['id'] ) ) {
					return (string) $item['id'];
				}
			}
		}

		return '';
	}
}
