<?php
namespace boru\ai\Tests\Conversation;

use boru\ai\Conversation\Conversation;
use boru\ai\Conversation\ConversationForkService;
use boru\ai\Conversation\ConversationNote;
use boru\ai\Conversation\ConversationReference;
use boru\ai\Conversation\Turn;
use boru\ai\Schema\Request;
use boru\ai\Schema\Response;
use boru\ai\Tests\Fakes\FakePersistenceProvider;
use PHPUnit\Framework\TestCase;

class ConversationForkServiceTest extends TestCase
{
    public function testForkCopiesConversationThroughSelectedTurnWithLineageMetadata()
    {
        $source = $this->buildConversation();
        $persistence = new FakePersistenceProvider();
        $persistence->loadedConversation = $source;

        $service = new ConversationForkService();
        $target = $service->fork($persistence, 'source_ref', 'target_ref', array(
            'through_turn' => 1,
        ));

        $this->assertSame('target_ref', $target->reference()->externalReference());
        $this->assertNull($target->reference()->id());
        $this->assertSame('gemini', $target->provider());
        $this->assertSame('gemini-2.5-pro', $target->model());
        $this->assertCount(1, $target->turns());
        $this->assertSame(1, $target->turns()[0]->number());
        $this->assertSame('source_conv', $target->metadata('forked_from')['conversation_id']);
        $this->assertSame('source_ref', $target->metadata('forked_from')['external_reference']);
        $this->assertSame(1, $target->metadata('forked_from')['through_turn']);
        $this->assertSame('planning', $target->metadata('topic'));
        $this->assertCount(1, $persistence->savedConversations);
        $this->assertCount(1, $persistence->savedTurns);
    }

    public function testForkCopiesMemoryAndNotesByDefault()
    {
        $source = $this->buildConversation();
        $persistence = new FakePersistenceProvider();
        $persistence->loadedConversation = $source;

        $target = (new ConversationForkService())->fork($persistence, 'source_ref', 'target_ref');

        $this->assertSame('Original title', $target->memory()->title());
        $this->assertSame('Original summary', $target->memory()->summary());
        $this->assertSame('memory-value', $target->memory()->metadata('memory-key'));
        $this->assertCount(1, $target->memory()->notes());
        $this->assertNull($target->memory()->notes()[0]->id());
        $this->assertSame('remember this', $target->memory()->notes()[0]->text());
    }

    public function testForkCanOmitNotes()
    {
        $source = $this->buildConversation();
        $persistence = new FakePersistenceProvider();
        $persistence->loadedConversation = $source;

        $target = (new ConversationForkService())->fork($persistence, 'source_ref', 'target_ref', array(
            'copy_notes' => false,
        ));

        $this->assertSame('Original title', $target->memory()->title());
        $this->assertCount(0, $target->memory()->notes());
    }

    public function testForkWithScrubbedProviderContinuityRemovesOpenAIResponseChainData()
    {
        $source = $this->buildConversation('openai', 'gpt-5.1');
        $persistence = new FakePersistenceProvider();
        $persistence->loadedConversation = $source;

        $target = (new ConversationForkService())->fork($persistence, 'source_ref', 'target_ref', array(
            'provider_continuity' => 'scrub',
        ));

        $request = $target->turns()[0]->request();
        $response = $target->turns()[0]->response();

        $this->assertNull($request->option('previous_response_id'));
        $this->assertNull($response->id());
        $this->assertNull($response->raw());
        $this->assertSame('Assistant turn 1', $response->text());
    }

    public function testForkDoesNotShareNestedRequestObjectsWithSource()
    {
        $source = $this->buildConversation();
        $persistence = new FakePersistenceProvider();
        $persistence->loadedConversation = $source;

        $target = (new ConversationForkService())->fork($persistence, 'source_ref', 'target_ref');
        $target->turns()[0]->request()->messages()[0]->parts()[0]->value('Changed in target');

        $this->assertSame('User turn 1', $source->turns()[0]->request()->messages()[0]->parts()[0]->value());
        $this->assertSame('Changed in target', $target->turns()[0]->request()->messages()[0]->parts()[0]->value());
    }

    protected function buildConversation($provider = 'gemini', $model = 'gemini-2.5-pro')
    {
        $reference = new ConversationReference();
        $reference->id('source_conv')->externalReference('source_ref')->userId('user_1');

        $conversation = new Conversation($reference);
        $conversation->provider($provider);
        $conversation->model($model);
        $conversation->metadata('topic', 'planning');
        $conversation->memory()->title('Original title');
        $conversation->memory()->summary('Original summary');
        $conversation->memory()->metadata('memory-key', 'memory-value');

        $note = new ConversationNote();
        $note->id('note_original')->kind('preference')->text('remember this')->confidence(0.9)->sourceTurnNumber(1);
        $conversation->memory()->addNote($note);

        $conversation->addTurn($this->buildTurn(1, $provider, $model));
        $conversation->addTurn($this->buildTurn(2, $provider, $model));

        return $conversation;
    }

    protected function buildTurn($number, $provider, $model)
    {
        $request = new Request();
        $request->provider($provider)->model($model)->addMessage('User turn ' . $number);
        $request->option('previous_response_id', 'resp_previous_' . $number);

        $response = new Response();
        $response->provider($provider)->model($model)->id('resp_' . $number)->status('completed')->text('Assistant turn ' . $number);
        $response->raw(array('id' => 'resp_' . $number));

        $turn = new Turn();
        $turn->number($number)->status('completed')->request($request)->response($response);
        return $turn;
    }
}
