<?php

declare(strict_types=1);

namespace App\Framework\Http\Batch;

use App\Framework\Async\FiberManager;
use App\Framework\Core\Application;
use App\Framework\Exception\FrameworkException;
use App\Framework\Exception\HttpErrorCode;
use App\Framework\Exception\ValidationErrorCode;
use App\Framework\Http\Headers;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\RequestBody;
use App\Framework\Http\Status;

/**
 * Processes batch requests by executing multiple operations
 */
final readonly class BatchProcessor
{
    public function __construct(
        private Application $application,
        private FiberManager $fiberManager,
        private BatchConfig $config = new BatchConfig()
    ) {
    }

    /**
     * Process a batch request and return batch responses
     * @return BatchResponse[]
     */
    public function process(BatchRequest $batchRequest): array
    {
        // Validate operations against config
        $this->validateOperations($batchRequest);

        if ($batchRequest->count() <= $this->config->maxConcurrentOperations) {
            return $this->processConcurrently($batchRequest);
        }

        return $this->processInChunks($batchRequest);
    }

    /**
     * Process all operations concurrently using FiberManager
     * @return BatchResponse[]
     */
    private function processConcurrently(BatchRequest $batchRequest): array
    {
        $operations = [];
        foreach ($batchRequest->operations as $operation) {
            $operations[$operation->id] = fn () => $this->executeOperation($operation);
        }

        $results = $this->fiberManager->batch($operations);

        $responses = [];
        foreach ($results as $id => $result) {
            if ($result instanceof BatchResponse) {
                $responses[] = $result;
            } elseif ($result instanceof \Throwable) {
                $responses[] = BatchResponse::error(
                    id: $id,
                    status: Status::INTERNAL_SERVER_ERROR,
                    error: $result->getMessage()
                );

                if (! $batchRequest->continueOnError) {
                    break;
                }
            }
        }

        return $responses;
    }

    /**
     * Process operations in chunks for large batch requests
     * @return BatchResponse[]
     */
    private function processInChunks(BatchRequest $batchRequest): array
    {
        $chunks = array_chunk($batchRequest->operations, $this->config->maxConcurrentOperations);
        $responses = [];

        foreach ($chunks as $chunk) {
            $chunkRequest = new BatchRequest($chunk, $batchRequest->continueOnError);
            $chunkResponses = $this->processConcurrently($chunkRequest);

            $responses = array_merge($responses, $chunkResponses);

            // Stop processing if continueOnError is false and we have an error
            if (! $batchRequest->continueOnError) {
                $hasError = false;
                foreach ($chunkResponses as $response) {
                    if ($response->error !== null) {
                        $hasError = true;

                        break;
                    }
                }
                if ($hasError) {
                    break;
                }
            }
        }

        return $responses;
    }

    /**
     * Execute a single batch operation
     */
    private function executeOperation(BatchOperation $operation): BatchResponse
    {
        try {
            // Create HTTP request from batch operation
            $request = $this->createHttpRequest($operation);

            // Execute the request through the application
            $response = $this->application->handleRequest($request);

            return BatchResponse::success(
                id: $operation->id,
                status: $response->status,
                body: $response->body,
                headers: $response->headers->toArray()
            );

        } catch (FrameworkException $e) {
            return BatchResponse::error(
                id: $operation->id,
                status: $this->mapExceptionToStatus($e),
                error: $e->getMessage()
            );
        } catch (\Throwable $e) {
            return BatchResponse::error(
                id: $operation->id,
                status: Status::INTERNAL_SERVER_ERROR,
                error: 'Internal server error'
            );
        }
    }

    /**
     * Create HTTP request from batch operation
     */
    private function createHttpRequest(BatchOperation $operation): HttpRequest
    {
        // Parse query string from path if present
        $pathParts = parse_url($operation->path);
        $path = $pathParts['path'] ?? $operation->path;
        $queryString = $pathParts['query'] ?? '';

        // Merge query params from path and operation
        $queryParams = $operation->queryParams;
        if (! empty($queryString)) {
            parse_str($queryString, $parsedQuery);
            $queryParams = array_merge($queryParams, $parsedQuery);
        }

        return new HttpRequest(
            method: $operation->method,
            headers: new Headers($operation->headers),
            body: $operation->body ?? '',
            path: $path,
            queryParams: $queryParams,
            parsedBody: new RequestBody(
                $operation->method,
                new Headers($operation->headers),
                $operation->body ?? '',
                []
            )
        );
    }

    /**
     * Validate operations against batch configuration
     */
    private function validateOperations(BatchRequest $batchRequest): void
    {
        if ($batchRequest->count() > $this->config->maxOperations) {
            throw FrameworkException::create(
                ValidationErrorCode::BUSINESS_RULE_VIOLATION,
                "Batch request exceeds maximum operations limit ({$this->config->maxOperations})"
            );
        }

        if (! $this->config->allowContinueOnError && $batchRequest->continueOnError) {
            throw FrameworkException::create(
                ValidationErrorCode::BUSINESS_RULE_VIOLATION,
                'Continue on error is not allowed by current configuration'
            );
        }

        foreach ($batchRequest->operations as $operation) {
            if (! $this->config->isMethodAllowed($operation->method->value)) {
                throw FrameworkException::create(
                    ValidationErrorCode::BUSINESS_RULE_VIOLATION,
                    "Method {$operation->method->value} is not allowed in batch operations"
                );
            }
        }
    }

    /**
     * Map framework exception to appropriate HTTP status
     */
    private function mapExceptionToStatus(FrameworkException $e): Status
    {
        return match(true) {
            $e->isCategory('VAL') => Status::BAD_REQUEST,
            $e->isCategory('AUTH') => Status::UNAUTHORIZED,
            $e->isCategory('HTTP') && $e->isErrorCode(HttpErrorCode::RATE_LIMIT_EXCEEDED) => Status::TOO_MANY_REQUESTS,
            default => Status::INTERNAL_SERVER_ERROR
        };
    }
}
