<?php

namespace App\Serp\Providers\DataForSeo;

use App\Serp\Data\SerpResponse;
use App\Serp\Data\SerpResultData;
use App\Serp\Data\SerpSubmission;
use App\Serp\Data\SerpTaskResult;
use App\Serp\Exceptions\UnexpectedProviderResponseException;

final class DataForSeoResponseNormalizer
{
    public function __construct(private DataForSeoErrorClassifier $errors) {}

    public function live(array $data, int $limit): SerpResponse
    {
        $task = $this->task($data, [20000]);

        return $this->response($task, $limit);
    }

    public function submission(array $data): SerpSubmission
    {
        $task = $this->task($data, [20000, 20100]);
        $id = $task['id'] ?? null;
        if (! is_string($id) || ! preg_match('/^[0-9a-f-]{36}$/i', $id)) {
            throw new UnexpectedProviderResponseException('DataForSEO task id is missing.');
        }

        return new SerpSubmission('dataforseo', $id, $this->cost($task, $data), 'USD', (int) $task['status_code']);
    }

    public function collection(array $data, string $taskId, int $limit): SerpTaskResult
    {
        $this->envelope($data);
        $tasks = $data['tasks'];
        if ($tasks === [] || ! isset($tasks[0]) || ! is_array($tasks[0])) {
            return SerpTaskResult::waiting();
        }
        $task = $tasks[0];
        if (($task['id'] ?? null) !== $taskId) {
            throw new UnexpectedProviderResponseException('DataForSEO task id mismatch.');
        }
        $status = $task['status_code'] ?? null;
        if (! is_int($status)) {
            throw new UnexpectedProviderResponseException('DataForSEO task status is missing.');
        }
        if (in_array($status, [20100, 40601, 40602], true) || ! array_key_exists('result', $task) || $task['result'] === null) {
            return SerpTaskResult::waiting($status);
        }
        if ($status !== 20000) {
            throw $this->errors->api($status);
        }
        $response = $this->response($task, $limit);

        return SerpTaskResult::ready($response, $this->cost($task, $data), 'USD', $status);
    }

    private function envelope(array $data): void
    {
        $status = $data['status_code'] ?? null;
        if (! is_int($status)) {
            throw new UnexpectedProviderResponseException('DataForSEO envelope status is missing.');
        }if ($status !== 20000) {
            throw $this->errors->api($status);
        }if (! isset($data['tasks']) || ! is_array($data['tasks'])) {
            throw new UnexpectedProviderResponseException('DataForSEO tasks are missing.');
        }
    }

    private function task(array $data, array $accepted): array
    {
        $this->envelope($data);
        $task = $data['tasks'][0] ?? null;
        if (! is_array($task) || ! is_int($task['status_code'] ?? null)) {
            throw new UnexpectedProviderResponseException('DataForSEO task is malformed.');
        }if (! in_array($task['status_code'], $accepted, true)) {
            throw $this->errors->api($task['status_code']);
        }

        return $task;
    }

    private function response(array $task, int $limit): SerpResponse
    {
        $result = $task['result'] ?? null;
        if (! is_array($result) || ! isset($result[0]) || ! is_array($result[0])) {
            throw new UnexpectedProviderResponseException('DataForSEO result is missing.');
        }
        $items = $result[0]['items'] ?? null;
        if (! is_array($items)) {
            throw new UnexpectedProviderResponseException('DataForSEO items are missing.');
        }
        $rows = [];
        $seen = [];
        foreach ($items as $item) {
            if (! is_array($item) || ($item['type'] ?? null) !== 'organic') {
                continue;
            }$position = $item['rank_group'] ?? null;
            $url = $item['url'] ?? null;
            if (! is_int($position) || $position < 1 || $position > $limit || ! is_string($url) || isset($seen[$position])) {
                continue;
            }try {
                $rows[] = new SerpResultData($position, $url, $this->text($item['title'] ?? null, 500), $this->text($item['breadcrumb'] ?? null, 500), $this->text($item['description'] ?? null, 5000));
                $seen[$position] = true;
            } catch (\InvalidArgumentException) {
            }
        }
        usort($rows, fn ($a, $b) => $a->position <=> $b->position);

        return new SerpResponse('dataforseo', $rows, is_string($task['id'] ?? null) ? $task['id'] : null, $limit, $this->cost($task, []), 'USD', is_int($task['status_code'] ?? null) ? $task['status_code'] : null);
    }

    private function text(mixed $value, int $max): ?string
    {
        if ($value === null) {
            return null;
        }if (! is_string($value)) {
            return null;
        }

        return mb_substr($value, 0, $max);
    }

    private function cost(array $task, array $data): ?float
    {
        $cost = $task['cost'] ?? $data['cost'] ?? null;

        return is_int($cost) || is_float($cost) ? (float) $cost : null;
    }
}
