<?php
namespace boru\ai\Tests\Provider\OpenAI;

use boru\ai\Conversation\ConversationReference;
use boru\ai\Http\HttpResponse;
use boru\ai\Provider\OpenAI\OpenAIClient;
use boru\ai\Provider\OpenAI\OpenAIMapper;
use boru\ai\Provider\OpenAI\OpenAIProvider;
use boru\ai\Provider\ProviderRegistry;
use boru\ai\Runtime\Runtime;
use boru\ai\Runtime\RuntimeContext;
use boru\ai\Runtime\ToolExecutor;
use boru\ai\Runtime\ToolRegistry;
use boru\ai\Schema\Request;
use boru\ai\Stream\StreamEvent;
use boru\ai\Tests\Fakes\FakeHttpClient;
use boru\ai\Tests\Fakes\FakePersistenceProvider;
use PHPUnit\Framework\TestCase;

class OpenAIProviderStreamingTest extends TestCase
{
    public function testExecuteStreamsNormalizedEventsAndReturnsFinalResponse()
    {
        $httpClient = new FakeHttpClient();
        $httpResponse = new HttpResponse();
        $httpResponse->statusCode(200);
        $httpClient->queueStream(array(
            "data: {\"type\":\"response.output_text.delta\",\"delta\":\"Hello \"}\n\n",
            "data: {\"type\":\"response.output_text.delta\",\"delta\":\"world\"}\n\n",
            "data: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_1\",\"model\":\"gpt-5.1\",\"status\":\"completed\",\"output\":[{\"type\":\"message\",\"content\":[{\"type\":\"output_text\",\"text\":\"Hello world\"}]}]}}\n\n",
            "data: [DONE]\n\n",
        ), $httpResponse);

        $client = new OpenAIClient('test-key', array(), $httpClient);
        $provider = new OpenAIProvider($client, new OpenAIMapper());

        $request = new Request();
        $request->model('gpt-5.1')->addMessage('Hello')->option('stream', true);

        $events = array();
        $response = $provider->execute($request, function ($event) use (&$events) {
            $events[] = $event;
        });

        $this->assertSame('Hello world', $response->text());
        $this->assertCount(3, $events);
        $this->assertSame(StreamEvent::TEXT_DELTA, $events[0]->type());
        $this->assertSame('Hello ', $events[0]->delta());
        $this->assertSame(StreamEvent::TEXT_DELTA, $events[1]->type());
        $this->assertSame('world', $events[1]->delta());
        $this->assertSame(StreamEvent::TEXT_COMPLETED, $events[2]->type());
        $this->assertSame('Hello world', $events[2]->text());
        $this->assertSame('text/event-stream', $httpClient->requests[0]->header('Accept'));
    }

    public function testOpenAIStreamingInvalidFinalResponseThrowsRuntimeException()
    {
        $httpClient = new FakeHttpClient();
        $httpResponse = new HttpResponse();
        $httpResponse->statusCode(502)->body('not-json');
        $httpClient->queueStream(array(
            "data: [DONE]\n\n",
        ), $httpResponse);

        $client = new OpenAIClient('test-key', array(), $httpClient);
        $provider = new OpenAIProvider($client, new OpenAIMapper());

        $request = new Request();
        $request->model('gpt-5.1')->addMessage('Hello')->option('stream', true);

        try {
            $provider->execute($request);
            $this->fail('Expected provider to throw for invalid OpenAI streaming final response');
        } catch (\RuntimeException $e) {
            $this->assertSame('OpenAI streaming response did not produce a valid final response', $e->getMessage());
            $this->assertSame(502, $e->getCode());
        }
    }

    public function testOpenAIStreamingInvalidFinalResponseCausesRuntimeFailure()
    {
        $httpClient = new FakeHttpClient();
        $httpResponse = new HttpResponse();
        $httpResponse->statusCode(502)->body('not-json');
        $httpClient->queueStream(array(
            "data: [DONE]\n\n",
        ), $httpResponse);

        $client = new OpenAIClient('test-key', array(), $httpClient);
        $provider = new OpenAIProvider($client, new OpenAIMapper());

        $providerRegistry = new ProviderRegistry();
        $providerRegistry->register($provider);
        $providerRegistry->mapModelPrefix('gpt-', 'openai');

        $persistence = new FakePersistenceProvider();
        $context = new RuntimeContext(
            $providerRegistry,
            $persistence,
            null,
            new ToolExecutor(new ToolRegistry()),
            new ToolRegistry()
        );

        $runtime = new Runtime($context);
        $request = new Request();
        $request->provider('openai')->model('gpt-5.1')->addMessage('Hello with streaming')->option('stream', true);

        try {
            $runtime->execute($request, new ConversationReference());
            $this->fail('Expected runtime to throw for OpenAI streaming provider failure');
        } catch (\RuntimeException $e) {
            $this->assertSame('OpenAI streaming response did not produce a valid final response', $e->getMessage());
            $this->assertSame(502, $e->getCode());
        }

        $lastTurn = $persistence->savedTurns[count($persistence->savedTurns) - 1];
        $this->assertSame('failed', $lastTurn->status());
        $this->assertSame('OpenAI streaming response did not produce a valid final response', $lastTurn->metadata('exception_message'));
        $this->assertSame('RuntimeException', $lastTurn->metadata('exception_class'));
        $this->assertSame(502, $lastTurn->metadata('exception_code'));
    }
}
