<?php

declare(strict_types=1);

use App\Framework\Api\ApiException;
use App\Framework\Api\ApiRequestTrait;
use App\Framework\Http\Headers;
use App\Framework\Http\Method;
use App\Framework\Http\Status;
use App\Framework\HttpClient\ClientOptions;
use App\Framework\HttpClient\ClientRequest;
use App\Framework\HttpClient\ClientResponse;
use App\Framework\HttpClient\HttpClient;

// Test class that uses the trait
final class TestApiClient
{
    use ApiRequestTrait;

    public function __construct(
        string $baseUrl,
        HttpClient $httpClient,
        ClientOptions $defaultOptions
    ) {
        $this->baseUrl = $baseUrl;
        $this->httpClient = $httpClient;
        $this->defaultOptions = $defaultOptions;
    }

    // Expose protected method for testing
    public function testDecodeJson(ClientResponse $response): array
    {
        return $this->decodeJson($response);
    }
}

describe('ApiRequestTrait', function () {
    beforeEach(function () {
        $this->httpClient = Mockery::mock(HttpClient::class);
        $this->defaultOptions = ClientOptions::withTimeout(30);
        $this->baseUrl = 'https://api.example.com';

        $this->apiClient = new TestApiClient(
            $this->baseUrl,
            $this->httpClient,
            $this->defaultOptions
        );
    });

    afterEach(function () {
        Mockery::close();
    });

    describe('sendRequest', function () {
        it('sends GET request successfully', function () {
            $expectedResponse = new ClientResponse(
                status: Status::OK,
                headers: new Headers([]),
                body: '{"data": "success"}'
            );

            $this->httpClient
                ->shouldReceive('send')
                ->once()
                ->with(Mockery::on(function ($request) {
                    return $request instanceof ClientRequest
                        && $request->method === Method::GET
                        && str_contains($request->url, '/api/users');
                }))
                ->andReturn($expectedResponse);

            $response = $this->apiClient->sendRequest(
                method: Method::GET,
                endpoint: '/api/users',
                data: []
            );

            expect($response)->toBe($expectedResponse);
            expect($response->status)->toBe(Status::OK);
        });

        it('sends POST request with data', function () {
            $expectedResponse = new ClientResponse(
                status: Status::CREATED,
                headers: new Headers([]),
                body: '{"id": 123}'
            );

            $this->httpClient
                ->shouldReceive('send')
                ->once()
                ->with(Mockery::on(function ($request) {
                    return $request instanceof ClientRequest
                        && $request->method === Method::POST
                        && str_contains($request->url, '/api/users')
                        && !empty($request->body); // Data is stored in body as JSON
                }))
                ->andReturn($expectedResponse);

            $response = $this->apiClient->sendRequest(
                method: Method::POST,
                endpoint: '/api/users',
                data: ['name' => 'John Doe', 'email' => 'john@example.com']
            );

            expect($response->status)->toBe(Status::CREATED);
        });

        it('strips leading slash from endpoint', function () {
            $expectedResponse = new ClientResponse(
                status: Status::OK,
                headers: new Headers([]),
                body: '{}'
            );

            $this->httpClient
                ->shouldReceive('send')
                ->once()
                ->with(Mockery::on(function ($request) {
                    // URL should be clean without double slashes
                    return $request->url === 'https://api.example.com/api/test';
                }))
                ->andReturn($expectedResponse);

            $this->apiClient->sendRequest(
                method: Method::GET,
                endpoint: '/api/test'
            );
        });

        it('handles endpoint without leading slash', function () {
            $expectedResponse = new ClientResponse(
                status: Status::OK,
                headers: new Headers([]),
                body: '{}'
            );

            $this->httpClient
                ->shouldReceive('send')
                ->once()
                ->with(Mockery::on(function ($request) {
                    return $request->url === 'https://api.example.com/api/test';
                }))
                ->andReturn($expectedResponse);

            $this->apiClient->sendRequest(
                method: Method::GET,
                endpoint: 'api/test'
            );
        });

        it('uses custom options when provided', function () {
            $customOptions = ClientOptions::withTimeout(60);
            $expectedResponse = new ClientResponse(
                status: Status::OK,
                headers: new Headers([]),
                body: '{}'
            );

            $this->httpClient
                ->shouldReceive('send')
                ->once()
                ->with(Mockery::on(function ($request) use ($customOptions) {
                    return $request->options === $customOptions;
                }))
                ->andReturn($expectedResponse);

            $this->apiClient->sendRequest(
                method: Method::GET,
                endpoint: '/api/test',
                data: [],
                options: $customOptions
            );
        });

        it('uses default options when none provided', function () {
            $expectedResponse = new ClientResponse(
                status: Status::OK,
                headers: new Headers([]),
                body: '{}'
            );

            $this->httpClient
                ->shouldReceive('send')
                ->once()
                ->with(Mockery::on(function ($request) {
                    return $request->options === $this->defaultOptions;
                }))
                ->andReturn($expectedResponse);

            $this->apiClient->sendRequest(
                method: Method::GET,
                endpoint: '/api/test'
            );
        });
    });

    describe('error handling', function () {
        it('throws ApiException for 400 errors', function () {
            $errorResponse = new ClientResponse(
                status: Status::BAD_REQUEST,
                headers: new Headers([]),
                body: '{"error": "Invalid request"}'
            );

            $this->httpClient
                ->shouldReceive('send')
                ->once()
                ->andReturn($errorResponse);

            $this->apiClient->sendRequest(
                method: Method::GET,
                endpoint: '/api/test'
            );
        })->throws(ApiException::class);

        it('throws ApiException for 401 errors', function () {
            $errorResponse = new ClientResponse(
                status: Status::UNAUTHORIZED,
                headers: new Headers([]),
                body: '{"error": "Unauthorized"}'
            );

            $this->httpClient
                ->shouldReceive('send')
                ->once()
                ->andReturn($errorResponse);

            $this->apiClient->sendRequest(
                method: Method::GET,
                endpoint: '/api/test'
            );
        })->throws(ApiException::class);

        it('throws ApiException for 404 errors', function () {
            $errorResponse = new ClientResponse(
                status: Status::NOT_FOUND,
                headers: new Headers([]),
                body: '{"error": "Not found"}'
            );

            $this->httpClient
                ->shouldReceive('send')
                ->once()
                ->andReturn($errorResponse);

            $this->apiClient->sendRequest(
                method: Method::GET,
                endpoint: '/api/test'
            );
        })->throws(ApiException::class);

        it('throws ApiException for 500 errors', function () {
            $errorResponse = new ClientResponse(
                status: Status::INTERNAL_SERVER_ERROR,
                headers: new Headers([]),
                body: '{"error": "Server error"}'
            );

            $this->httpClient
                ->shouldReceive('send')
                ->once()
                ->andReturn($errorResponse);

            $this->apiClient->sendRequest(
                method: Method::GET,
                endpoint: '/api/test'
            );
        })->throws(ApiException::class);

        it('formats error message with detail field', function () {
            $errorResponse = new ClientResponse(
                status: Status::BAD_REQUEST,
                headers: new Headers([]),
                body: '{"detail": "Validation failed"}'
            );

            $this->httpClient
                ->shouldReceive('send')
                ->once()
                ->andReturn($errorResponse);

            try {
                $this->apiClient->sendRequest(
                    method: Method::POST,
                    endpoint: '/api/users'
                );
                expect(false)->toBeTrue(); // Should not reach here
            } catch (ApiException $e) {
                expect($e->getMessage())->toContain('Validation failed');
            }
        });

        it('formats error message with validation_messages', function () {
            $errorResponse = new ClientResponse(
                status: Status::UNPROCESSABLE_ENTITY,
                headers: new Headers([]),
                body: '{"detail": "Validation failed", "validation_messages": {"email": "Invalid format"}}'
            );

            $this->httpClient
                ->shouldReceive('send')
                ->once()
                ->andReturn($errorResponse);

            try {
                $this->apiClient->sendRequest(
                    method: Method::POST,
                    endpoint: '/api/users'
                );
                expect(false)->toBeTrue(); // Should not reach here
            } catch (ApiException $e) {
                expect($e->getMessage())->toContain('Validation failed');
                expect($e->getMessage())->toContain('Validierungsfehler');
                expect($e->getMessage())->toContain('email');
            }
        });

        it('formats error message with error field', function () {
            $errorResponse = new ClientResponse(
                status: Status::INTERNAL_SERVER_ERROR,
                headers: new Headers([]),
                body: '{"error": "Database connection failed"}'
            );

            $this->httpClient
                ->shouldReceive('send')
                ->once()
                ->andReturn($errorResponse);

            try {
                $this->apiClient->sendRequest(
                    method: Method::GET,
                    endpoint: '/api/test'
                );
                expect(false)->toBeTrue(); // Should not reach here
            } catch (ApiException $e) {
                expect($e->getMessage())->toContain('Database connection failed');
            }
        });

        it('formats generic error message when no error fields present', function () {
            $errorResponse = new ClientResponse(
                status: Status::BAD_REQUEST,
                headers: new Headers([]),
                body: '{"some_field": "some_value"}'
            );

            $this->httpClient
                ->shouldReceive('send')
                ->once()
                ->andReturn($errorResponse);

            try {
                $this->apiClient->sendRequest(
                    method: Method::GET,
                    endpoint: '/api/test'
                );
                expect(false)->toBeTrue(); // Should not reach here
            } catch (ApiException $e) {
                expect($e->getMessage())->toBe('API-Fehler');
            }
        });

        it('does not throw exception for 2xx responses', function () {
            $successResponse = new ClientResponse(
                status: Status::OK,
                headers: new Headers([]),
                body: '{"success": true}'
            );

            $this->httpClient
                ->shouldReceive('send')
                ->once()
                ->andReturn($successResponse);

            $response = $this->apiClient->sendRequest(
                method: Method::GET,
                endpoint: '/api/test'
            );

            expect($response->status)->toBe(Status::OK);
        });

        it('does not throw exception for 3xx responses', function () {
            $redirectResponse = new ClientResponse(
                status: Status::MOVED_PERMANENTLY,
                headers: new Headers(['Location' => 'https://example.com/new']),
                body: ''
            );

            $this->httpClient
                ->shouldReceive('send')
                ->once()
                ->andReturn($redirectResponse);

            $response = $this->apiClient->sendRequest(
                method: Method::GET,
                endpoint: '/api/test'
            );

            expect($response->status)->toBe(Status::MOVED_PERMANENTLY);
        });
    });

    describe('decodeJson', function () {
        it('decodes valid JSON response', function () {
            $response = new ClientResponse(
                status: Status::OK,
                headers: new Headers([]),
                body: '{"name": "John", "age": 30}'
            );

            $data = $this->apiClient->testDecodeJson($response);

            expect($data)->toBeArray();
            expect($data['name'])->toBe('John');
            expect($data['age'])->toBe(30);
        });

        it('returns empty array for invalid JSON', function () {
            $response = new ClientResponse(
                status: Status::OK,
                headers: new Headers([]),
                body: 'Invalid JSON {'
            );

            $data = $this->apiClient->testDecodeJson($response);

            expect($data)->toBe([]);
        });

        it('returns empty array for empty body', function () {
            $response = new ClientResponse(
                status: Status::NO_CONTENT,
                headers: new Headers([]),
                body: ''
            );

            $data = $this->apiClient->testDecodeJson($response);

            expect($data)->toBe([]);
        });

        it('decodes nested JSON structures', function () {
            $response = new ClientResponse(
                status: Status::OK,
                headers: new Headers([]),
                body: '{"user": {"name": "John", "email": "john@example.com"}, "meta": {"total": 1}}'
            );

            $data = $this->apiClient->testDecodeJson($response);

            expect($data)->toBeArray();
            expect($data['user'])->toBeArray();
            expect($data['user']['name'])->toBe('John');
            expect($data['meta']['total'])->toBe(1);
        });
    });
});
