<?php

use Datalumo\PhpSdk\Exceptions\AuthenticationException;
use Datalumo\PhpSdk\Exceptions\DatalumoException;
use Datalumo\PhpSdk\Exceptions\NotFoundException;
use Datalumo\PhpSdk\Exceptions\QuotaExceededException;
use Datalumo\PhpSdk\Exceptions\ValidationException;
use Datalumo\PhpSdk\HttpClient;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;

function createClientWithMock(array $responses, array &$history = []): HttpClient
{
    $mock = new MockHandler($responses);
    $stack = HandlerStack::create($mock);
    $stack->push(Middleware::history($history));

    $guzzle = new Client(['handler' => $stack, 'base_uri' => 'https://example.com/api/v1/']);

    $client = new HttpClient('https://example.com', 'test-token');

    // Replace the internal Guzzle client via reflection
    $reflection = new ReflectionClass($client);
    $property = $reflection->getProperty('client');
    $property->setValue($client, $guzzle);

    return $client;
}

it('sends GET requests with query parameters', function () {
    $history = [];
    $client = createClientWithMock([
        new Response(200, [], json_encode(['data' => 'ok'])),
    ], $history);

    $result = $client->get('collections', ['page' => 1]);

    expect($result)->toBe(['data' => 'ok']);
    expect($history[0]['request']->getMethod())->toBe('GET');
    expect($history[0]['request']->getUri()->getQuery())->toBe('page=1');
});

it('sends POST requests with json body', function () {
    $history = [];
    $client = createClientWithMock([
        new Response(201, [], json_encode(['data' => ['id' => '123']])),
    ], $history);

    $result = $client->post('collections', ['name' => 'Test']);

    expect($result)->toBe(['data' => ['id' => '123']]);
    expect($history[0]['request']->getMethod())->toBe('POST');
    expect(json_decode($history[0]['request']->getBody()->getContents(), true))
        ->toBe(['name' => 'Test']);
});

it('sends PATCH requests', function () {
    $history = [];
    $client = createClientWithMock([
        new Response(200, [], json_encode(['data' => ['id' => '123']])),
    ], $history);

    $client->patch('collections/123', ['name' => 'Updated']);

    expect($history[0]['request']->getMethod())->toBe('PATCH');
});

it('sends PUT requests', function () {
    $history = [];
    $client = createClientWithMock([
        new Response(200, [], json_encode(['data' => ['id' => '123']])),
    ], $history);

    $client->put('entries/upsert', ['raw_text' => 'content']);

    expect($history[0]['request']->getMethod())->toBe('PUT');
});

it('sends DELETE requests and returns empty array', function () {
    $client = createClientWithMock([
        new Response(204),
    ]);

    $client->delete('collections/123');

    // No exception = success
    expect(true)->toBeTrue();
});

it('throws AuthenticationException on 401', function () {
    $client = createClientWithMock([
        new Response(401, [], json_encode(['message' => 'Unauthenticated.'])),
    ]);

    // ClientException requires a request, so we need to trigger the actual request
    $mock = new MockHandler([
        new ClientException(
            'Unauthorized',
            new Request('GET', '/test'),
            new Response(401, [], json_encode(['message' => 'Unauthenticated.'])),
        ),
    ]);
    $guzzle = new Client(['handler' => HandlerStack::create($mock)]);
    $httpClient = new HttpClient('https://example.com', 'token');
    $reflection = new ReflectionClass($httpClient);
    $property = $reflection->getProperty('client');
    $property->setValue($httpClient, $guzzle);

    $httpClient->get('test');
})->throws(AuthenticationException::class, 'Unauthenticated.');

it('throws AuthenticationException on 403', function () {
    $mock = new MockHandler([
        new ClientException(
            'Forbidden',
            new Request('GET', '/test'),
            new Response(403, [], json_encode(['message' => 'Forbidden.'])),
        ),
    ]);
    $guzzle = new Client(['handler' => HandlerStack::create($mock)]);
    $client = new HttpClient('https://example.com', 'token');
    $reflection = new ReflectionClass($client);
    $property = $reflection->getProperty('client');
    $property->setValue($client, $guzzle);

    $client->get('test');
})->throws(AuthenticationException::class, 'Forbidden.');

it('throws QuotaExceededException on 402', function () {
    $mock = new MockHandler([
        new ClientException(
            'Payment Required',
            new Request('GET', '/test'),
            new Response(402, [], json_encode(['message' => 'Quota exceeded.'])),
        ),
    ]);
    $guzzle = new Client(['handler' => HandlerStack::create($mock)]);
    $client = new HttpClient('https://example.com', 'token');
    $reflection = new ReflectionClass($client);
    $property = $reflection->getProperty('client');
    $property->setValue($client, $guzzle);

    $client->get('test');
})->throws(QuotaExceededException::class, 'Quota exceeded.');

it('throws NotFoundException on 404', function () {
    $mock = new MockHandler([
        new ClientException(
            'Not Found',
            new Request('GET', '/test'),
            new Response(404, [], json_encode(['message' => 'Not found.'])),
        ),
    ]);
    $guzzle = new Client(['handler' => HandlerStack::create($mock)]);
    $client = new HttpClient('https://example.com', 'token');
    $reflection = new ReflectionClass($client);
    $property = $reflection->getProperty('client');
    $property->setValue($client, $guzzle);

    $client->get('test');
})->throws(NotFoundException::class, 'Not found.');

it('throws ValidationException on 422 with errors', function () {
    $mock = new MockHandler([
        new ClientException(
            'Unprocessable',
            new Request('POST', '/test'),
            new Response(422, [], json_encode([
                'message' => 'The name field is required.',
                'errors' => ['name' => ['The name field is required.']],
            ])),
        ),
    ]);
    $guzzle = new Client(['handler' => HandlerStack::create($mock)]);
    $client = new HttpClient('https://example.com', 'token');
    $reflection = new ReflectionClass($client);
    $property = $reflection->getProperty('client');
    $property->setValue($client, $guzzle);

    try {
        $client->post('test', []);
    } catch (ValidationException $e) {
        expect($e->errors())->toBe(['name' => ['The name field is required.']]);
        expect($e->getMessage())->toBe('The name field is required.');

        return;
    }

    test()->fail('Expected ValidationException was not thrown.');
});

it('throws DatalumoException on unexpected status codes', function () {
    $mock = new MockHandler([
        new ClientException(
            'Too Many Requests',
            new Request('GET', '/test'),
            new Response(429, [], json_encode(['message' => 'Rate limited.'])),
        ),
    ]);
    $guzzle = new Client(['handler' => HandlerStack::create($mock)]);
    $client = new HttpClient('https://example.com', 'token');
    $reflection = new ReflectionClass($client);
    $property = $reflection->getProperty('client');
    $property->setValue($client, $guzzle);

    $client->get('test');
})->throws(DatalumoException::class, 'Rate limited.');
