<?php

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

use Easymailing\App\Api\Client;
use Easymailing\App\Api\Exception\NotFoundException;
use Easymailing\App\Model\Customer;
use Easymailing\App\Repository\ConfigRepository;
use Easymailing\App\Utils\Api;
use Easymailing\App\Utils\CustomerPayload;

/**
 * Servicio para gestionar la creación/actualización de clientes
 * en EasyMailing, incluyendo finalidades (treatment purposes)
 * y custom fields.
 */
class CustomerService
{
	private $config;
	private $apiClient;

	public function __construct(ConfigRepository $configRepository, Client $apiClient)
	{
		$this->config    = $configRepository->getSynchronizationConfig();
		$this->apiClient = $apiClient;
	}

	public function processCustomer(int $userId): void
	{
		$emCustomerId = get_user_meta($userId, MetadataHelper::getResourceId(), true);
		$userData = get_userdata($userId);
		$userInfo = $this->getUserInfo($userId);

		if (!$userData) {
			return;
		}

		// 1) Si NO hay $emCustomerId, buscamos por email/teléfono y procedemos
		if (!$emCustomerId) {
			$customer = $this->apiClient->findCustomerByIdentity(
				sprintf("/stores/%s/customers", $this->config->getShopId()),
				$userInfo['email'],
				$userInfo['phone']
			);

			// 1a) Si lo encontramos, lo actualizamos
			if ($customer) {
				$this->updateCustomer($userId,  $customer->getResourceId(), $userInfo);
				update_user_meta($userId, MetadataHelper::getResourceId(), $customer->getResourceId());
				update_user_meta($userId, MetadataHelper::getSynchronizationStatus(), 'success');
				return;
			}

			// 1b) Si no existe, lo creamos
			$customer = $this->createCustomer($userId, $userInfo);
			update_user_meta($userId, MetadataHelper::getResourceId(), $customer->getResourceId());
			update_user_meta($userId, MetadataHelper::getSynchronizationStatus(), 'success');
			return;
		}

		// 2) Si SÍ hay $emCustomerId, lo buscamos directamente
		$customer = null;
		try {
			$customer = $this->apiClient->getCustomer(
				sprintf("/stores/%s/customers/%s", $this->config->getShopId(), $emCustomerId)
			);
		}catch(NotFoundException $e){}


		// 2a) Si existe, actualizamos
		if ($customer) {
			$this->updateCustomer($userId,  $customer->getResourceId(), $userInfo);
			update_user_meta($userId, MetadataHelper::getSynchronizationStatus(), 'success');
			return;
		}

		// 2b) Si no existe, lo buscamos por email/teléfono
		$customer = $this->apiClient->findCustomerByIdentity(
			sprintf("/stores/%s/customers", $this->config->getShopId()),
			$userInfo['email'],
			$userInfo['phone']
		);

		// 2c) Si se encuentra por identidad, actualizamos con $emCustomerId
		if ($customer) {
			$this->updateCustomer($userId,  $customer->getResourceId(), $userInfo);
			update_user_meta($userId, MetadataHelper::getResourceId(), $customer->getResourceId());
			update_user_meta($userId, MetadataHelper::getSynchronizationStatus(), 'success');
			return;
		}

		// 2d) Si no lo encontramos, lo creamos
		$customer = $this->createCustomer($userId, $userInfo);
		update_user_meta($userId, MetadataHelper::getResourceId(), $customer->getResourceId());
		update_user_meta($userId, MetadataHelper::getSynchronizationStatus(), 'success');

	}


	private function getUserInfo($userId): array
	{
		$country = PhoneCountryResolver::fromUser($userId);

		return [
			'firstname' => !empty($billingFirstName = get_user_meta($userId, 'billing_first_name', true)) ? $billingFirstName : (get_user_meta($userId, 'first_name', true) ?: ''),
			'lastname'  => !empty($billingLastName = get_user_meta($userId, 'billing_last_name', true)) ? $billingLastName : (get_user_meta($userId, 'last_name', true) ?: ''),
			'email'     => !empty($billingEmail = get_user_meta($userId, 'billing_email', true)) ? $billingEmail : (get_userdata($userId)->user_email ?: ''),
			'company'   => get_user_meta($userId, 'billing_company', true) ?: '',
			'phone'     => CustomerPayload::normalizePhone(get_user_meta($userId, 'billing_phone', true), $country) ?: '',
			'country'   => $country,
		];
	}

	/**
	 * Crea un nuevo Customer en EasyMailing a partir de un usuario registrado
	 * (userId > 0). Solo enviará finalidades/custom_fields si están configurados.
	 */
	public function createCustomer(int $userId, array $userInfo): Customer
	{
		// 1) Obtenemos finalidades/custom_fields desde el user meta
		$consentData = $this->getConsentData($userId, null);

		// 2) Generamos la URL y un resource_id
		$url          = sprintf("/stores/%s/customers", $this->config->getShopId());
		$emCustomerId = wp_generate_uuid4();

		// 3) Construimos el array base (con resource_id)
		$baseData = $this->buildBaseData($userInfo, $emCustomerId);

		// 4) Añadimos member_consent y custom_fields si procede
		$body = $this->mergeConsentData($baseData, $consentData);

		// 5) Llamamos a la API
		return $this->apiClient->createCustomer($url, $body, CustomerPayload::optInQuery());
	}

	/**
	 * Actualiza un Customer existente en EasyMailing a partir de un usuario registrado.
	 */
	public function updateCustomer(int $userId, string $emCustomerId, array $userInfo): Customer
	{
		$consentData = $this->getConsentData($userId, null);

		$url = sprintf("/stores/%s/customers/%s", $this->config->getShopId(), $emCustomerId);

		// Generamos baseData sin resource_id (porque ya existe)
		$baseData = $this->buildBaseData($userInfo);

		$body = $this->mergeConsentData($baseData, $consentData);

		return $this->apiClient->updateCustomer($url, $body, CustomerPayload::optInQuery());
	}

	/**
	 * Busca un customer en Easymailing por email y teléfono. Si no existe, lo crea con los datos
	 * disponibles (del $order si se pasa, o del array $customerData, o con solo el
	 * email en caso contrario). Si el customer ya existe y los datos que tenemos a
	 * mano (firstname/lastname/company/phone) son distintos y no vacíos respecto a los
	 * del customer actual en Easymailing, lanza un PUT para mantenerlos al día.
	 *
	 * Se usa desde los flujos de carrito abandonado para guests (Block Checkout y
	 * Classic Checkout) donde aún no hay WP user_id pero sí tenemos billing
	 * first_name/last_name/company mientras el usuario rellena el checkout.
	 *
	 * @param string         $email        Email obligatorio.
	 * @param \WC_Order|null $order        Si se pasa, sus billing_* se usan tanto para crear como para update.
	 * @param array          $customerData Fallback si no hay $order. Claves admitidas: firstname, lastname, company, phone.
	 */
	public function resolveByEmail(string $email, ?\WC_Order $order = null, array $customerData = []): ?string
	{
		if (empty($email)) {
			return null;
		}

		$incoming = $this->buildIncomingData($order, $customerData);

		try {
			$customer = $this->apiClient->findCustomerByIdentity(
				sprintf("/stores/%s/customers", $this->config->getShopId()),
				$email,
				$incoming['phone']
			);

			if ($customer) {
				$resourceId = $customer->getResourceId();
				$this->updateCustomerIfChanged($customer, $incoming, $order);
				return $resourceId;
			}
		} catch (\Exception $e) {
			return null;
		}

		try {
			$url = sprintf("/stores/%s/customers", $this->config->getShopId());

			if ($order instanceof \WC_Order) {
				$body = $this->createCustomerFromOrderBody($order);
			} else {
				$body = CustomerPayload::body([
					'email'       => $email,
					'firstname'   => $incoming['firstname'],
					'lastname'    => $incoming['lastname'],
					'company'     => $incoming['company'],
					'phone'       => $incoming['phone'],
					'country'     => $incoming['country'],
				], wp_generate_uuid4(), $incoming['country'] ?: null);
			}

			$created = $this->apiClient->createCustomer($url, $body, CustomerPayload::optInQuery());
			return $created->getResourceId();
		} catch (\Exception $e) {
			return null;
		}
	}

	/**
	 * Normaliza los datos que tenemos disponibles para un customer guest desde
	 * el $order de WC, o desde el array $customerData, o ambos (orden precede).
	 */
	private function buildIncomingData(?\WC_Order $order, array $customerData): array
	{
		$data = [
			'firstname' => '',
			'lastname'  => '',
			'company'   => '',
			'phone'     => '',
			'country'   => '',
		];

		if ($order instanceof \WC_Order) {
			$data['country']   = PhoneCountryResolver::fromOrder($order) ?: '';
			$data['firstname'] = (string) $order->get_billing_first_name();
			$data['lastname']  = (string) $order->get_billing_last_name();
			$data['company']   = (string) $order->get_billing_company();
			$data['phone']     = CustomerPayload::normalizePhone($order->get_billing_phone(), $data['country']) ?: '';
		}

		if (!empty($customerData['country']) || !empty($customerData['billing_country'])) {
			$data['country'] = PhoneCountryResolver::fromCustomerData($customerData) ?: '';
		}

		foreach (['firstname', 'lastname', 'company'] as $key) {
			if (!empty($customerData[$key])) {
				$data[$key] = (string) $customerData[$key];
			}
		}

		if (!empty($customerData['phone'])) {
			$data['phone'] = CustomerPayload::normalizePhone($customerData['phone'], $data['country'] ?: null) ?: '';
		}

		return $data;
	}

	/**
	 * Dispara PUT /customers/X solo si los datos entrantes (billing_first_name,
	 * billing_last_name, billing_company, billing_phone) traen al menos un campo no vacío que
	 * difiera del valor actual del customer en Easymailing. Así evitamos
	 * sobrecargar la API con PUTs redundantes cuando WC hace varios AJAX de
	 * update_order_review durante el checkout.
	 */
	private function updateCustomerIfChanged(\Easymailing\App\Model\Customer $existing, array $incoming, ?\WC_Order $order): void
	{
		$current = [
			'firstname' => (string) ($existing->firstname ?? ''),
			'lastname'  => (string) ($existing->lastname ?? ''),
			'company'   => (string) ($existing->company ?? ''),
			'phone'     => (string) ($existing->phone ?? ''),
		];

		$hasChanges = false;
		foreach (['firstname', 'lastname', 'company', 'phone'] as $key) {
			if ($incoming[$key] !== '' && $incoming[$key] !== $current[$key]) {
				$hasChanges = true;
				break;
			}
		}

		if (!$hasChanges) {
			return;
		}

		try {
			$url = sprintf("/stores/%s/customers/%s", $this->config->getShopId(), $existing->getResourceId());

			if ($order instanceof \WC_Order) {
				$body = $this->updateCustomerFromOrderBody($order);
			} else {
				$body = CustomerPayload::body([
					'email'     => (string) ($existing->email ?? ''),
					'firstname' => $incoming['firstname'],
					'lastname'  => $incoming['lastname'],
					'company'   => $incoming['company'],
					'phone'     => $incoming['phone'],
					'country'   => $incoming['country'],
				], null, $incoming['country'] ?: null);
			}

			$this->apiClient->updateCustomer($url, $body, CustomerPayload::optInQuery());
		} catch (\Exception $e) {
			// No hacer fallar el flujo del checkout si el PUT falla.
		}
	}

	/**
	 * Devuelve un "body" con la información necesaria para crear un Customer
	 * a partir de un pedido invitado (userId = 0).
	 * Útil para batch requests o sincronizaciones de pedidos.
	 */
	public function createCustomerFromOrderBody(\WC_Order $order): array
	{
		$emCustomerId = wp_generate_uuid4();
		$country = PhoneCountryResolver::fromOrder($order);

		// Info base (email, firstname, etc.) extraída de la order
		$baseData = CustomerPayload::body([
			'email'       => $order->get_billing_email(),
			'firstname'   => $order->get_billing_first_name(),
			'lastname'    => $order->get_billing_last_name(),
			'company'     => $order->get_billing_company(),
			'phone'       => $order->get_billing_phone(),
			'country'     => $country,
		], $emCustomerId, $country);

		// Obtenemos finalidades/custom_fields desde el ORDER meta (pedido invitado)
		$consentData = $this->getConsentData(null, $order->get_id());

		return $this->mergeConsentData($baseData, $consentData);
	}

	/**
	 * Devuelve un "body" para actualizar un Customer a partir de un pedido invitado.
	 */
	public function updateCustomerFromOrderBody(\WC_Order $order): array
	{
		$country = PhoneCountryResolver::fromOrder($order);

		$baseData = CustomerPayload::body([
			'email'     => $order->get_billing_email(),
			'firstname' => $order->get_billing_first_name(),
			'lastname'  => $order->get_billing_last_name(),
			'company'   => $order->get_billing_company(),
			'phone'     => $order->get_billing_phone(),
			'country'   => $country,
		], null, $country);

		// Obtenemos finalidades/custom_fields desde el pedido invitado
		$consentData = $this->getConsentData(null, $order->get_id());

		return $this->mergeConsentData($baseData, $consentData);
	}

	/**
	 * =============================
	 *       MÉTODOS PRIVADOS
	 * =============================
	 */

	/**
	 * Crea un array base con datos de usuario (email, firstname, lastname, company, phone)
	 * y opcionalmente "resource_id" si se provee.
	 */
	private function buildBaseData(array $userInfo, ?string $emCustomerId = null): array
	{
		return CustomerPayload::body($userInfo, $emCustomerId, $userInfo['country'] ?? null);
	}

	/**
	 * Dado un array base y el $consentData (que contiene treatment_purposes y/o custom_fields),
	 * añade los datos pertinentes: "member_consent" y "custom_fields".
	 */
	private function mergeConsentData(array $baseData, array $consentData): array
	{
		// Hacemos copia para no mutar el original
		$body = $baseData;

		// Si tenemos finalidades
		if (isset($consentData['treatment_purposes'])) {
			$body['member_consent'] = [
				'ip'                 => Api::getClientIP(),
				'consent_at'         => (new \DateTime("now", new \DateTimeZone("UTC")))->format('Y-m-d\TH:i:s\Z'),
				'treatment_purposes' => $consentData['treatment_purposes']
			];
		}

		// Si tenemos custom_fields
		if (isset($consentData['custom_fields'])) {
			$body['custom_fields'] = $consentData['custom_fields'];
		}

		return $body;
	}

	/**
	 * Obtiene la info de consent (finalidades y/o custom fields) según la config,
	 * ya sea de un usuario (userId) o de un pedido invitado (orderId).
	 * Retorna un array con 'treatment_purposes' y/o 'custom_fields' si aplican.
	 */
	private function getConsentData(?int $userId, ?int $orderId): array
	{
		// 1) Miramos qué está configurado
		$tpConfig   = $this->config->getTreatmentPurposes() ?: [];
		$listConfig = $this->config->getBooleanListFields() ?: [];

		// 2) Leemos el JSON guardado
		$storedJson = '';
		if ($userId && $userId > 0) {
			// Usuario registrado => user_meta
			$storedJson = get_user_meta($userId, MetadataHelper::getTreatmentPurposes(), true);
		} elseif ($orderId && $orderId > 0) {
			// Pedido invitado => post_meta
			$storedJson = get_post_meta($orderId, MetadataHelper::getTreatmentPurposes(), true);
		}

		$decoded = json_decode($storedJson, true);
		if (empty($decoded) || !is_array($decoded)) {
			return [];
		}

		$purposes     = [];
		$customFields = [];

		// 3) Separamos según 'type'
		foreach ($decoded as $item) {
			if (!isset($item['type'], $item['id'], $item['value'])) {
				continue;
			}
			// Finalidades
			if (!empty($tpConfig)
			    && $item['type'] === 'treatment_purpose'
			    && $item['value'] === true
			) {
				$purposes[] = $item['id'];
			}
			// Custom fields
			if (!empty($listConfig)
			    && $item['type'] === 'custom_field'
			) {
				$customFields[] = [
					'list_field' => $item['id'],
					'value'      => (bool)$item['value']
				];
			}
		}

		// 4) Construimos la salida según config
		$result = [];
		if (!empty($tpConfig) && !empty($purposes)) {
			$result['treatment_purposes'] = $purposes;
		}
		if (!empty($listConfig) && !empty($customFields)) {
			$result['custom_fields'] = $customFields;
		}

		return $result;
	}
}
