<?php
/**
 * In-memory top affiliate candidate repository.
 *
 * @package Affilun\Infrastructure\Persistence
 */

declare(strict_types=1);

namespace Affilun\Infrastructure\Persistence;

/**
 * Stores Cloud top affiliate candidates in memory for tests and fallback wiring.
 */
final class InMemoryTopAffiliateCandidateRepository implements TopAffiliateCandidateRepository {

	/**
	 * @var list<ProductRecord>
	 */
	private array $records = array();

	/**
	 * Replace cached candidate rows.
	 *
	 * @param list<array<string,mixed>> $rows Product-shaped rows.
	 */
	public function replaceAll( array $rows ): int {
		$records = array();
		$nextId  = 1;
		$now     = gmdate( ProductRecord::MYSQL_DATETIME_FORMAT );
		foreach ( $rows as $row ) {
			$provider       = self::stringField( $row, 'provider' );
			$providerItemId = self::stringField( $row, 'provider_item_id' );
			$title          = self::stringField( $row, 'title' );
			$detailUrl      = self::stringField( $row, 'detail_url' );
			if ( $provider === '' || $providerItemId === '' || $title === '' || $detailUrl === '' ) {
				continue;
			}
			$row['id']               = $nextId++;
			$row['provider']         = $provider;
			$row['provider_item_id'] = $providerItemId;
			$row['canonical_hash']   = self::stringField( $row, 'canonical_hash' ) !== ''
				? self::stringField( $row, 'canonical_hash' )
				: hash( 'sha256', $provider . ':' . $providerItemId );
			$row['title']            = $title;
			$row['detail_url']       = $detailUrl;
			$row['snapshot_version'] = isset( $row['snapshot_version'] ) ? (int) $row['snapshot_version'] : 1;
			$row['sync_state']       = self::stringField( $row, 'sync_state' ) !== '' ? self::stringField( $row, 'sync_state' ) : ProductRecord::DEFAULT_SYNC_STATE;
			$row['lifecycle_state']  = self::stringField( $row, 'lifecycle_state' ) !== '' ? self::stringField( $row, 'lifecycle_state' ) : ProductRecord::DEFAULT_LIFECYCLE_STATE;
			$row['created_at']       = self::stringField( $row, 'created_at' ) !== '' ? self::stringField( $row, 'created_at' ) : $now;
			$row['updated_at']       = self::stringField( $row, 'updated_at' ) !== '' ? self::stringField( $row, 'updated_at' ) : $now;
			$row['last_synced_at']   = self::stringField( $row, 'last_synced_at' ) !== '' ? self::stringField( $row, 'last_synced_at' ) : $now;
			$records[]               = ProductRecord::fromArray( $row );
		}
		$this->records = $records;

		return count( $this->records );
	}

	/**
	 * List cached candidate rows.
	 *
	 * @param ProductListQuery $query List query.
	 */
	public function findPaginated( ProductListQuery $query ): ProductListResult {
		$records = array_values(
			array_filter(
				$this->records,
				static function ( ProductRecord $record ) use ( $query ): bool {
					if ( $query->provider() !== null && $record->provider() !== $query->provider() ) {
						return false;
					}
					if ( $query->search() !== null && stripos( $record->title(), $query->search() ) === false ) {
						return false;
					}
					if ( $query->syncState() !== null && $record->syncState() !== $query->syncState() ) {
						return false;
					}
					if ( $query->lifecycleState() !== null && $record->lifecycleState() !== $query->lifecycleState() ) {
						return false;
					}

					return true;
				}
			)
		);
		usort(
			$records,
			static function ( ProductRecord $left, ProductRecord $right ) use ( $query ): int {
				$value = match ( $query->sortKey() ) {
					ProductListQuery::SORT_KEY_CREATED_AT => $left->createdAt() <=> $right->createdAt(),
					ProductListQuery::SORT_KEY_TITLE => strcasecmp( $left->title(), $right->title() ),
					ProductListQuery::SORT_KEY_LAST_SYNCED_AT => ( $left->lastSyncedAt()?->getTimestamp() ?? 0 ) <=> ( $right->lastSyncedAt()?->getTimestamp() ?? 0 ),
					default => $left->updatedAt() <=> $right->updatedAt(),
				};

				return $query->sortOrder() === ProductListQuery::SORT_ORDER_ASC ? $value : -$value;
			}
		);
		$total  = count( $records );
		$offset = ( $query->page() - 1 ) * $query->perPage();
		$items  = array_slice( $records, $offset, $query->perPage() );

		return new ProductListResult( $items, $total, $query->page(), $query->perPage() );
	}

	/**
	 * Count cached candidate rows.
	 */
	public function count(): int {
		return count( $this->records );
	}

	/**
	 * @param array<string,mixed> $row Row.
	 * @param string              $key Field key.
	 */
	private static function stringField( array $row, string $key ): string {
		$value = $row[ $key ] ?? '';
		return is_scalar( $value ) ? trim( (string) $value ) : '';
	}
}
