<?php

declare(strict_types=1);

namespace App\Framework\Http\Emitter;

use App\Framework\Http\Range;
use App\Framework\Http\Response;
use App\Framework\Http\Responses\StreamResponse;
use App\Framework\Http\Status;
use InvalidArgumentException;

final class StreamEmitter implements Emitter
{
    public function emit(Response $response): void
    {
        if (! $response instanceof StreamResponse) {
            throw new InvalidArgumentException('Response must be StreamResponse');
        }

        $chunkSize = 8192;

        // Range Request verarbeiten
        if ($response->range !== null) {
            $this->emitRangeResponse($response, $chunkSize);
        } else {
            $this->emitFullResponse($response, $chunkSize);
        }

    }

    private function emitRangeResponse(StreamResponse $response, int $chunkSize): void
    {
        $range = $response->range;

        // 206 Partial Content Status
        http_response_code(Status::PARTIAL_CONTENT->value);

        // Range-spezifische Header
        header("Content-Range: bytes {$range->start}-{$range->end}/{$range->total}");
        header('Content-Length: ' . $range->length);
        header("Content-Type: {$response->mimeType}");
        header('Accept-Ranges: bytes');

        // Zusätzliche Header
        foreach ($response->headers->all() as $name => $value) {
            $headerValue = is_array($value) ? implode(', ', $value) : $value;
            header("$name: $headerValue");
        }

        $this->streamRange($response->filePath, $range, $chunkSize);
    }

    private function streamRange(string $filePath, Range $range, int $chunkSize): void
    {
        // Stream öffnen und zur Start-Position springen
        $handle = fopen($filePath, 'rb');
        fseek($handle, $range->start);

        $remaining = $range->length;

        while ($remaining > 0 && ! feof($handle)) {
            $readSize = min($chunkSize, $remaining);
            $chunk = fread($handle, $readSize);

            echo $chunk;

            $remaining -= strlen($chunk);

            // Output buffer leeren
            $this->flushOutput();
        }

        fclose($handle);
    }

    private function flushOutput(): void
    {
        if (ob_get_level() > 0) {
            ob_flush();
        }
        flush();
    }

    private function emitFullResponse(StreamResponse $response, int $chunkSize): void
    {
        // Standard Header
        http_response_code($response->status->value);
        header("Content-Type: {$response->mimeType}");
        header("Content-Length: {$response->fileSize}");
        header('Accept-Ranges: bytes');

        $this->streamFile($response->filePath, $chunkSize);
    }

    private function streamFile(string $filePath, int $chunkSize): void
    {
        // Stream komplett senden
        $handle = fopen($filePath, 'rb');

        while (! feof($handle)) {
            echo fread($handle, $chunkSize);

            $this->flushOutput();
        }

        fclose($handle);
    }
}
