<?php

declare(strict_types=1);

namespace App\Framework\HttpClient;

use App\Framework\Async\AsyncPromise;
use App\Framework\Async\AsyncService;
use App\Framework\Core\ValueObjects\Duration;

/**
 * Async Adapter für HttpClient - Fluent API Style
 *
 * Provides async operations as property access: $httpClient->async->sendMultiple()
 */
final readonly class AsyncHttpClientAdapter
{
    public function __construct(
        private HttpClient $httpClient,
        private AsyncService $asyncService
    ) {
    }

    /**
     * Send request asynchronously
     */
    public function send(ClientRequest $request): AsyncPromise
    {
        return $this->asyncService->promise(
            fn () => $this->httpClient->send($request)
        );
    }

    /**
     * Send multiple requests in parallel
     */
    public function sendMultiple(array $requests): array
    {
        $operations = [];
        foreach ($requests as $key => $request) {
            $operations[$key] = fn () => $this->httpClient->send($request);
        }

        $results = $this->asyncService->parallel($operations);

        return $results->await();
    }

    /**
     * Send request with timeout protection
     */
    public function sendWithTimeout(ClientRequest $request, Duration $timeout): ClientResponse
    {
        return $this->asyncService->withTimeout(
            fn () => $this->httpClient->send($request),
            $timeout
        );
    }

    /**
     * Batch requests with concurrency control
     */
    public function sendBatch(array $requests, int $maxConcurrency = 5): array
    {
        $operations = [];
        foreach ($requests as $key => $request) {
            $operations[$key] = fn () => $this->httpClient->send($request);
        }

        return $this->asyncService->batch($operations, $maxConcurrency);
    }

    /**
     * Parallel API aggregation
     */
    public function aggregate(array $namedRequests): array
    {
        $operations = [];
        foreach ($namedRequests as $name => $request) {
            $operations[$name] = function () use ($request) {
                try {
                    $response = $this->httpClient->send($request);

                    return [
                        'success' => true,
                        'response' => $response,
                        'error' => null,
                    ];
                } catch (\Exception $e) {
                    return [
                        'success' => false,
                        'response' => null,
                        'error' => $e->getMessage(),
                    ];
                }
            };
        }

        $results = $this->asyncService->parallel($operations);

        return $results->await();
    }

    /**
     * Fan-out pattern: Send same request to multiple endpoints
     */
    public function fanOut(ClientRequest $baseRequest, array $urls): array
    {
        $operations = [];
        foreach ($urls as $key => $url) {
            $request = $baseRequest->with(['url' => $url]);
            $operations[$key] = fn () => $this->httpClient->send($request);
        }

        $results = $this->asyncService->parallel($operations);

        return $results->await();
    }

    /**
     * Race pattern: Return first successful response
     */
    public function race(array $requests): ClientResponse
    {
        $promises = [];
        foreach ($requests as $key => $request) {
            $promises[$key] = $this->send($request);
        }

        $results = $this->asyncService->parallel(
            array_map(fn ($promise) => fn () => $promise->await(), $promises)
        );

        $responses = $results->await();

        // Return first successful response
        foreach ($responses as $response) {
            if ($response instanceof ClientResponse && $response->isSuccessful()) {
                return $response;
            }
        }

        // If no successful response, return first one
        return reset($responses);
    }

    /**
     * Health check multiple endpoints
     */
    public function healthCheck(array $urls, ?Duration $timeout = null): array
    {
        $timeout ??= Duration::fromSeconds(5);

        $operations = [];
        foreach ($urls as $name => $url) {
            $operations[$name] = function () use ($url, $timeout) {
                try {
                    $request = new ClientRequest(
                        \App\Framework\Http\Method::GET,
                        $url
                    );

                    $start = microtime(true);
                    $response = $this->sendWithTimeout($request, $timeout);
                    $duration = microtime(true) - $start;

                    return [
                        'url' => $url,
                        'status' => $response->status->value,
                        'healthy' => $response->isSuccessful(),
                        'response_time_ms' => round($duration * 1000, 2),
                        'error' => null,
                    ];
                } catch (\Exception $e) {
                    return [
                        'url' => $url,
                        'status' => null,
                        'healthy' => false,
                        'response_time_ms' => null,
                        'error' => $e->getMessage(),
                    ];
                }
            };
        }

        $results = $this->asyncService->parallel($operations);

        return $results->await();
    }

    /**
     * Send request and retry on failure
     */
    public function sendWithRetry(
        ClientRequest $request,
        int $maxRetries = 3,
        ?Duration $retryDelay = null
    ): ClientResponse {
        $retryDelay ??= Duration::fromSeconds(1);
        $lastException = null;

        for ($attempt = 1; $attempt <= $maxRetries + 1; $attempt++) {
            try {
                return $this->httpClient->send($request);
            } catch (\Exception $e) {
                $lastException = $e;

                if ($attempt <= $maxRetries) {
                    // Wait before retry
                    $this->asyncService->delay($retryDelay)->getReturn();

                    // Exponential backoff
                    $retryDelay = Duration::fromSeconds($retryDelay->toSeconds() * 2);
                }
            }
        }

        throw $lastException;
    }

    /**
     * Streaming data collection from multiple sources
     */
    public function collect(array $requests, ?callable $processor = null): array
    {
        $operations = [];
        foreach ($requests as $key => $request) {
            $operations[$key] = function () use ($request, $processor, $key) {
                $response = $this->httpClient->send($request);

                if ($processor) {
                    return $processor($response, $key);
                }

                return $response;
            };
        }

        $results = $this->asyncService->parallel($operations);

        return $results->await();
    }

    /**
     * Get statistics
     */
    public function getStats(): array
    {
        return [
            'async_enabled' => true,
            'async_stats' => $this->asyncService->getStats(),
        ];
    }
}
