<?php

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

use Easymailing\App\Core\Log\LogManager;
use Easymailing\App\Core\Provider\Woocommerce\Exception\CustomerNotFoundException;
use Easymailing\App\Core\Provider\Woocommerce\Exception\ProductOrVariantNotFoundException;
use Easymailing\App\Core\Provider\Woocommerce\Model\BatchOperationIdentifier;
use Easymailing\App\Core\Provider\Woocommerce\Model\LogType;
use Easymailing\App\Core\Provider\Woocommerce\Model\SkipReason;
use Easymailing\App\Core\Provider\Woocommerce\Model\SynchronizationStatus;
use Easymailing\App\Core\Provider\Woocommerce\Service\CustomerService;
use Easymailing\App\Core\Provider\Woocommerce\Service\MetadataHelper;
use Easymailing\App\Core\Provider\Woocommerce\Service\OrderValidator;
use Easymailing\App\Repository\ConfigRepository;
use Easymailing\App\Utils\Api;


class OrderBatchOperations
{
	private $configRepository;
	private $customerService;
	private $orderValidator;
	private $logManager;

	/**
	 * Mapa email => customer_resource_id. Se inicializa con los users registrados
	 * sincronizados (para poder imputarles orders guest con billing_email coincidente)
	 * y se extiende con los customers guest creados durante este run.
	 */
	private $emailIds = [];

	public function __construct(
		ConfigRepository $configRepository,
		CustomerService $customerService,
		OrderValidator $orderValidator,
		LogManager $logManager
	) {
		$this->configRepository = $configRepository;
		$this->customerService  = $customerService;
		$this->orderValidator  = $orderValidator;
		$this->logManager       = $logManager;
	}

	/**
	 * Genera un array de "batch operations" para crear/actualizar pedidos (y clientes)
	 * en EasyMailing.
	 */
	public function getOrderBatchOperations(): array
	{
		$batchOperations = [];

//		try {
			$this->logManager->createLog(LogType::BATCH, "start OrderBatchOperations");

			$config = $this->configRepository->getSynchronizationConfig();
			$shopId = $config->getShopId();

			// Índice email → resource_id construido a partir de users registrados ya
			// sincronizados. Permite imputar un order guest al customer existente si
			// el billing_email coincide, evitando 422 "email already used" al crear
			// customers guest duplicados.
			$this->emailIds = $this->buildRegisteredEmailIndex();
			$registeredReused = 0;
			$guestGenerated = 0;
			$retryOnly = (bool) get_option(MetadataHelper::getRetryOnlyMode());

			// Todos los pedidos de WooCommerce
			$wcOrders = wc_get_orders(['limit' => -1]);

			// 1) Resolver el customer de cada pedido de invitado
			foreach ($wcOrders as $wcOrder) {

				$order = $this->getMainOrder($wcOrder);

				if(!$order){
					continue;
				}

				// En modo retry_only, el pase 1 solo debe crear customers guest para
				// orders que están en error; los orders ok ya tienen su customer.
				if ($retryOnly && get_post_meta($order->get_id(), MetadataHelper::getSynchronizationStatus(), true) !== SynchronizationStatus::ERROR) {
					continue;
				}

				if ($order->get_user_id() === 0 && !get_post_meta($order->get_id(), MetadataHelper::getResourceId(), true)) {
					// Pedido invitado
					$email = strtolower(trim((string) $order->get_billing_email()));

					if ($email === '') {
						continue;
					}

					if (array_key_exists($email, $this->emailIds)) {
						// El email coincide con un user registrado ya sincronizado (o con
						// un customer guest creado antes en este mismo run). Reusamos su
						// resource_id y guardamos el meta del order para futuras syncs.
						if (!get_post_meta($order->get_id(), MetadataHelper::getCustomerResourceId(), true)) {
							update_post_meta($order->get_id(), MetadataHelper::getCustomerResourceId(), $this->emailIds[$email]);
							$registeredReused++;
						}
						continue;
					}

					// Si el order ya tiene customer_resource_id guardado, lo reutilizamos
					// en lugar de generar un UUID nuevo. Esto protege recoveries previos
					// (Fix B reactivo adoptó un resource_id real del API) y evita el bucle
					// en el que cada sync genera un UUID distinto y lo reintenta en vano.
					$existingCustomerResourceId = (string) get_post_meta($order->get_id(), MetadataHelper::getCustomerResourceId(), true);
					if ($existingCustomerResourceId !== '') {
						$this->emailIds[$email] = $existingCustomerResourceId;
						continue;
					}

					// Sin match: generamos un customer guest nuevo.
					$resourceId = wp_generate_uuid4();
					$this->emailIds[$email] = $resourceId;
					$guestGenerated++;

					$body = $this->customerService->createCustomerFromOrderBody($order);
					$body['resource_id'] = $resourceId;
					$body['email'] = $email;

					$url = "/stores/{$shopId}/customers?disableDoubleOptIn=true&disableWelcomeEmail=true&disableSmsDoubleOptIn=true";

					update_post_meta($order->get_id(), MetadataHelper::getCustomerResourceId(), $resourceId);

					$batchOperations[] = [
						"method" => "POST",
						"path"   => $url,
						"body"   => json_encode($body),
						"external_identifier" => BatchOperationIdentifier::buildIdentifier(
							BatchOperationIdentifier::GUEST_CUSTOMER,
							$order->get_id()
						)
					];
				}
			}

			$this->logManager->createLog(LogType::BATCH, sprintf(
				"Guest customers: %d generated, %d reused from registered users",
				$guestGenerated,
				$registeredReused
			));

			// 2) Crear o actualizar pedidos
			foreach ($wcOrders as $wcOrder) {

				$this->logManager->createLog(LogType::BATCH, "1");
				$order = $this->getMainOrder($wcOrder);

				if(!$order){
					continue;
				}

				if ($retryOnly && get_post_meta($order->get_id(), MetadataHelper::getSynchronizationStatus(), true) !== SynchronizationStatus::ERROR) {
					continue;
				}

				// Validar el pedido antes de procesarlo
				try {
					$this->orderValidator->validateOrder($order);
				} catch (ProductOrVariantNotFoundException $e) {
					$this->logManager->createLog(
						LogType::WARNING,
						sprintf(
							"Order ID: %s could not be synced. One or more order items (product or variant) have been deleted or moved to trash. Product ID: %s, Variant ID: %s.",
							$e->getOrderId(),
							$e->getProductId() ?? 'N/A',
							$e->getVariantId() ?? 'N/A'
						)
					);
					continue;
				}catch (CustomerNotFoundException $e) {
					$this->logManager->createLog(
						LogType::WARNING,
						sprintf(
							"Order ID: %s could not be synced. The associated customer (ID: %s) has been deleted from WordPress.",
							$e->getOrderId(),
							$e->getCustomerId()
						)
					);
					continue;
				}

				$emOrderId = get_post_meta($order->get_id(), MetadataHelper::getResourceId(), true);
				$isResourceCreated = get_post_meta($order->get_id(), MetadataHelper::isResourceCreated(), true);

				// Resolvemos el customer ANTES de decidir CREATE/UPDATE para aplicar
				// el mismo criterio de skip en los dos caminos: si no hay customer
				// válido, no enviamos la operación (el API devolvería 422).
				$customerResourceId = $this->resolveOrderCustomerResourceId($order);

				if (empty($customerResourceId)) {
					$this->markOrderAsSkipped($order, SkipReason::MISSING_CUSTOMER);
					continue;
				}

				if ($emOrderId && $isResourceCreated) {
					$batchOperations[] = $this->updateOrderLine($order, $emOrderId, $shopId, $customerResourceId);
				} else {
					$batchOperations[] = $this->createOrderLine($order, $shopId, $customerResourceId);
				}
			}

//		} catch (\Exception $e) {
//			$this->logManager->createLog(LogType::BATCH, sprintf("Error in OrderBatchOperations: %s", $e->getMessage()));
//		}

		$this->logManager->createLog(LogType::BATCH, "End OrderBatchOperations");

		return $batchOperations;
	}

	private function getMainOrder($order): ?\WC_Order
	{
		// Verificar si el objeto es un pedido válido
		if (!$order instanceof \WC_Order) {
			// Manejar diferentes tipos de objetos relacionados con pedidos
			if (method_exists($order, 'get_parent_id')) {
				$parentOrderId = $order->get_parent_id();
				if ($parentOrderId) {
					return wc_get_order($parentOrderId);
				}
			}

			// Si no tiene un método para obtener el pedido principal, devolvemos null
			return null;
		}

		// Si es un pedido regular, devolver directamente
		return $order;
	}

	private function generateOrderBody(\WC_Order $order, string $shopId, string $customerResourceId): array
	{
		$body = [
			'currency'     => $order->get_currency(),
			'order_total'  => Api::convertEurToCents($order->get_total()),
			'status'       => $this->getCustomOrderStatus($order->get_status()),
			'checkout_url' => $order->get_view_order_url(),
			'order_items'  => $this->getEasyMailingOrderItems($order, $shopId),
			'customer'     => "/stores/{$shopId}/customers/{$customerResourceId}",
			'order_number' => $order->get_order_number(),
			'processed_at' => $order->get_date_created() ? $order->get_date_created()->format('Y-m-d\TH:i:s\Z') : null,
		];

		$hash = get_post_meta($order->get_id(), MetadataHelper::getCampaignHash(), true);

		if($hash){
			$body['campaign_hash'] = $hash;
		}

		// Ver si está pagado
		if ($order->is_paid()) {
			$body['paid_at'] = $order->get_date_paid() ? $order->get_date_paid()->format('Y-m-d\TH:i:s\Z') : null;
		}

		// Ver si está cancelado
		if ($order->get_status() === 'cancelled') {
			$body['cancelled_at'] = $order->get_date_modified() ? $order->get_date_modified()->format('Y-m-d\TH:i:s\Z') : null;
		}

		// Tracking: carrier, number, URL
		$trackingCarrier = $order->get_meta('tracking_carrier');
		$trackingNumber = $order->get_meta('tracking_number');
		$trackingUrl = $order->get_meta('tracking_url');

		if ($trackingCarrier && $trackingNumber && $trackingUrl) {
			$body['tracking_carrier'] = $trackingCarrier;
			$body['tracking_number'] = $trackingNumber;
			$body['tracking_url'] = $trackingUrl;
		}

		return $body;
	}

	private function createOrderLine(\WC_Order $order, string $shopId, string $customerResourceId): array
	{
		$emOrderId = wp_generate_uuid4();
		update_post_meta($order->get_id(), MetadataHelper::getResourceId(), $emOrderId);
		$url = "/stores/{$shopId}/orders/import";
		$body = $this->generateOrderBody($order, $shopId, $customerResourceId);
		$body['resource_id'] = $emOrderId;

		return [
			"method" => "POST",
			"path"   => $url,
			"body"   => json_encode($body),
			"external_identifier" => BatchOperationIdentifier::buildIdentifier(BatchOperationIdentifier::ORDER, $order->get_id())
		];
	}

	private function updateOrderLine(\WC_Order $order, string $resourceId, string $shopId, string $customerResourceId): array
	{
		$url = "/stores/{$shopId}/orders/{$resourceId}";
		$body = $this->generateOrderBody($order, $shopId, $customerResourceId);

		return [
			"method" => "PUT",
			"path"   => $url,
			"body"   => json_encode($body),
			"external_identifier" => BatchOperationIdentifier::buildIdentifier(BatchOperationIdentifier::ORDER, $order->get_id())
		];
	}

	/**
	 * Devuelve las "order_items" para EasyMailing a partir de un pedido de WooCommerce.
	 */
	private function getEasyMailingOrderItems(\WC_Order $order, string $shopId): array
	{
		$items = [];
		foreach ($order->get_items() as $item) {
			$productId   = $item->get_product_id();
			$productResourceId = get_post_meta($productId, MetadataHelper::getResourceId(), true);

			$variationId = $item->get_variation_id();
			$variantResourceId   = $variationId
				? get_post_meta($variationId, MetadataHelper::getResourceId(), true)
				: get_post_meta($productId, MetadataHelper::getVariantResourceId(), true);

			$items[] = [
				'quantity' => $item->get_quantity(),
				'price'    => Api::convertEurToCents($item->get_total()),
				'variant'  => "/stores/{$shopId}/products/{$productResourceId}/variants/{$variantResourceId}",
			];
		}
		return $items;
	}


	/**
	 * Mapea el estado de WooCommerce a un estado custom para EasyMailing.
	 */
	private function getCustomOrderStatus($status): string
	{
		$custom_statuses = [
			'pending'    => 'new',
			'failed'    => 'new',
			'on-hold'    => 'new',
			'processing' => 'paid',
			'completed'  => 'shipped',
			'cancelled'  => 'cancelled',
			'refunded'   => 'refunded'
		];

		return $custom_statuses[$status] ?? 'new';
	}

	/**
	 * Resuelve el customer_resource_id a usar para este order:
	 *  - Registered (user_id > 0): user_meta del user asociado. Es la fuente viva, y
	 *    refleja reasignaciones hechas por el admin entre sync y sync.
	 *  - Guest (user_id == 0): preferimos el índice email → resource_id actual, porque
	 *    cubre imputación a user registrado y descarta UUIDs huérfanos guardados en
	 *    runs previos. Solo caemos al post_meta guardado si el email no tiene match.
	 *  - Si el índice devuelve un UUID distinto al guardado, actualizamos el post_meta
	 *    para que la siguiente sync no pelee contra un valor obsoleto.
	 */
	private function resolveOrderCustomerResourceId(\WC_Order $order): string
	{
		if ($order->get_user_id() > 0) {
			return (string) get_user_meta($order->get_customer_id(), MetadataHelper::getResourceId(), true);
		}

		$email = (string) $order->get_billing_email();
		$indexMatch = $this->getUuidByEmail($email);
		$storedMeta = (string) get_post_meta($order->get_id(), MetadataHelper::getCustomerResourceId(), true);

		if ($indexMatch) {
			if ($indexMatch !== $storedMeta) {
				update_post_meta($order->get_id(), MetadataHelper::getCustomerResourceId(), $indexMatch);
			}
			return $indexMatch;
		}

		return $storedMeta;
	}

	/**
	 * Devuelve un resource_id (uuid) conocido para ese email:
	 * un user registrado ya sincronizado o un customer guest creado en este run.
	 */
	private function getUuidByEmail($email): ?string
	{
		$email = strtolower(trim((string) $email));
		if ($email === '') {
			return null;
		}

		return $this->emailIds[$email] ?? null;
	}

	/**
	 * Construye un mapa email → customer_resource_id a partir de users registrados
	 * cuyo customer ya ha sido (o está siendo) sincronizado. Indexamos por user_email
	 * y por billing_email, con prioridad al primero.
	 */
	private function buildRegisteredEmailIndex(): array
	{
		global $wpdb;

		$resourceKey = MetadataHelper::getResourceId();
		$index = [];

		$userEmailRows = $wpdb->get_results($wpdb->prepare("
			SELECT u.user_email, m.meta_value AS resource_id
			FROM {$wpdb->users} u
			INNER JOIN {$wpdb->usermeta} m ON m.user_id = u.ID AND m.meta_key = %s
			WHERE m.meta_value <> ''
		", $resourceKey));

		foreach ($userEmailRows as $row) {
			$email = strtolower(trim((string) $row->user_email));
			if ($email !== '' && !isset($index[$email])) {
				$index[$email] = (string) $row->resource_id;
			}
		}

		$billingRows = $wpdb->get_results($wpdb->prepare("
			SELECT b.meta_value AS billing_email, r.meta_value AS resource_id
			FROM {$wpdb->usermeta} b
			INNER JOIN {$wpdb->usermeta} r ON r.user_id = b.user_id AND r.meta_key = %s
			WHERE b.meta_key = 'billing_email' AND b.meta_value <> '' AND r.meta_value <> ''
		", $resourceKey));

		foreach ($billingRows as $row) {
			$email = strtolower(trim((string) $row->billing_email));
			if ($email !== '' && !isset($index[$email])) {
				$index[$email] = (string) $row->resource_id;
			}
		}

		return $index;
	}

	private function markOrderAsSkipped(\WC_Order $order, string $reason): void
	{
		update_post_meta($order->get_id(), MetadataHelper::getSynchronizationStatus(), SynchronizationStatus::SKIPPED);
		update_post_meta($order->get_id(), MetadataHelper::getSkipReason(), $reason);

		$this->logManager->createLog(
			LogType::WARNING,
			sprintf("Order %d skipped: %s", $order->get_id(), $reason)
		);
	}
}
