<?php

namespace ZenoChatbot\Services\Indexing\Engine;

use ZenoChatbot\Helper\Resources;
use ZenoChatbot\Helper\Settings;
use ZenoChatbot\Support\Hooks;

if (!defined('ABSPATH')) {
    exit;
}

class ProductContentIndexEngine
{
    private const DEFAULT_CHUNK_SIZE = 30;

    /**
     * @return array<int,string>
     */
    private static function allowedFields()
    {
        return [
            'title',
            'short_description',
            'description',
            'price',
            'sale_price',
            'sku',
            'stock_status',
            'categories',
            'tags',
            'attributes',
            'image_urls',
            'meta_data',
            'variations',
        ];
    }

    /**
     * @return string
     */
    private static function getFilePath()
    {
        return Resources::getBaseDirPath() . '/product-content-index.json';
    }

    /**
     * @return bool
     */
    private static function isWooCommerceAvailable()
    {
        return class_exists('WooCommerce') && function_exists('wc_get_product');
    }

    /**
     * @return array<int,array<string,mixed>>
     */
    public static function getAll()
    {
        $filePath = static::getFilePath();
        if (!file_exists($filePath)) {
            return [];
        }

        $content = file_get_contents($filePath);
        if ($content === false) {
            return [];
        }

        $decoded = json_decode($content, true);
        return is_array($decoded) ? $decoded : [];
    }

    /**
     * @param array<int,string> $fields
     * @param int|null $chunkSize
     * @return array<string,mixed>
     */
    public static function startBuildJob($fields = [], $chunkSize = null)
    {
        if (!static::isWooCommerceAvailable()) {
            return [
                'success' => false,
                'message' => 'WooCommerce is not active on this site.'
            ];
        }

        $normalizedFields = static::normalizeFields($fields);
        if (empty($normalizedFields)) {
            return [
                'success' => false,
                'message' => 'Please select at least one product data field.'
            ];
        }

        $chunkSize = (int) ($chunkSize ?? static::DEFAULT_CHUNK_SIZE);
        $chunkSize = max(1, $chunkSize);

        $productIds = static::getProductIds();
        if (empty($productIds)) {
            return [
                'success' => false,
                'message' => 'No published products found for indexing.'
            ];
        }

        $jobId = wp_generate_uuid4();
        $itemsFile = static::getJobItemsFilePath($jobId);
        if (file_exists($itemsFile)) {
            // Delete the items file if it exists
            @wp_delete_file($itemsFile);
        }

        $job = [
            'job_id' => $jobId,
            'status' => 'running',
            'fields' => $normalizedFields,
            'product_ids' => $productIds,
            'chunk_size' => $chunkSize,
            'offset' => 0,
            'total' => count($productIds),
            'indexed' => 0,
            'skipped' => 0,
            'failed' => 0,
            'created_at' => gmdate('c'),
            'updated_at' => gmdate('c'),
            'completed_at' => null,
            'summary' => null,
            'message' => 'Product indexing initialized.',
        ];

        static::writeJob($jobId, $job);

        return [
            'success' => true,
            'data' => static::formatJobData($job, true)
        ];
    }

    /**
     * @param string $jobId
     * @return array<string,mixed>
     */
    public static function processBuildJob($jobId)
    {
        $jobId = sanitize_text_field((string) $jobId);
        if ($jobId === '') {
            return [
                'success' => false,
                'message' => 'Invalid job id.'
            ];
        }

        $job = static::readJob($jobId);
        if (!is_array($job)) {
            return [
                'success' => false,
                'message' => 'Product indexing job not found.'
            ];
        }

        $status = (string) ($job['status'] ?? '');
        if ($status === 'completed' || $status === 'failed') {
            return [
                'success' => true,
                'data' => static::formatJobData($job, true)
            ];
        }

        $productIds = is_array($job['product_ids'] ?? null) ? $job['product_ids'] : [];
        $offset = max(0, (int) ($job['offset'] ?? 0));
        $chunkSize = max(1, (int) ($job['chunk_size'] ?? static::DEFAULT_CHUNK_SIZE));
        $fields = is_array($job['fields'] ?? null) ? static::normalizeFields($job['fields']) : [];

        $chunk = array_slice($productIds, $offset, $chunkSize);
        foreach ($chunk as $productId) {
            $item = static::buildProductItem((int) $productId, $fields);
            if ($item === null) {
                $job['skipped'] = ((int) ($job['skipped'] ?? 0)) + 1;
                continue;
            }

            if (!static::appendJobItem($jobId, $item)) {
                $job['failed'] = ((int) ($job['failed'] ?? 0)) + 1;
                continue;
            }

            $job['indexed'] = ((int) ($job['indexed'] ?? 0)) + 1;
        }

        $job['offset'] = $offset + count($chunk);
        $job['updated_at'] = gmdate('c');

        $total = max(0, (int) ($job['total'] ?? 0));
        if ((int) $job['offset'] >= $total) {
            $finalize = static::finalizeBuildJob($jobId, $job);
            if (!($finalize['success'] ?? false)) {
                $job['status'] = 'failed';
                $job['message'] = (string) ($finalize['message'] ?? 'Failed to finalize product index.');
                $job['completed_at'] = gmdate('c');
                static::writeJob($jobId, $job);

                return [
                    'success' => false,
                    'message' => $job['message'],
                    'data' => static::formatJobData($job, true)
                ];
            }

            $job = $finalize['job'];
        }

        static::writeJob($jobId, $job);

        return [
            'success' => true,
            'data' => static::formatJobData($job, true)
        ];
    }

    /**
     * @param string $jobId
     * @return array<string,mixed>
     */
    public static function getBuildJobStatus($jobId)
    {
        $jobId = sanitize_text_field((string) $jobId);
        if ($jobId === '') {
            return [
                'success' => false,
                'message' => 'Invalid job id.'
            ];
        }

        $job = static::readJob($jobId);
        if (!is_array($job)) {
            return [
                'success' => false,
                'message' => 'Product indexing job not found.'
            ];
        }

        return [
            'success' => true,
            'data' => static::formatJobData($job, true)
        ];
    }

    /**
     * @return array<string,mixed>
     */
    public static function clearIndex()
    {
        $currentCount = static::count();
        $saved = static::saveAll([]);

        if (!$saved) {
            return [
                'success' => false,
                'message' => 'Failed to clear product index.'
            ];
        }

        Settings::delete('product_index.last_indexed_at');
        Settings::delete('product_index.indexed_count');
        Settings::delete('product_index.selected_fields');

        static::emitUpdateHooks('clear');

        return [
            'success' => true,
            'message' => "Successfully cleared {$currentCount} product items.",
            'data' => [
                'cleared_count' => $currentCount
            ]
        ];
    }

    /**
     * @return array<string,mixed>
     */
    public static function getStats()
    {
        return [
            'total_count' => static::count(),
            'indexed_count' => (int) Settings::get('product_index.indexed_count', 0),
            'last_indexed_at' => Settings::get('product_index.last_indexed_at'),
            'selected_fields' => Settings::get('product_index.selected_fields', []),
            'woocommerce_available' => static::isWooCommerceAvailable(),
        ];
    }

    /**
     * @return int
     */
    public static function count()
    {
        return count(static::getAll());
    }

    /**
     * @param array<int,string> $fields
     * @return array<int,string>
     */
    private static function normalizeFields($fields)
    {
        $allowed = static::allowedFields();
        $normalized = [];

        foreach ((array) $fields as $field) {
            $key = sanitize_key((string) $field);
            if (in_array($key, $allowed, true)) {
                $normalized[] = $key;
            }
        }

        return array_values(array_unique($normalized));
    }

    /**
     * @return array<int,int>
     */
    private static function getProductIds()
    {
        $query = new \WP_Query([
            'post_type' => 'product',
            'post_status' => 'publish',
            'fields' => 'ids',
            'posts_per_page' => -1,
            'orderby' => 'ID',
            'order' => 'ASC',
            'no_found_rows' => true,
            'cache_results' => false,
            'update_post_meta_cache' => false,
            'update_post_term_cache' => false,
        ]);

        return array_map('intval', (array) $query->posts);
    }

    /**
     * @param int $productId
     * @param array<int,string> $fields
     * @return array<string,mixed>|null
     */
    private static function buildProductItem($productId, $fields)
    {
        $product = wc_get_product($productId);
        if (!$product) {
            return null;
        }

        $title = (string) $product->get_name();
        $link = get_permalink($productId);
        if ($title === '' || !is_string($link) || $link === '') {
            return null;
        }

        $payload = [];

        if (in_array('title', $fields, true)) {
            $payload['title'] = sanitize_text_field($title);
        }

        if (in_array('short_description', $fields, true)) {
            $payload['short_description'] = wp_strip_all_tags((string) $product->get_short_description());
        }

        if (in_array('description', $fields, true)) {
            $payload['description'] = wp_strip_all_tags((string) $product->get_description());
        }

        if (in_array('price', $fields, true)) {
            $payload['price'] = sanitize_text_field((string) $product->get_price());
        }

        if (in_array('sale_price', $fields, true)) {
            $payload['sale_price'] = sanitize_text_field((string) $product->get_sale_price());
        }

        if (in_array('sku', $fields, true)) {
            $payload['sku'] = sanitize_text_field((string) $product->get_sku());
        }

        if (in_array('stock_status', $fields, true)) {
            $payload['stock_status'] = sanitize_text_field((string) $product->get_stock_status());
        }

        if (in_array('categories', $fields, true)) {
            $payload['categories'] = array_values(array_filter(array_map('sanitize_text_field', wc_get_product_category_list($productId, '|') ? explode('|', wp_strip_all_tags(wc_get_product_category_list($productId, '|'))) : [])));
        }

        if (in_array('tags', $fields, true)) {
            $payload['tags'] = array_values(array_filter(array_map('sanitize_text_field', wc_get_product_tag_list($productId, '|') ? explode('|', wp_strip_all_tags(wc_get_product_tag_list($productId, '|'))) : [])));
        }

        if (in_array('attributes', $fields, true)) {
            $payload['attributes'] = static::normalizeAttributes($product->get_attributes());
        }

        if (in_array('image_urls', $fields, true)) {
            $payload['image_urls'] = static::collectProductImageUrls($product);
        }

        $includeMeta = in_array('meta_data', $fields, true);
        $includeVariations = in_array('variations', $fields, true);

        $rawMeta = $includeMeta ? static::collectRawMeta($productId) : [];
        $variations = $includeVariations ? static::collectVariations($product, $includeMeta) : [];

        $context = static::buildContext($payload, $variations, $rawMeta);

        return [
            'title' => sanitize_text_field($title),
            'link' => esc_url_raw($link),
            'context' => $context,
            'product_data' => $payload,
            'variations' => $variations,
            'meta' => [
                'raw_meta' => $rawMeta,
            ],
        ];
    }

    /**
     * @param array<int,mixed> $attributes
     * @return array<int,array<string,mixed>>
     */
    private static function normalizeAttributes($attributes)
    {
        $normalized = [];

        foreach ($attributes as $attribute) {
            if (!$attribute) {
                continue;
            }

            $name = method_exists($attribute, 'get_name') ? (string) $attribute->get_name() : '';
            $options = method_exists($attribute, 'get_options') ? (array) $attribute->get_options() : [];

            $normalized[] = [
                'name' => sanitize_text_field($name),
                'values' => array_map('sanitize_text_field', array_map('strval', $options)),
            ];
        }

        return $normalized;
    }

    /**
     * @param \WC_Product $product
     * @return array<int,string>
     */
    private static function collectProductImageUrls($product)
    {
        $images = [];

        $mainId = $product->get_image_id();
        if ($mainId) {
            $url = wp_get_attachment_url($mainId);
            if (is_string($url) && $url !== '') {
                $images[] = esc_url_raw($url);
            }
        }

        foreach ($product->get_gallery_image_ids() as $imageId) {
            $url = wp_get_attachment_url((int) $imageId);
            if (is_string($url) && $url !== '') {
                $images[] = esc_url_raw($url);
            }
        }

        return array_values(array_unique($images));
    }

    /**
     * @param \WC_Product $product
     * @param bool $includeMeta
     * @return array<int,array<string,mixed>>
     */
    private static function collectVariations($product, $includeMeta)
    {
        if (!$product->is_type('variable')) {
            return [];
        }

        $parentUrl = get_permalink((int) $product->get_id());
        if (!is_string($parentUrl) || $parentUrl === '') {
            $parentUrl = '';
        }

        $variations = [];
        foreach ($product->get_children() as $variationId) {
            $variation = wc_get_product((int) $variationId);
            if (!$variation || !($variation instanceof \WC_Product_Variation)) {
                continue;
            }

            $attrs = [];
            foreach ((array) $variation->get_attributes() as $attrKey => $attrValue) {
                $key = sanitize_text_field((string) $attrKey);
                $value = sanitize_text_field((string) $attrValue);
                if ($key !== '' && $value !== '') {
                    $attrs[$key] = $value;
                }
            }

            $url = $parentUrl;
            if ($url !== '' && !empty($attrs)) {
                $params = [];
                foreach ($attrs as $key => $value) {
                    $paramKey = str_starts_with($key, 'attribute_') ? $key : ('attribute_' . $key);
                    $params[$paramKey] = $value;
                }
                $url = add_query_arg($params, $url);
            }

            $variationItem = [
                'variation_id' => (int) $variation->get_id(),
                'url' => esc_url_raw((string) $url),
                'attributes' => $attrs,
                'price' => sanitize_text_field((string) $variation->get_price()),
                'sale_price' => sanitize_text_field((string) $variation->get_sale_price()),
                'stock_status' => sanitize_text_field((string) $variation->get_stock_status()),
                'stock_quantity' => $variation->get_stock_quantity() === null ? null : (int) $variation->get_stock_quantity(),
                'sku' => sanitize_text_field((string) $variation->get_sku()),
                'is_in_stock' => (bool) $variation->is_in_stock(),
                'is_purchasable' => (bool) $variation->is_purchasable(),
                'meta' => [
                    'raw_meta' => $includeMeta ? static::collectRawMeta((int) $variation->get_id()) : [],
                ],
            ];

            $variations[] = $variationItem;
        }

        return $variations;
    }

    /**
     * @param int $postId
     * @return array<string,mixed>
     */
    private static function collectRawMeta($postId)
    {
        $allMeta = get_post_meta((int) $postId);
        if (!is_array($allMeta)) {
            return [];
        }

        $result = [];
        foreach ($allMeta as $metaKey => $values) {
            $key = sanitize_text_field((string) $metaKey);
            if ($key === '') {
                continue;
            }

            if (!is_array($values)) {
                $values = [$values];
            }

            $normalized = [];
            foreach ($values as $value) {
                $clean = static::sanitizeMetaValue($value);
                if ($clean !== null && $clean !== '' && $clean !== []) {
                    $normalized[] = $clean;
                }
            }

            if (empty($normalized)) {
                continue;
            }

            $result[$key] = count($normalized) === 1 ? $normalized[0] : $normalized;
        }

        return $result;
    }

    /**
     * @param mixed $value
     * @param int $depth
     * @return mixed
     */
    private static function sanitizeMetaValue($value, $depth = 0)
    {
        if ($depth > 3) {
            return null;
        }

        if (is_string($value)) {
            $maybe = maybe_unserialize($value);
            if ($maybe !== $value) {
                return static::sanitizeMetaValue($maybe, $depth + 1);
            }

            return sanitize_text_field($value);
        }

        if (is_int($value) || is_float($value) || is_bool($value)) {
            return $value;
        }

        if (is_array($value)) {
            $clean = [];
            foreach ($value as $k => $v) {
                $nested = static::sanitizeMetaValue($v, $depth + 1);
                if ($nested === null || $nested === '' || $nested === []) {
                    continue;
                }

                if (is_string($k)) {
                    $clean[sanitize_text_field($k)] = $nested;
                } else {
                    $clean[] = $nested;
                }
            }

            return $clean;
        }

        return null;
    }

    /**
     * @param array<string,mixed> $payload
     * @param array<int,array<string,mixed>> $variations
     * @param array<string,mixed> $rawMeta
     * @return string
     */
    private static function buildContext($payload, $variations, $rawMeta)
    {
        $lines = [];

        foreach ($payload as $key => $value) {
            $text = static::toContextLineValue($value);
            if ($text === '') {
                continue;
            }

            $lines[] = ucfirst(str_replace('_', ' ', (string) $key)) . ': ' . $text;
        }

        if (!empty($variations)) {
            $lines[] = 'Variations count: ' . count($variations);

            $preview = array_slice($variations, 0, 5);
            foreach ($preview as $index => $variation) {
                $attrs = static::toContextLineValue($variation['attributes'] ?? []);
                $stock = static::toContextLineValue($variation['stock_status'] ?? '');
                $price = static::toContextLineValue($variation['price'] ?? '');
                $qty = static::toContextLineValue($variation['stock_quantity'] ?? '');

                $lines[] = 'Variation ' . ($index + 1) . ': attrs=' . $attrs . ', stock=' . $stock . ', qty=' . $qty . ', price=' . $price;
            }
        }

        if (!empty($rawMeta)) {
            $metaText = static::toContextLineValue($rawMeta);
            if ($metaText !== '') {
                $lines[] = 'Meta: ' . $metaText;
            }
        }

        return sanitize_textarea_field(implode("\n", $lines));
    }

    /**
     * @param mixed $value
     * @return string
     */
    private static function toContextLineValue($value)
    {
        if (is_scalar($value)) {
            return trim((string) $value);
        }

        if (is_array($value)) {
            $json = wp_json_encode($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
            return is_string($json) ? $json : '';
        }

        return '';
    }

    /**
     * @param string $jobId
     * @param array<string,mixed> $job
     * @return array<string,mixed>
     */
    private static function finalizeBuildJob($jobId, $job)
    {
        $items = static::readJobItems($jobId);
        if (!static::saveAll($items)) {
            return [
                'success' => false,
                'message' => 'Failed to save product content index.'
            ];
        }

        $selectedFields = is_array($job['fields'] ?? null) ? $job['fields'] : [];

        Settings::update('product_index.last_indexed_at', gmdate('Y-m-d H:i:s'));
        Settings::update('product_index.indexed_count', (int) ($job['indexed'] ?? 0));
        Settings::update('product_index.selected_fields', $selectedFields);

        static::emitUpdateHooks('build');

        $job['status'] = 'completed';
        $job['completed_at'] = gmdate('c');
        $job['message'] = 'Product indexing completed successfully.';
        $job['summary'] = [
            'total' => (int) ($job['total'] ?? 0),
            'indexed' => (int) ($job['indexed'] ?? 0),
            'skipped' => (int) ($job['skipped'] ?? 0),
            'failed' => (int) ($job['failed'] ?? 0),
            'selected_fields' => $selectedFields,
        ];

        return [
            'success' => true,
            'job' => $job
        ];
    }

    /**
     * @param string $jobId
     * @param array<string,mixed> $item
     * @return bool
     */
    private static function appendJobItem($jobId, $item)
    {
        $line = wp_json_encode($item, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        if (!is_string($line)) {
            return false;
        }

        return file_put_contents(static::getJobItemsFilePath($jobId), $line . "\n", FILE_APPEND | LOCK_EX) !== false;
    }

    /**
     * @param string $jobId
     * @return array<int,array<string,mixed>>
     */
    private static function readJobItems($jobId)
    {
        $path = static::getJobItemsFilePath($jobId);
        if (!file_exists($path)) {
            return [];
        }

        $lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        if (!is_array($lines)) {
            return [];
        }

        $items = [];
        foreach ($lines as $line) {
            $decoded = json_decode((string) $line, true);
            if (is_array($decoded)) {
                $items[] = $decoded;
            }
        }

        return $items;
    }

    /**
     * @param array<int,array<string,mixed>> $content
     * @return bool
     */
    private static function saveAll($content)
    {
        $encoded = wp_json_encode($content, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        if (!is_string($encoded)) {
            return false;
        }

        return file_put_contents(static::getFilePath(), $encoded, LOCK_EX) !== false;
    }

    /**
     * @param string $jobId
     * @param array<string,mixed> $job
     * @return bool
     */
    private static function writeJob($jobId, $job)
    {
        $encoded = wp_json_encode($job, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        if (!is_string($encoded)) {
            return false;
        }

        return file_put_contents(static::getJobFilePath($jobId), $encoded, LOCK_EX) !== false;
    }

    /**
     * @param string $jobId
     * @return array<string,mixed>|null
     */
    private static function readJob($jobId)
    {
        $path = static::getJobFilePath($jobId);
        if (!file_exists($path)) {
            return null;
        }

        $content = file_get_contents($path);
        if ($content === false) {
            return null;
        }

        $decoded = json_decode($content, true);
        return is_array($decoded) ? $decoded : null;
    }

    /**
     * @param string $jobId
     * @return string
     */
    private static function getJobFilePath($jobId)
    {
        return Resources::getCacheDirPath() . '/product-build-' . sanitize_file_name((string) $jobId) . '.json';
    }

    /**
     * @param string $jobId
     * @return string
     */
    private static function getJobItemsFilePath($jobId)
    {
        return Resources::getCacheDirPath() . '/product-build-' . sanitize_file_name((string) $jobId) . '.items.jsonl';
    }

    /**
     * @param array<string,mixed> $job
     * @param bool $includeSummary
     * @return array<string,mixed>
     */
    private static function formatJobData($job, $includeSummary = false)
    {
        $total = max(0, (int) ($job['total'] ?? 0));
        $offset = max(0, (int) ($job['offset'] ?? 0));
        $progress = $total > 0 ? min(100, (int) floor(($offset / $total) * 100)) : 0;

        $data = [
            'job_id' => (string) ($job['job_id'] ?? ''),
            'status' => (string) ($job['status'] ?? 'running'),
            'message' => (string) ($job['message'] ?? ''),
            'fields' => is_array($job['fields'] ?? null) ? $job['fields'] : [],
            'total' => $total,
            'processed' => $offset,
            'indexed' => (int) ($job['indexed'] ?? 0),
            'skipped' => (int) ($job['skipped'] ?? 0),
            'failed' => (int) ($job['failed'] ?? 0),
            'progress_percent' => $progress,
            'created_at' => (string) ($job['created_at'] ?? ''),
            'updated_at' => (string) ($job['updated_at'] ?? ''),
            'completed_at' => (string) ($job['completed_at'] ?? ''),
        ];

        if ($includeSummary) {
            $data['summary'] = is_array($job['summary'] ?? null) ? $job['summary'] : null;
        }

        return $data;
    }

    /**
     * @param string $operation
     * @return void
     */
    private static function emitUpdateHooks($operation)
    {
        $context = [
            'source' => 'product',
            'operation' => sanitize_text_field((string) $operation),
            'timestamp' => gmdate('c'),
        ];

        Hooks::doAction(Hooks::INDEX_PRODUCT_UPDATED, $context);
    }
}
