<?php

declare(strict_types=1);

namespace App\Services\DataForSeo;

use Illuminate\Support\Arr;
use Illuminate\Support\Str;

class DataForSeoResultNormalizer
{
    public function __construct(private readonly UrlNormalizer $urlNormalizer) {}

    /**
     * @param  array<string, mixed>  $response
     * @return list<NormalizedResult>
     */
    public function normalizeTaskResponse(array $response): array
    {
        $items = $this->extractItems($response);
        $results = [];

        foreach ($items as $item) {
            if (! is_array($item)) {
                continue;
            }

            // Skip carousel wrappers — they group products but don't represent individual price results.
            $type = $item['type'] ?? '';
            if ($type === 'google_shopping_carousel') {
                continue;
            }

            $title = $this->pickFirstString($item, ['title', 'product_title', 'name']);

            // Minimum viable item: must have at least a title.
            if ($title === null) {
                continue;
            }

            // Direct store URL (preferred for matching). May be null in Merchant API.
            $directUrl = $this->pickFirstString($item, ['url', 'product_url', 'click_url', 'url_to_product', 'store_url']);

            // Google Shopping proxy URL — stored for auditing, but never used for domain inference.
            $shoppingUrl = $this->pickFirstString($item, ['shopping_url']);

            // Stored URL: prefer direct, fall back to Google proxy for audit trail.
            $storedUrl = $directUrl ?? $shoppingUrl;

            // Domain: only extracted from the direct store URL, never from the Google proxy.
            $domain = $this->pickFirstString($item, ['domain']) ?? $this->urlNormalizer->extractDomain($directUrl);

            // rank_absolute is the primary ranking field; rank_group is secondary.
            $rankAbsolute = $this->extractInteger($item, ['rank_absolute']);
            $rankGroup = $this->extractInteger($item, ['rank_group']);

            // position (legacy): mapped from rank_absolute for backwards-compatibility.
            $position = $rankAbsolute;

            $results[] = new NormalizedResult(
                title: $title,
                seller: $this->pickFirstString($item, ['seller', 'shop_name', 'merchant_name', 'store_name']),
                domain: $domain,
                directUrl: $directUrl,
                shoppingUrl: $shoppingUrl,
                url: $storedUrl,
                normalizedUrl: $this->urlNormalizer->normalize($directUrl),
                imageUrl: $this->pickFirstString($item, ['image_url', 'thumbnail', 'image', 'thumbnail_url'])
                    ?? $this->extractFirstArrayString($item, 'product_images'),
                price: $this->extractPrice($item, ['price', 'price_from', 'current_price', 'amount']),
                previousPrice: $this->extractPrice($item, ['previous_price', 'price_old', 'old_price', 'regular_price']),
                currency: $this->pickFirstString($item, ['currency', 'price_currency']),
                position: $position,
                rankGroup: $rankGroup,
                rankAbsolute: $rankAbsolute,
                dataDocid: $this->pickFirstString($item, ['data_docid']),
                productId: $this->pickFirstString($item, ['product_id']),
                gid: $this->pickFirstString($item, ['gid']),
                resultType: $type !== '' ? $type : $this->pickFirstString($item, ['result_type']),
                rawPayload: $item,
            );
        }

        return $results;
    }

    /**
     * @param  array<string, mixed>  $response
     * @return list<array<string, mixed>>
     */
    private function extractItems(array $response): array
    {
        $paths = [
            'tasks.0.result.0.items',
            'tasks.0.result.items',
            'result.0.items',
            'result.items',
            'items',
        ];

        foreach ($paths as $path) {
            $items = Arr::get($response, $path);

            if (is_array($items)) {
                return array_values(array_filter($items, 'is_array'));
            }
        }

        $flattenedItems = [];
        $taskResults = Arr::get($response, 'tasks.0.result');

        if (is_array($taskResults)) {
            foreach ($taskResults as $taskResult) {
                if (is_array($taskResult) && isset($taskResult['items']) && is_array($taskResult['items'])) {
                    foreach ($taskResult['items'] as $item) {
                        if (is_array($item)) {
                            $flattenedItems[] = $item;
                        }
                    }
                }
            }
        }

        return $flattenedItems;
    }

    /**
     * @param  array<string, mixed>  $payload
     * @param  list<string>  $keys
     */
    private function pickFirstString(array $payload, array $keys): ?string
    {
        foreach ($keys as $key) {
            $value = Arr::get($payload, $key);

            if (is_string($value) && trim($value) !== '') {
                return trim($value);
            }
        }

        return null;
    }

    /**
     * @param  array<string, mixed>  $payload
     * @param  list<string>  $keys
     */
    private function extractPrice(array $payload, array $keys): ?float
    {
        foreach ($keys as $key) {
            $value = Arr::get($payload, $key);

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

            if (! is_string($value) || trim($value) === '') {
                continue;
            }

            $normalizedValue = Str::of($value)
                ->replaceMatches('/[^\d,.-]/', '')
                ->replace(',', '')
                ->value();

            if ($normalizedValue === '' || ! is_numeric($normalizedValue)) {
                continue;
            }

            return (float) $normalizedValue;
        }

        return null;
    }

    /**
     * @param  array<string, mixed>  $payload
     * @param  list<string>  $keys
     */
    private function extractInteger(array $payload, array $keys): ?int
    {
        foreach ($keys as $key) {
            $value = Arr::get($payload, $key);

            if (is_int($value)) {
                return $value;
            }

            if (is_string($value) && is_numeric($value)) {
                return (int) $value;
            }
        }

        return null;
    }

    /**
     * Returns the first non-empty string from a nested array field (e.g., product_images[0]).
     *
     * @param  array<string, mixed>  $payload
     */
    private function extractFirstArrayString(array $payload, string $key): ?string
    {
        $value = $payload[$key] ?? null;

        if (! is_array($value)) {
            return null;
        }

        foreach ($value as $item) {
            if (is_string($item) && trim($item) !== '') {
                return trim($item);
            }
        }

        return null;
    }
}
