<?php

namespace App\Services;

use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Http;
use RuntimeException;

class N8nOffice
{
    public function workState(string $workflowId): array
    {
        [$client, $url] = $this->employeeClient($workflowId);
        $limited = false;
        // Check in-flight runs independently: a newer completed run must not hide them.
        foreach (['running', 'waiting', null] as $filter) {
            $query = ['workflowId' => $workflowId, 'limit' => 1, 'includeData' => 'false'];
            if ($filter !== null) {
                $query['status'] = $filter;
            }
            $response = $client->get($url.'/api/v1/executions', $query);
            // Older n8n versions reject the running filter. Keep the office usable,
            // but report that the snapshot is based on the executions they expose.
            if ($filter === 'running' && $response->status() === 400) {
                $limited = true;
                continue;
            }
            $rows = $response->json('data');
            if (!$response->successful() || !is_array($rows) || !array_is_list($rows)) {
                throw new RuntimeException('Status pekerjaan n8n belum tersedia. Periksa izin executions.');
            }
            if (!$rows) {
                continue;
            }
            $row = $rows[0];
            if (!is_array($row) || (string) ($row['workflowId'] ?? '') !== $workflowId || !isset($row['id'])) {
                throw new RuntimeException('Respons status pekerjaan tidak valid.');
            }
            $status = $row['status'] ?? $filter;
            $state = match ($status) {
                'running', 'new' => 'working',
                'waiting' => 'waiting',
                'error', 'crashed' => 'error',
                'success' => 'success',
                'canceled' => 'idle',
                default => null,
            };
            return ['workState' => $state, 'executionId' => (string) $row['id'], 'limited' => $limited];
        }
        return ['workState' => 'idle', 'executionId' => null, 'limited' => $limited];
    }

    private function employeeClient(string $workflowId): array
    {
        $url = rtrim((string) config('services.n8n.url'), '/');
        $key = (string) config('services.n8n.api_key');
        if (!filter_var($url, FILTER_VALIDATE_URL) || !in_array(parse_url($url, PHP_URL_SCHEME), ['http', 'https']) || $key === '') {
            throw new RuntimeException('Lengkapi N8N_URL dan N8N_API_KEY pada konfigurasi server.');
        }
        $client = Http::acceptJson()->withHeaders(['X-N8N-API-KEY' => $key])
            ->connectTimeout(5)->timeout(15)->withoutRedirecting();
        $workflow = $client->get($url.'/api/v1/workflows/'.rawurlencode($workflowId));
        abort_if($workflow->status() === 404, 404, 'Karyawan tidak ditemukan.');
        if (!$workflow->successful()) {
            throw new RuntimeException('Tidak dapat membaca workflow n8n. Periksa koneksi dan izin API key.');
        }
        abort_unless(collect($workflow->json('tags', []))->pluck('name')
            ->contains(config('services.n8n.employee_tag', 'employee')), 404, 'Karyawan tidak ditemukan.');

        return [$client, $url];
    }

    public function executions(string $workflowId, ?string $cursor = null): array
    {
        [$client, $url] = $this->employeeClient($workflowId);
        $query = ['workflowId' => $workflowId, 'limit' => 20, 'includeData' => 'false'];
        if ($cursor !== null) {
            $query['cursor'] = $cursor;
        }
        $response = $client->get($url.'/api/v1/executions', $query);
        if (!$response->successful()) {
            throw new RuntimeException(in_array($response->status(), [401, 403])
                ? 'Akses log ditolak. API key n8n memerlukan izin membaca executions.'
                : 'Log n8n belum dapat dimuat. Silakan coba lagi.');
        }
        $data = $response->json('data');
        $next = $response->json('nextCursor');
        if (!is_array($data) || !array_is_list($data) || ($next !== null && !is_string($next))) {
            throw new RuntimeException('Respons log n8n tidak valid.');
        }
        $logs = collect($data)->filter(fn ($row) => is_array($row)
            && isset($row['id'], $row['workflowId']) && (string) $row['workflowId'] === $workflowId)
            ->map(fn ($row) => [
                'id' => (string) $row['id'],
                'status' => is_string($row['status'] ?? null) ? $row['status'] : 'unknown',
                'startedAt' => is_string($row['startedAt'] ?? null) ? $row['startedAt'] : null,
                'stoppedAt' => is_string($row['stoppedAt'] ?? null) ? $row['stoppedAt'] : null,
                'url' => $url.'/workflow/'.rawurlencode($workflowId).'/executions/'.rawurlencode((string) $row['id']),
            ])->values()->all();

        return ['data' => $logs, 'nextCursor' => $next ?: null];
    }

    public function executionError(string $workflowId, string $executionId): array
    {
        [$client, $url] = $this->employeeClient($workflowId);
        $response = $client->get($url.'/api/v1/executions/'.rawurlencode($executionId), ['includeData' => 'true']);
        abort_if($response->status() === 404, 404, 'Eksekusi tidak ditemukan atau sudah dihapus.');
        if (!$response->successful()) {
            throw new RuntimeException('Detail eksekusi tidak dapat dimuat. Periksa koneksi dan izin membaca executions.');
        }
        $execution = $response->json();
        if (!is_array($execution) || !isset($execution['workflowId'], $execution['id'])) {
            throw new RuntimeException('Respons detail eksekusi tidak valid.');
        }
        abort_unless((string) $execution['workflowId'] === $workflowId && (string) $execution['id'] === $executionId, 404, 'Eksekusi tidak ditemukan untuk karyawan ini.');
        $result = data_get($execution, 'data.resultData', []);
        $error = data_get($result, 'error');
        $node = data_get($error, 'node.name');
        if (!is_array($error)) {
            foreach ((array) data_get($result, 'runData', []) as $nodeName => $runs) {
                foreach ((array) $runs as $run) {
                    if (is_array(data_get($run, 'error'))) {
                        $error = $run['error'];
                        $node = $nodeName;
                    }
                }
            }
        }
        $node = $node ?? data_get($result, 'lastNodeExecuted');
        $message = data_get($error, 'message');
        $description = data_get($error, 'description');

        return [
            'id' => $executionId,
            'node' => is_array($error) && is_string($node) ? $node : null,
            'message' => is_string($message) ? $message : null,
            'description' => is_string($description) ? $description : null,
        ];
    }

    public function employees(): Collection
    {
        $url = rtrim((string) config('services.n8n.url'), '/');
        $key = (string) config('services.n8n.api_key');
        if (!filter_var($url, FILTER_VALIDATE_URL) || !in_array(parse_url($url, PHP_URL_SCHEME), ['http', 'https']) || $key === '') {
            throw new RuntimeException('Lengkapi N8N_URL dan N8N_API_KEY pada konfigurasi server.');
        }

        $employees = collect();
        $cursor = null;
        $seen = [];
        do {
            $query = ['limit' => 100];
            if ($cursor !== null) {
                $query['cursor'] = $cursor;
            }
            $response = Http::acceptJson()->withHeaders(['X-N8N-API-KEY' => $key])
                ->connectTimeout(5)->timeout(15)->withoutRedirecting()
                ->get($url.'/api/v1/workflows', $query);

            if (!$response->successful()) {
                throw new RuntimeException(in_array($response->status(), [401, 403])
                    ? 'Akses n8n ditolak. Periksa API key dan izin membaca workflow.'
                    : 'n8n belum dapat dihubungi. Silakan coba lagi nanti.');
            }
            $data = $response->json('data');
            if (!is_array($data) || !array_is_list($data)) {
                throw new RuntimeException('Respons n8n tidak valid. Periksa URL server n8n.');
            }
            foreach ($data as $workflow) {
                if (!is_array($workflow) || !isset($workflow['id'], $workflow['name'])) {
                    continue;
                }
                $tags = collect($workflow['tags'] ?? [])->pluck('name')->filter(fn ($tag) => is_string($tag))->values();
                if (!$tags->contains(config('services.n8n.employee_tag', 'employee'))) {
                    continue;
                }
                $employees->push([
                    'id' => (string) $workflow['id'],
                    'name' => (string) $workflow['name'],
                    'active' => (bool) ($workflow['active'] ?? false),
                    'tags' => $tags->all(),
                    'url' => $url.'/workflow/'.rawurlencode((string) $workflow['id']),
                ]);
            }
            $cursor = $response->json('nextCursor');
            if ($cursor === '') {
                $cursor = null;
            }
            if ($cursor !== null) {
                if (!is_string($cursor) || isset($seen[$cursor]) || count($seen) >= 20) {
                    throw new RuntimeException('Pagination n8n tidak dapat diselesaikan. Periksa jumlah workflow atau coba lagi.');
                }
                $seen[$cursor] = true;
            }
        } while ($cursor !== null);

        return $employees->unique('id')->sortBy('name', SORT_NATURAL | SORT_FLAG_CASE)->values();
    }
}
