<?php

namespace App\Modules\Stock\ImportMapSources;

use App\Contracts\ImportMapSource;
use App\Models\ImportMap;
use App\Models\ImportMapField;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Http;

class HttpJsonSource implements ImportMapSource
{
    public function name(): string
    {
        return 'http_json';
    }

    public function fetch(ImportMap $map): iterable
    {
        $config = $map->data['http'] ?? [];
        $url = $config['url'] ?? null;

        if (empty($url)) {
            throw new \InvalidArgumentException(
                "Import map [{$map->id}] is configured as http_json but has no URL.",
            );
        }

        $response = Http::withHeaders($this->parseHeaders($config['headers'] ?? []))
            ->acceptJson()
            ->get($url);

        $response->throw();

        $rows = $this->extractRows($response->json(), $config['array_path'] ?? null);

        $latestBy = $config['latest_by'] ?? null;
        if ($latestBy !== null && $latestBy !== '') {
            $rows = $this->keepLatestPerMatchKey($rows, $map, $latestBy);
        }

        yield from $rows;
    }

    /**
     * Accept either an associative [key => value] map or a list of
     * [{key, value}] objects (the shape used by the admin UI form).
     *
     * @param array<int|string, mixed> $headers
     * @return array<string, string>
     */
    private function parseHeaders(array $headers): array
    {
        if (empty($headers)) {
            return [];
        }

        if (!array_is_list($headers)) {
            return array_map(static fn ($value) => (string) $value, $headers);
        }

        return collect($headers)
            ->filter(fn ($entry) => is_array($entry) && !empty($entry['key']))
            ->mapWithKeys(fn ($entry) => [(string) $entry['key'] => (string) ($entry['value'] ?? '')])
            ->all();
    }

    private function extractRows(mixed $payload, ?string $arrayPath): array
    {
        if (!is_array($payload)) {
            return [];
        }

        $rows = empty($arrayPath) ? $payload : Arr::get($payload, $arrayPath);

        if (!is_array($rows)) {
            return [];
        }

        return array_values(array_filter($rows, 'is_array'));
    }

    /**
     * Group rows by their match-key field value(s), sort each group descending
     * by $latestBy, keep only the first row per group.
     *
     * Match-key fields come from ImportMapField rows flagged is_match_key.
     *
     * @param array<int, array<string, mixed>> $rows
     * @return array<int, array<string, mixed>>
     */
    private function keepLatestPerMatchKey(array $rows, ImportMap $map, string $latestBy): array
    {
        $matchFields = $map->fields
            ->filter(fn (ImportMapField $field) => !empty($field->is_match_key))
            ->pluck('on_file')
            ->filter()
            ->values();

        if ($matchFields->isEmpty()) {
            return $rows;
        }

        return (new Collection($rows))
            ->groupBy(fn (array $row) => $matchFields
                ->map(fn (string $key) => (string) ($row[$key] ?? ''))
                ->implode('|'))
            ->map(fn (Collection $group) => $group
                ->sortByDesc(fn (array $row) => $row[$latestBy] ?? null)
                ->first())
            ->values()
            ->all();
    }
}
