<?php

namespace Tests\Feature;

use App\Services\N8nOffice;
use App\Http\Middleware\EnsureSuperadmin;
use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Illuminate\Support\Facades\Http;
use RuntimeException;
use Tests\TestCase;

class VirtualOfficeTest extends TestCase
{
    protected function setUp(): void
    {
        parent::setUp();
        config(['services.n8n.url' => 'https://office.example', 'services.n8n.api_key' => 'test-key', 'services.n8n.employee_tag' => 'employee']);
        Http::preventStrayRequests();
    }

    public function test_reads_all_pages_and_only_exposes_employee_summary(): void
    {
        Http::fakeSequence()->push(['data' => [
            ['id' => '1', 'name' => 'Finance', 'active' => true, 'tags' => [['name' => 'employee']], 'nodes' => ['private']],
            ['id' => '2', 'name' => 'Other', 'tags' => []],
        ], 'nextCursor' => 'page2'])->push(['data' => [
            ['id' => '3', 'name' => 'Admin', 'active' => false, 'tags' => [['name' => 'employee']]],
        ], 'nextCursor' => null]);
        $employees = (new N8nOffice)->employees();
        $this->assertSame(['3', '1'], $employees->pluck('id')->all());
        $this->assertArrayNotHasKey('nodes', $employees->last());
        Http::assertSent(fn ($request) => $request->hasHeader('X-N8N-API-KEY', 'test-key') && ($request->data()['cursor'] ?? null) === 'page2');
        Http::assertSentCount(2);
    }

    public function test_missing_configuration_does_not_send_request(): void
    {
        config(['services.n8n.api_key' => '']);
        try {
            (new N8nOffice)->employees();
            $this->fail('Expected configuration error');
        } catch (RuntimeException $exception) {
            $this->assertStringContainsString('N8N_API_KEY', $exception->getMessage());
        }
        Http::assertNothingSent();
    }

    public function test_api_failure_does_not_expose_response_body(): void
    {
        Http::fakeSequence()->push(['secret' => 'sensitive'], 403);
        $this->expectExceptionMessage('Akses n8n ditolak. Periksa API key dan izin membaca workflow.');
        (new N8nOffice)->employees();
    }

    public function test_repeated_cursor_is_rejected(): void
    {
        Http::fakeSequence()->push(['data' => [], 'nextCursor' => 'same'])->push(['data' => [], 'nextCursor' => 'same']);
        $this->expectExceptionMessage('Pagination n8n tidak dapat diselesaikan');
        (new N8nOffice)->employees();
    }

    public function test_invalid_response_is_not_treated_as_empty_office(): void
    {
        Http::fakeSequence()->push('<html>Login</html>', 200);
        $this->expectExceptionMessage('Respons n8n tidak valid');
        (new N8nOffice)->employees();
    }

    public function test_office_route_requires_authentication(): void
    {
        $route = app('router')->getRoutes()->getByName('virtual-office.index');
        $this->assertNotNull($route);
        $this->assertSame('virtual-office', $route->uri());
        $this->assertContains('auth', $route->gatherMiddleware());
        $this->assertContains(EnsureSuperadmin::class, $route->gatherMiddleware());
        $logs = app('router')->getRoutes()->getByName('virtual-office.logs');
        $this->assertContains('auth', $logs->gatherMiddleware());
        $this->assertContains(EnsureSuperadmin::class, $logs->gatherMiddleware());
    }

    public function test_logs_are_scoped_paginated_and_exclude_execution_payloads(): void
    {
        Http::fakeSequence()->push(['id' => 'worker', 'tags' => [['name' => 'employee']]])
            ->push(['data' => [
                ['id' => '10', 'workflowId' => 'worker', 'status' => 'success', 'startedAt' => '2026-09-30T10:00:00Z', 'stoppedAt' => '2026-09-30T10:00:02Z', 'data' => ['private' => 'secret']],
                ['id' => '11', 'workflowId' => 'other', 'status' => 'error'],
                ['id' => '12', 'workflowId' => 'worker'],
            ], 'nextCursor' => 'older']);
        $result = (new N8nOffice)->executions('worker', 'page2');
        $this->assertCount(2, $result['data']);
        $this->assertSame('older', $result['nextCursor']);
        $this->assertSame('success', $result['data'][0]['status']);
        $this->assertSame('unknown', $result['data'][1]['status']);
        $this->assertArrayNotHasKey('data', $result['data'][0]);
        Http::assertSent(fn ($request) => str_contains($request->url(), '/executions?')
            && $request['workflowId'] === 'worker' && $request['includeData'] === 'false' && $request['cursor'] === 'page2');
    }

    public function test_non_employee_cannot_read_logs(): void
    {
        Http::fakeSequence()->push(['id' => 'other', 'tags' => []]);
        try {
            (new N8nOffice)->executions('other');
            $this->fail('Expected HTTP 404');
        } catch (HttpException $exception) {
            $this->assertSame(404, $exception->getStatusCode());
        }
        Http::assertSentCount(1);
    }

    public function test_logs_permission_failure_has_safe_message(): void
    {
        Http::fakeSequence()->push(['tags' => [['name' => 'employee']]])->push(['secret' => 'private'], 403);
        $this->expectExceptionMessage('Akses log ditolak. API key n8n memerlukan izin membaca executions.');
        (new N8nOffice)->executions('worker');
    }

    public function test_empty_execution_history_is_valid(): void
    {
        Http::fakeSequence()->push(['tags' => [['name' => 'employee']]])->push(['data' => [], 'nextCursor' => null]);
        $this->assertSame(['data' => [], 'nextCursor' => null], (new N8nOffice)->executions('worker'));
    }

    public function test_work_state_prioritizes_in_flight_execution(): void
    {
        Http::fakeSequence()->push(['tags' => [['name' => 'employee']]])->push([
            'data' => [['id' => '99', 'workflowId' => 'worker', 'status' => 'running']],
        ]);
        $this->assertSame('working', (new N8nOffice)->workState('worker')['workState']);
        Http::assertSentCount(2);
        $route = app('router')->getRoutes()->getByName('virtual-office.work-state');
        $this->assertContains('auth', $route->gatherMiddleware());
        $this->assertContains(EnsureSuperadmin::class, $route->gatherMiddleware());
    }

    public function test_work_state_maps_last_failure_after_checking_running_and_waiting(): void
    {
        Http::fakeSequence()->push(['tags' => [['name' => 'employee']]])
            ->push(['data' => []])->push(['data' => []])->push(['data' => [
                ['id' => '12', 'workflowId' => 'worker', 'status' => 'error'],
            ]]);
        $this->assertSame('error', (new N8nOffice)->workState('worker')['workState']);
    }

    public function test_older_n8n_running_filter_is_reported_as_limited(): void
    {
        Http::fakeSequence()->push(['tags' => [['name' => 'employee']]])
            ->push(['message' => 'unsupported'], 400)->push(['data' => []])->push(['data' => []]);
        $state = (new N8nOffice)->workState('worker');
        $this->assertTrue($state['limited']);
        $this->assertSame('idle', $state['workState']);
    }

    public function test_missing_execution_status_is_not_assumed_success(): void
    {
        Http::fakeSequence()->push(['tags' => [['name' => 'employee']]])
            ->push(['data' => []])->push(['data' => []])->push(['data' => [['id' => '12', 'workflowId' => 'worker']]]);
        $this->assertNull((new N8nOffice)->workState('worker')['workState']);
    }

    public function test_failed_status_request_is_not_idle(): void
    {
        Http::fakeSequence()->push(['tags' => [['name' => 'employee']]])->push([], 403);
        $this->expectException(RuntimeException::class);
        (new N8nOffice)->workState('worker');
    }

    public function test_execution_error_returns_node_message_without_raw_payload(): void
    {
        Http::fakeSequence()->push(['tags' => [['name' => 'employee']]])->push([
            'id' => '42', 'workflowId' => 'worker', 'data' => ['resultData' => [
                'error' => ['node' => ['name' => 'HTTP Request', 'credentials' => 'private'],
                    'message' => 'Forbidden - perhaps check your credentials?', 'stack' => 'private'],
            ]],
        ]);
        $result = (new N8nOffice)->executionError('worker', '42');
        $this->assertSame(['id' => '42', 'node' => 'HTTP Request', 'message' => 'Forbidden - perhaps check your credentials?', 'description' => null], $result);
        $route = app('router')->getRoutes()->getByName('virtual-office.execution');
        $this->assertContains('auth', $route->gatherMiddleware());
        $this->assertContains(EnsureSuperadmin::class, $route->gatherMiddleware());
    }

    public function test_execution_from_another_workflow_is_rejected(): void
    {
        Http::fakeSequence()->push(['tags' => [['name' => 'employee']]])->push(['id' => '42', 'workflowId' => 'other']);
        $this->expectException(HttpException::class);
        (new N8nOffice)->executionError('worker', '42');
    }

    public function test_execution_error_falls_back_to_node_run_error(): void
    {
        Http::fakeSequence()->push(['tags' => [['name' => 'employee']]])->push([
            'id' => '42', 'workflowId' => 'worker', 'data' => ['resultData' => [
                'runData' => ['HTTP Request' => [['error' => ['message' => 'Forbidden']]]],
            ]],
        ]);
        $result = (new N8nOffice)->executionError('worker', '42');
        $this->assertSame('HTTP Request', $result['node']);
        $this->assertSame('Forbidden', $result['message']);
    }

    public function test_execution_without_saved_error_returns_empty_detail(): void
    {
        Http::fakeSequence()->push(['tags' => [['name' => 'employee']]])->push(['id' => '42', 'workflowId' => 'worker']);
        $this->assertNull((new N8nOffice)->executionError('worker', '42')['message']);
    }

    public function test_invalid_execution_history_is_rejected(): void
    {
        Http::fakeSequence()->push(['tags' => [['name' => 'employee']]])->push('<html>Error</html>');
        $this->expectExceptionMessage('Respons log n8n tidak valid.');
        (new N8nOffice)->executions('worker');
    }

    public function test_superadmin_is_allowed_for_integer_and_string_group_id(): void
    {
        foreach ([1, '1'] as $groupId) {
            $request = Request::create('/virtual-office');
            $request->setUserResolver(fn () => (object) ['group_id' => $groupId]);
            $response = (new EnsureSuperadmin)->handle($request, fn () => response('allowed'));
            $this->assertSame('allowed', $response->getContent());
        }
    }

    public function test_other_groups_and_guests_are_denied_before_fetching_n8n(): void
    {
        foreach ([2, '2', 0, null] as $groupId) {
            $request = Request::create('/virtual-office');
            $request->setUserResolver(fn () => $groupId === null ? null : (object) ['group_id' => $groupId]);
            try {
                (new EnsureSuperadmin)->handle($request, function () {
                    $this->fail('Unauthorized request reached the controller.');
                });
                $this->fail('Expected HTTP 403.');
            } catch (HttpException $exception) {
                $this->assertSame(403, $exception->getStatusCode());
            }
        }
        Http::assertNothingSent();
    }
}
