<?php

namespace ShopManagerPro\Products;

use ShopManagerPro\Jobs\DTO\Generated\JobProductsItemChanges;
use ShopManagerPro\Jobs\DTO\Generated\JobProductsItemChangesAcf;
use ShopManagerPro\Jobs\DTO\Generated\JobProductsItemChangesBasic;
use ShopManagerPro\Jobs\DTO\Generated\JobProductsItemChangesDefaultTaxonomy;
use ShopManagerPro\Jobs\DTO\Generated\JobProductsItemChangesProductAttribute;
use ShopManagerPro\Jobs\DTO\Generated\JobProductsItemErrorAlternative2;
use ShopManagerPro\Products\DTO\Generated\Product;
use ShopManagerPro\Products\DTO\Generated\ProductBasicStockStatusAlternative1;
use ShopManagerPro\Products\DTO\Generated\ProductBulkUpdateActions;
use ShopManagerPro\Products\DTO\Generated\ProductBulkUpdateActionsSpecialProductGroupUpdates;
use ShopManagerPro\Products\DTO\Generated\ProductBulkUpdateActionsSpecialRegularPriceMode;
use ShopManagerPro\Products\DTO\Generated\ProductBulkUpdateActionsSpecialSalePriceMode;
use ShopManagerPro\Products\DTO\ProductUpdateException;
use ShopManagerPro\Products\Fields\DTO\ProductFieldGroupType;
use ShopManagerPro\Products\Fields\FieldNameEncoderService;
use ShopManagerPro\Shared\Utils\Logger;

class ProductUpdateService {
	public static function getProductChanges(Product $product, ProductBulkUpdateActions $actions) {
		$changesDTO = new JobProductsItemChanges();
		$basicChangesDTO = new JobProductsItemChangesBasic();
		$basic = $product->getBasic();
		$basicChangesDTO = ProductChangeService::applyRegularPriceChange($basicChangesDTO, $actions, $basic);
		$basicChangesDTO = ProductChangeService::applySalePriceChange($basicChangesDTO, $actions, $basic);
		$basicChangesDTO = ProductChangeService::applyStockStatusChange($basicChangesDTO, $actions, $basic);
		$basicChangesDTO = ProductChangeService::applyPostStatusChange($basicChangesDTO, $actions, $basic);
		$basicChangesDTO = ProductChangeService::applyStockChange($basicChangesDTO, $actions, $basic);
		$basicChangesDTO = ProductChangeService::applyTaxClassChange($basicChangesDTO, $actions, $basic);
		$basicChangesDTO = ProductChangeService::applyTaxStatusChange($basicChangesDTO, $actions, $basic);
		if ($basicChangesDTO->toJson() !== []) {
			$changesDTO = $changesDTO->withBasic($basicChangesDTO);
		} $specialActions = $actions->getSpecial();
		if ($specialActions && $specialActions->getProductGroupUpdates() !== null) {
			$productGroupUpdates = $specialActions->getProductGroupUpdates();
			if ($productGroupUpdates->getAdd() !== null || $productGroupUpdates->getRemove() !== null) {
				self::buildProductGroupChanges($product, $productGroupUpdates, $changesDTO);
			}
		}

return $changesDTO;
	}

	public static function updateProduct(int $productId, JobProductsItemChanges $changes) {
		$product = wc_get_product($productId);
		if (!$product) {
			throw new ProductUpdateException(new JobProductsItemErrorAlternative2("Product with ID $productId not found for bulk update."));
		} self::validateExpectedValues($product, $changes);
		if ($changes->getBasic()) {
			$basic = $changes->getBasic();
			if ($basic->getRegularPrice()) {
				$product->set_regular_price($basic->getRegularPrice()->getNew());
			} if ($basic->getSalePrice()) {
				$product->set_sale_price($basic->getSalePrice()->getNew());
			} if ($basic->getStockStatus()) {
				$product->set_stock_status($basic->getStockStatus()->getNew()->value);
			} if ($basic->getPostStatus()) {
				$product->set_status($basic->getPostStatus()->getNew()->value);
			} if ($basic->getStock()) {
				$product->set_stock_quantity($basic->getStock()->getNew());
			} if ($basic->getTaxClass()) {
				$product->set_tax_class($basic->getTaxClass()->getNew());
			} if ($basic->getTaxStatus()) {
				$product->set_tax_status($basic->getTaxStatus()->getNew());
			}
		} if ($changes->getDefaultTaxonomy()) {
			foreach ($changes->getDefaultTaxonomy()->getNew() as $taxonomy => $termIds) {
				wp_set_object_terms($productId, $termIds, $taxonomy, false);
			}
		} if ($changes->getAcf() && function_exists('update_field')) {
			foreach ($changes->getAcf()->getNew() as $fieldId => $values) {
				update_field($fieldId, $values, $productId);
			}
		} if ($changes->getProductAttribute()) {
			foreach ($changes->getProductAttribute()->getNew() as $attribute => $termIds) {
				wp_set_object_terms($productId, $termIds, $attribute, false);
			}
		} $product->save();
	}

	private static function validateExpectedValues(\WC_Product $product, JobProductsItemChanges $changes) {
		ProductChangeService::validateRegularPrice($product, $changes);
		ProductChangeService::validateSalePrice($product, $changes);
		ProductChangeService::validateStockStatus($product, $changes);
		ProductChangeService::validatePostStatus($product, $changes);
		ProductChangeService::validateStock($product, $changes);
		ProductChangeService::validateTaxClass($product, $changes);
		ProductChangeService::validateTaxStatus($product, $changes);
		ProductChangeService::validateDefaultTaxonomies($product, $changes);
		ProductChangeService::validateProductAttributes($product, $changes);
		ProductChangeService::validateAcfFields($product, $changes);
	}

	public static function valuesMatch(mixed $expected, mixed $actual) {
		if ($expected === null && $actual === null) {
			return true;
		} if ($expected === null || $actual === null) {
			return false;
		} if ((is_float($expected) || is_int($expected)) && (is_float($actual) || is_int($actual))) {
			return abs((float) $expected - (float) $actual) < 0.001;
		}

return $expected === $actual;
	}

	public static function calculateNewPrice(?float $referencePrice, ?float $priceModification, ProductBulkUpdateActionsSpecialRegularPriceMode|ProductBulkUpdateActionsSpecialSalePriceMode $mode) {
		if ($mode === ProductBulkUpdateActionsSpecialRegularPriceMode::clear || $mode === ProductBulkUpdateActionsSpecialSalePriceMode::clear) {
			return null;
		} if ($referencePrice === null) {
			$referencePrice = 0;
		} if ($priceModification === null) {
			$priceModification = 0;
		} $newPrice = match ($mode) {
			ProductBulkUpdateActionsSpecialRegularPriceMode::fixed, ProductBulkUpdateActionsSpecialSalePriceMode::fixed => $priceModification, ProductBulkUpdateActionsSpecialRegularPriceMode::absolute, ProductBulkUpdateActionsSpecialSalePriceMode::absolute => $referencePrice + $priceModification, ProductBulkUpdateActionsSpecialRegularPriceMode::percentage, ProductBulkUpdateActionsSpecialSalePriceMode::percentage => $referencePrice * (1 + $priceModification / 100),
		};
		$newPrice = max(0, $newPrice);
		$newPrice = round($newPrice, 2);

		return $newPrice;
	}

	public static function isValidStockStatus(string $stockStatus) {
		$validStatuses = array_column(ProductBasicStockStatusAlternative1::cases(), 'value');

		return in_array($stockStatus, $validStatuses, true);
	}

	private static function buildProductGroupChanges(Product $product, ProductBulkUpdateActionsSpecialProductGroupUpdates $productGroupUpdates, JobProductsItemChanges &$changesDTO) {
		$taxonomyChanges = [];
		$acfChanges = [];
		$productAttributeChanges = [];
		$addItems = $productGroupUpdates->getAdd() ?? [];
		$removeItems = $productGroupUpdates->getRemove() ?? [];
		foreach ([['add', $addItems], ['remove', $removeItems]] as [$operation, $items]) {
			foreach ($items as $item) {
				$key = $item->getKey();
				$values = $item->getValues();
				try {
					$decoded = FieldNameEncoderService::decode($key);
					$fieldType = $decoded['type'];
					$fieldName = $decoded['field'];
				} catch (\ValueError $e) {
					Logger::error('Invalid field key format', ['key' => $key, 'error' => $e->getMessage()]);
					continue;
				} switch ($fieldType) {
					case ProductFieldGroupType::DEFAULT_TAXONOMY: if (!isset($taxonomyChanges[$fieldName])) {
						$taxonomyChanges[$fieldName] = ['add' => [], 'remove' => []];
					} $taxonomyChanges[$fieldName][$operation] = array_map(function ($v) { return (int) $v; }, $values);
						break;
					case ProductFieldGroupType::ACF: if (!isset($acfChanges[$fieldName])) {
						$acfChanges[$fieldName] = ['add' => [], 'remove' => []];
					} $acfChanges[$fieldName][$operation] = $values;
						break;
					case ProductFieldGroupType::PRODUCT_ATTRIBUTE: if (!isset($productAttributeChanges[$fieldName])) {
						$productAttributeChanges[$fieldName] = ['add' => [], 'remove' => []];
					} $productAttributeChanges[$fieldName][$operation] = array_map(function ($v) { return (int) $v; }, $values);
						break;
					default: break;
				}
			}
		} if (!empty($taxonomyChanges)) {
			$oldTaxonomies = [];
			$newTaxonomies = [];
			foreach ($taxonomyChanges as $taxonomyName => $operations) {
				$currentTermIds = self::getProductTermIds($product, $taxonomyName);
				$oldTaxonomies[$taxonomyName] = $currentTermIds;
				$newTermIds = $currentTermIds;
				if (!empty($operations['add'])) {
					$newTermIds = array_unique(array_merge($newTermIds, $operations['add']));
				} if (!empty($operations['remove'])) {
					$newTermIds = array_diff($newTermIds, $operations['remove']);
				} $newTermIds = array_values($newTermIds);
				$newTaxonomies[$taxonomyName] = $newTermIds;
			} $taxonomyDTO = new JobProductsItemChangesDefaultTaxonomy($oldTaxonomies, $newTaxonomies);
			$changesDTO = $changesDTO->withDefaultTaxonomy($taxonomyDTO);
		} if (!empty($acfChanges)) {
			$oldAcfValues = [];
			$newAcfValues = [];
			foreach ($acfChanges as $fieldId => $operations) {
				$currentValues = self::getProductAcfValues($product, $fieldId);
				$oldAcfValues[$fieldId] = $currentValues;
				$newValues = $currentValues;
				if (!empty($operations['add'])) {
					$newValues = array_unique(array_merge($newValues, $operations['add']));
				} if (!empty($operations['remove'])) {
					$newValues = array_diff($newValues, $operations['remove']);
				} $newValues = array_values($newValues);
				$newAcfValues[$fieldId] = $newValues;
			} $acfDTO = new JobProductsItemChangesAcf($oldAcfValues, $newAcfValues);
			$changesDTO = $changesDTO->withAcf($acfDTO);
		} if (!empty($productAttributeChanges)) {
			$oldAttributes = [];
			$newAttributes = [];
			foreach ($productAttributeChanges as $attributeName => $operations) {
				$currentTermIds = self::getProductTermIds($product, $attributeName);
				$oldAttributes[$attributeName] = $currentTermIds;
				$newTermIds = $currentTermIds;
				if (!empty($operations['add'])) {
					$newTermIds = array_unique(array_merge($newTermIds, $operations['add']));
				} if (!empty($operations['remove'])) {
					$newTermIds = array_diff($newTermIds, $operations['remove']);
				} $newTermIds = array_values($newTermIds);
				$newAttributes[$attributeName] = $newTermIds;
			} $attributeDTO = new JobProductsItemChangesProductAttribute($oldAttributes, $newAttributes);
			$changesDTO = $changesDTO->withProductAttribute($attributeDTO);
		}
	}

	private static function getProductTermIds(Product $product, string $taxonomy): array {
		$productId = $product->getBasic()->getId();
		$terms = wp_get_object_terms($productId, $taxonomy, ['fields' => 'ids']);
		if ($terms instanceof \WP_Error) {
			Logger::error('Failed to get product terms', ['productId' => $productId, 'taxonomy' => $taxonomy, 'error' => $terms->get_error_message()]);

			return [];
		} $termIds = $terms;

		return $termIds;
	}

	private static function getProductAcfValues(Product $product, string $fieldId): array {
		$productId = $product->getBasic()->getId();
		if (!function_exists('get_field')) {
			Logger::error('ACF not available');

			return [];
		} $values = get_field($fieldId, $productId);
		if (!is_array($values)) {
			return [];
		}

return array_map('strval', $values);
	}
}
