<?php

namespace App\Services\Jobs;

use App\Models\ImportedJob;
use App\Models\JobSource;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use RuntimeException;

class JsonFeedJobImporter
{
    public function __construct(private readonly JobPayloadMapper $mapper) {}

    /**
     * @return array{imported: int, expired: int}
     */
    public function import(JobSource $source): array
    {
        if (! filled($source->feed_url)) {
            throw new RuntimeException('JSON/API job source endpoint URL is not configured.');
        }

        $response = $this->request($source);

        if (! $response->successful()) {
            throw new RuntimeException("JSON/API source request failed with HTTP {$response->status()}.");
        }

        $payload = $response->json();

        if (! is_array($payload)) {
            throw new RuntimeException('JSON/API source response did not contain a valid JSON object or array.');
        }

        $items = $this->itemsFromPayload($source, $payload);

        if ($items === []) {
            throw new RuntimeException('JSON/API source did not contain any job items.');
        }

        $seenExternalIds = [];
        $imported = 0;

        DB::transaction(function () use ($source, $items, &$seenExternalIds, &$imported): void {
            foreach ($items as $item) {
                if (! is_array($item)) {
                    continue;
                }

                $attributes = $this->mapper->attributesFor($source, $item);

                if ($attributes === null) {
                    continue;
                }

                $seenExternalIds[] = $attributes['external_id'];

                ImportedJob::query()->updateOrCreate(
                    [
                        'job_source_id' => $source->id,
                        'external_id' => $attributes['external_id'],
                    ],
                    Arr::except($attributes, ['external_id'])
                );

                $imported++;
            }
        });

        return [
            'imported' => $imported,
            'expired' => $this->mapper->expireMissingJobs($source, $seenExternalIds),
        ];
    }

    private function request(JobSource $source): Response
    {
        $request = Http::timeout((int) data_get($source->sync_settings, 'timeout', 20))
            ->acceptJson()
            ->withHeaders($source->request_headers ?: []);

        if ($source->auth_type === JobSource::AUTH_BEARER && filled($source->auth_secret)) {
            $request = $request->withToken((string) $source->auth_secret);
        }

        if ($source->auth_type === JobSource::AUTH_BASIC && filled($source->auth_username) && filled($source->auth_secret)) {
            $request = $request->withBasicAuth((string) $source->auth_username, (string) $source->auth_secret);
        }

        if ($source->auth_type === JobSource::AUTH_API_KEY_HEADER && filled($source->auth_header_name) && filled($source->auth_secret)) {
            $request = $request->withHeader((string) $source->auth_header_name, (string) $source->auth_secret);
        }

        $method = strtolower((string) ($source->request_method ?: 'get'));
        $parameters = $source->query_parameters ?: [];

        return $method === 'post'
            ? $request->post($source->feed_url, $parameters)
            : $request->get($source->feed_url, $parameters);
    }

    /**
     * @param  array<string, mixed>  $payload
     * @return array<int, mixed>
     */
    private function itemsFromPayload(JobSource $source, array $payload): array
    {
        $itemsPath = data_get($source->sync_settings, 'items_path', 'jobs');
        $items = is_string($itemsPath) && $itemsPath !== ''
            ? data_get($payload, $itemsPath)
            : $payload;

        if (! is_array($items) && array_is_list($payload)) {
            $items = $payload;
        }

        return is_array($items) ? array_values($items) : [];
    }
}
