<?php

namespace Easymailing\App\Core\Provider\Woocommerce\Service;

use Easymailing\App\Api\Client;
use Easymailing\App\Core\Log\LogManager;
use Easymailing\App\Core\Provider\Woocommerce\Model\BatchOperationIdentifier;
use Easymailing\App\Core\Provider\Woocommerce\Model\LogType;
use Easymailing\App\Core\Provider\Woocommerce\Model\SynchronizationStatus;
use Easymailing\App\Core\Provider\Woocommerce\Service\MetadataHelper;
use Easymailing\App\Model\BatchOperation;

class BatchResultProcessManager
{
	private $apiClient;
	private $logManager;

	public function __construct(Client $apiClient, LogManager $logManager)
	{
		$this->apiClient = $apiClient;
		$this->logManager = $logManager;
	}

	/**
	 * @param array<BatchOperation> $bachOperations
	 *
	 */
	public function updateSynchronizationStatus(array $bachOperations): void
	{

		$this->logManager->createLog(LogType::BATCH, "Start updateSynchronizationStatus");

		foreach($bachOperations as $bachOperation) {
			$url = sprintf("%s/actions/regenerate_response_body_url", $bachOperation->id);
			$batchOperationResponseBodyUpdated = $this->apiClient->regenerateBatchOperationsResponseBodyUrl($url);
			$operations = $this->readResponseBodyUrl($batchOperationResponseBodyUpdated->responseBodyUrl);

			$this->logManager->createLog(LogType::BATCH, sprintf("Update status from batch operations. Count: %s", count($operations)));

			// GUEST_CUSTOMER comparte post_id con su order asociado. Procesamos el
			// order primero y el guest_customer al final: si el customer falla sin
			// recovery, su error ("email already used") sobreescribe al confuso del
			// order ("Este valor debería ser de tipo CustomerResource") y el UI
			// muestra la causa real.
			usort($operations, function($a, $b) {
				$aIsGuest = strpos((string) ($a['external_identifier'] ?? ''), 'guest_customer-') === 0;
				$bIsGuest = strpos((string) ($b['external_identifier'] ?? ''), 'guest_customer-') === 0;
				return ($aIsGuest ? 1 : 0) - ($bIsGuest ? 1 : 0);
			});

			foreach($operations as $operation) {

				$externalIdentifier = $operation['external_identifier'];
				if(!$externalIdentifier){
					continue;
				}

				$type = BatchOperationIdentifier::getIdentifierType($externalIdentifier);
				$id = BatchOperationIdentifier::getIdentifierId($externalIdentifier);
				$status = $this->getStatusFromOperation($operation);

				// Fix B reactivo: si un POST /customers falla con una identidad ya usada,
				// intentamos recuperar el resource_id del customer existente en el store
				// vía GET /customers?email=X o GET /customers?phone=X. Si lo encontramos, adoptamos ese UUID y
				// consideramos la operación como resuelta. Aplica a customers de users
				// registrados (tipo CUSTOMER) y a customers sintéticos de pedidos invitados
				// (tipo GUEST_CUSTOMER, donde $id es el order_id).
				$recoveredResourceId = null;
				$isCustomerLike = in_array($type, [
					BatchOperationIdentifier::CUSTOMER,
					BatchOperationIdentifier::GUEST_CUSTOMER,
				], true);
				if ($isCustomerLike && $status !== SynchronizationStatus::SUCCESS) {
					$recoveredResourceId = $this->tryRecoverCustomerByIdentity($operation);
					if ($recoveredResourceId !== null) {
						$status = SynchronizationStatus::SUCCESS;
					}
				}

				$errorMessage = $status === SynchronizationStatus::ERROR
					? $this->extractErrorMessage($operation)
					: null;

				if($status !== SynchronizationStatus::SUCCESS){
					$this->logManager->createLog(LogType::BATCH, sprintf("type: %s, id: %s, status: %s, path: %s, body: %s, response %s", $type, $id, $status, $operation['path'], $operation['body'], $operation['response'] ));
				}

				$shouldClearOrphan = $recoveredResourceId === null
					? $this->shouldClearOrphanMeta($operation, $status)
					: false;

				switch($type) {
					case BatchOperationIdentifier::CUSTOMER:
						update_user_meta($id, MetadataHelper::getSynchronizationStatus(), $status);
						if ($recoveredResourceId !== null) {
							update_user_meta($id, MetadataHelper::getResourceId(), $recoveredResourceId);
							update_user_meta($id, MetadataHelper::isResourceCreated(), true);
						} elseif ($operation['method'] === 'POST' && $status === SynchronizationStatus::SUCCESS) {
							update_user_meta($id, MetadataHelper::isResourceCreated(), true);
						}
						$this->persistErrorMessage('user', $id, $errorMessage);
						if($shouldClearOrphan){
							delete_user_meta($id, MetadataHelper::getResourceId());
							delete_user_meta($id, MetadataHelper::isResourceCreated());
						}
						break;
					case BatchOperationIdentifier::GUEST_CUSTOMER:
						// $id es el order_id. La operación del order asociado (si existe)
						// ya gestiona getSynchronizationStatus() y el error message; aquí
						// movemos el customer_resource_id del order según el resultado
						// del POST del customer guest: adoptamos el recovered, o limpiamos
						// el UUID huérfano para que la próxima sync regenere.
						if ($recoveredResourceId !== null) {
							update_post_meta($id, MetadataHelper::getCustomerResourceId(), $recoveredResourceId);
						} elseif ($shouldClearOrphan) {
							delete_post_meta($id, MetadataHelper::getCustomerResourceId());
						}
						// Si el customer falló sin recovery, sobreescribimos el error_message
						// del post con el del customer (más claro que el del order).
						if ($recoveredResourceId === null && $errorMessage !== null) {
							update_post_meta($id, MetadataHelper::getErrorMessage(), $errorMessage);
						}
						break;
					case BatchOperationIdentifier::CATEGORY:
						update_term_meta($id, MetadataHelper::getSynchronizationStatus(), $status);
						if($operation['method'] === 'POST' and $status === SynchronizationStatus::SUCCESS){
							update_term_meta($id, MetadataHelper::isResourceCreated(), true);
						}
						$this->persistErrorMessage('term', $id, $errorMessage);
						if($shouldClearOrphan){
							delete_term_meta($id, MetadataHelper::getResourceId());
							delete_term_meta($id, MetadataHelper::isResourceCreated());
						}
						break;
					case BatchOperationIdentifier::ORDER:
					case BatchOperationIdentifier::VARIANT:
					case BatchOperationIdentifier::PRODUCT:
						update_post_meta($id, MetadataHelper::getSynchronizationStatus(), $status);
						if($operation['method'] === 'POST' and $status === SynchronizationStatus::SUCCESS){
							update_post_meta($id, MetadataHelper::isResourceCreated(), true);
						}
						$this->persistErrorMessage('post', $id, $errorMessage);
						if($shouldClearOrphan){
							delete_post_meta($id, MetadataHelper::getResourceId());
							delete_post_meta($id, MetadataHelper::isResourceCreated());
						}
						break;
					case BatchOperationIdentifier::VARIANT_SIMPLE:
						update_post_meta($id, MetadataHelper::getSynchronizationStatus(), $status);
						if($operation['method'] === 'POST' and $status === SynchronizationStatus::SUCCESS){
							update_post_meta($id, MetadataHelper::isResourceCreated(), true);
						}
						$this->persistErrorMessage('post', $id, $errorMessage);
						if($shouldClearOrphan){
							delete_post_meta($id, MetadataHelper::getVariantResourceId());
						}
						break;


				}
			}
		}
	}

	private function getStatusFromOperation(array $operation): string
	{
		$status = SynchronizationStatus::PENDING;

		if($operation['status'] >= 200 && $operation['status'] <= 300){
			$status = SynchronizationStatus::SUCCESS;
		}

		if($operation['status'] > 300){
			$status = SynchronizationStatus::ERROR;
		}

		return $status;
	}

	/**
	 * Determina si debemos borrar el resource_id guardado porque ha quedado huérfano:
	 * - Un POST que falla deja un resource_id local sin recurso remoto.
	 * - Un PUT que devuelve 404 apunta a un recurso que ya no existe en la tienda actual.
	 * En ambos casos, el próximo sync debería generar un POST nuevo.
	 * Otros errores (p.ej. PUT 422 por referencias rotas) mantienen el meta porque el recurso sí existe.
	 */
	private function shouldClearOrphanMeta(array $operation, string $status): bool
	{
		if($status === SynchronizationStatus::SUCCESS){
			return false;
		}

		$method = $operation['method'] ?? null;
		$httpStatus = $operation['status'] ?? 0;

		if($method === 'POST'){
			return true;
		}

		if($method === 'PUT' && (int) $httpStatus === 404){
			return true;
		}

		return false;
	}

	/**
	 * Cuando un POST /customers devuelve 422 por identidad ya usada, el customer ya
	 * existe en la store (típicamente de una sync previa, un retry o un cruce con
	 * subscribers preexistentes). En lugar de dejar al plugin atorado con UUIDs
	 * huérfanos, consultamos el API por email o teléfono y adoptamos el resource_id remoto.
	 */
	private function tryRecoverCustomerByIdentity(array $operation): ?string
	{
		if (($operation['method'] ?? '') !== 'POST') {
			return null;
		}

		$response = $operation['response'] ?? '';
		$responseText = is_string($response) ? $response : json_encode($response);
		if (stripos((string) $responseText, 'already used') === false) {
			return null;
		}

		$body = json_decode($operation['body'] ?? '', true);
		$email = is_array($body) ? ($body['email'] ?? null) : null;
		$phone = is_array($body) ? ($body['phone'] ?? null) : null;
		if (!$email && !$phone) {
			return null;
		}

		$rawPath = (string) ($operation['path'] ?? '');
		if ($rawPath === '') {
			return null;
		}
		$collectionIri = strtok($rawPath, '?');
		if ($collectionIri === false || $collectionIri === '') {
			return null;
		}

		try {
			$customer = $this->apiClient->findCustomerByIdentity($collectionIri, $email, $phone);
		} catch (\Exception $e) {
			$this->logManager->createLog(
				LogType::BATCH,
				sprintf("Recover customer by identity failed (%s / %s): %s", $email, $phone, $e->getMessage())
			);
			return null;
		}

		if (!$customer || empty($customer->resourceId)) {
			return null;
		}

		$this->logManager->createLog(
			LogType::BATCH,
			sprintf("Recovered customer by identity: %s / %s -> resource_id %s", $email, $phone, $customer->resourceId)
		);

		return $customer->resourceId;
	}

	/**
	 * Saca un mensaje legible del response del API. API Platform devuelve detail,
	 * hydra:description o title según el caso; preferimos detail por ser el más
	 * específico.
	 */
	private function extractErrorMessage(array $operation): ?string
	{
		$raw = $operation['response'] ?? null;
		if (empty($raw)) {
			return null;
		}

		$response = is_string($raw) ? json_decode($raw, true) : $raw;
		if (!is_array($response)) {
			return is_string($raw) ? $raw : null;
		}

		foreach (['detail', 'hydra:description', 'description', 'title'] as $key) {
			if (!empty($response[$key]) && is_string($response[$key])) {
				return $response[$key];
			}
		}

		return null;
	}

	private function persistErrorMessage(string $metaType, int $id, ?string $message): void
	{
		$metaKey = MetadataHelper::getErrorMessage();
		if ($message === null || $message === '') {
			switch ($metaType) {
				case 'user':
					delete_user_meta($id, $metaKey);
					break;
				case 'post':
					delete_post_meta($id, $metaKey);
					break;
				case 'term':
					delete_term_meta($id, $metaKey);
					break;
			}
			return;
		}

		switch ($metaType) {
			case 'user':
				update_user_meta($id, $metaKey, $message);
				break;
			case 'post':
				update_post_meta($id, $metaKey, $message);
				break;
			case 'term':
				update_term_meta($id, $metaKey, $message);
				break;
		}
	}

	private function readResponseBodyUrl($url): array
	{
		$jsonData = file_get_contents($url);
		return json_decode($jsonData, true);

	}
}
