<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop_chat\Unit\Service;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\ImmutableConfig;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\flowdrop_chat\Service\ChatLlmClientInterface;
use Drupal\flowdrop_chat\Service\ChatService;
use Drupal\flowdrop_chat\Service\SystemPromptBuilderInterface;
use Drupal\flowdrop_memory\Service\MemoryManager;
use Drupal\flowdrop_workflow_executor\Service\WorkflowExecutionServiceInterface;
use Drupal\Tests\UnitTestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Psr\Log\LoggerInterface;

/**
 * Tests the ChatService orchestration service.
 *
 * @coversDefaultClass \Drupal\flowdrop_chat\Service\ChatService
 * @group flowdrop_chat
 */
class ChatServiceTest extends UnitTestCase {

  use ProphecyTrait;

  /**
   * The LLM client mock.
   *
   * @var \Prophecy\Prophecy\ObjectProphecy|\Drupal\flowdrop_chat\Service\ChatLlmClientInterface
   */
  private $llmClient;

  /**
   * The system prompt builder mock.
   *
   * @var \Prophecy\Prophecy\ObjectProphecy|\Drupal\flowdrop_chat\Service\SystemPromptBuilderInterface
   */
  private $systemPromptBuilder;

  /**
   * The memory manager mock.
   *
   * @var \Prophecy\Prophecy\ObjectProphecy|\Drupal\flowdrop_memory\Service\MemoryManager
   */
  private $memoryManager;

  /**
   * The current user mock.
   *
   * @var \Prophecy\Prophecy\ObjectProphecy|\Drupal\Core\Session\AccountProxyInterface
   */
  private $currentUser;

  /**
   * The config factory mock.
   *
   * @var \Prophecy\Prophecy\ObjectProphecy|\Drupal\Core\Config\ConfigFactoryInterface
   */
  private $configFactory;

  /**
   * The config mock.
   *
   * @var \Prophecy\Prophecy\ObjectProphecy|\Drupal\Core\Config\ImmutableConfig
   */
  private $config;

  /**
   * The workflow execution service mock.
   *
   * @var \Prophecy\Prophecy\ObjectProphecy|\Drupal\flowdrop_workflow_executor\Service\WorkflowExecutionServiceInterface
   */
  private $workflowExecutionService;

  /**
   * The entity type manager mock.
   *
   * @var \Prophecy\Prophecy\ObjectProphecy|\Drupal\Core\Entity\EntityTypeManagerInterface
   */
  private $entityTypeManager;

  /**
   * The logger mock.
   *
   * @var \Prophecy\Prophecy\ObjectProphecy|\Psr\Log\LoggerInterface
   */
  private $logger;

  /**
   * The service under test.
   *
   * @var \Drupal\flowdrop_chat\Service\ChatService
   */
  private ChatService $chatService;

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();

    $this->llmClient = $this->prophesize(ChatLlmClientInterface::class);
    $this->systemPromptBuilder = $this->prophesize(SystemPromptBuilderInterface::class);
    $this->memoryManager = $this->prophesize(MemoryManager::class);
    $this->currentUser = $this->prophesize(AccountProxyInterface::class);
    $this->configFactory = $this->prophesize(ConfigFactoryInterface::class);
    $this->config = $this->prophesize(ImmutableConfig::class);
    $this->workflowExecutionService = $this->prophesize(WorkflowExecutionServiceInterface::class);
    $this->entityTypeManager = $this->prophesize(EntityTypeManagerInterface::class);
    $this->logger = $this->prophesize(LoggerInterface::class);

    $this->currentUser->id()->willReturn('42');
    $this->configFactory->get('flowdrop_chat.settings')
      ->willReturn($this->config->reveal());
    $this->config->get('processing_mode')->willReturn(ChatService::MODE_BUILTIN);
    $this->config->get('max_history_length')->willReturn(50);
    $this->config->get('memory_backend')->willReturn('entity');

    $this->chatService = $this->buildService();
  }

  /**
   * Builds the service under test with current mock state.
   */
  private function buildService(): ChatService {
    return new ChatService(
      $this->llmClient->reveal(),
      $this->systemPromptBuilder->reveal(),
      $this->memoryManager->reveal(),
      $this->currentUser->reveal(),
      $this->configFactory->reveal(),
      $this->workflowExecutionService->reveal(),
      $this->entityTypeManager->reveal(),
      $this->logger->reveal(),
    );
  }

  /**
   * Tests chat() loads history from MemoryManager in builtin mode.
   *
   * @covers ::chat
   */
  public function testChatLoadsHistoryFromMemory(): void {
    $storedHistory = [
      ['role' => 'user', 'content' => 'Previous message'],
      ['role' => 'assistant', 'content' => 'Previous response'],
    ];

    $this->memoryManager->get('chat', 'wf1:42', 'history', [], 'entity')
      ->willReturn($storedHistory)
      ->shouldBeCalled();

    $this->systemPromptBuilder->build([], $storedHistory)
      ->willReturn('system prompt')
      ->shouldBeCalled();

    $this->llmClient->chat('system prompt', 'Hello', ['flowdrop_chat'])
      ->willReturn('LLM response');

    $this->memoryManager->set('chat', 'wf1:42', 'history', Argument::type('array'), NULL, 'entity')
      ->shouldBeCalled();

    $result = $this->chatService->chat('wf1', 'Hello', []);

    $this->assertSame('LLM response', $result['content']);
    $this->assertSame('wf1', $result['conversationId']);
  }

  /**
   * Tests system prompt is built with correct workflowState and history.
   *
   * @covers ::chat
   */
  public function testSystemPromptBuiltWithCorrectArguments(): void {
    $workflowState = ['nodes' => [['id' => 'llm_node.1']], 'edges' => []];
    $history = [['role' => 'user', 'content' => 'Hi']];

    $this->memoryManager->get('chat', 'wf1:42', 'history', [], 'entity')
      ->willReturn($history);

    $this->systemPromptBuilder->build($workflowState, $history)
      ->willReturn('built prompt')
      ->shouldBeCalled();

    $this->llmClient->chat('built prompt', 'Do something', ['flowdrop_chat'])
      ->willReturn('Done');

    $this->memoryManager->set(Argument::cetera())->shouldBeCalled();

    $this->chatService->chat('wf1', 'Do something', $workflowState);
  }

  /**
   * Tests that LLM response is returned in expected format.
   *
   * @covers ::chat
   */
  public function testChatReturnFormat(): void {
    $this->memoryManager->get(Argument::cetera())->willReturn([]);
    $this->systemPromptBuilder->build(Argument::cetera())->willReturn('prompt');
    $this->llmClient->chat(Argument::cetera())->willReturn('The LLM says hello');
    $this->memoryManager->set(Argument::cetera())->shouldBeCalled();

    $result = $this->chatService->chat('workflow-123', 'Hi', []);

    $this->assertArrayHasKey('content', $result);
    $this->assertArrayHasKey('conversationId', $result);
    $this->assertSame('The LLM says hello', $result['content']);
    $this->assertSame('workflow-123', $result['conversationId']);
  }

  /**
   * Tests that history is appended with user message + assistant response.
   *
   * @covers ::chat
   */
  public function testHistoryAppendedAndPersisted(): void {
    $existingHistory = [
      ['role' => 'user', 'content' => 'First'],
      ['role' => 'assistant', 'content' => 'Reply'],
    ];

    $this->memoryManager->get('chat', 'wf1:42', 'history', [], 'entity')
      ->willReturn($existingHistory);

    $this->systemPromptBuilder->build(Argument::cetera())->willReturn('prompt');
    $this->llmClient->chat(Argument::cetera())->willReturn('New reply');

    $expectedHistory = [
      ['role' => 'user', 'content' => 'First'],
      ['role' => 'assistant', 'content' => 'Reply'],
      ['role' => 'user', 'content' => 'Second'],
      ['role' => 'assistant', 'content' => 'New reply'],
    ];

    $this->memoryManager->set('chat', 'wf1:42', 'history', $expectedHistory, NULL, 'entity')
      ->shouldBeCalled();

    $this->chatService->chat('wf1', 'Second', []);
  }

  /**
   * Tests that history is trimmed to max_history_length.
   *
   * @covers ::chat
   */
  public function testHistoryTrimmedToMaxLength(): void {
    $this->config->get('max_history_length')->willReturn(4);
    $this->chatService = $this->buildService();

    $existingHistory = [
      ['role' => 'user', 'content' => 'Old message 1'],
      ['role' => 'assistant', 'content' => 'Old reply 1'],
      ['role' => 'user', 'content' => 'Old message 2'],
      ['role' => 'assistant', 'content' => 'Old reply 2'],
    ];

    $this->memoryManager->get('chat', 'wf1:42', 'history', [], 'entity')
      ->willReturn($existingHistory);

    $this->systemPromptBuilder->build(Argument::cetera())->willReturn('prompt');
    $this->llmClient->chat(Argument::cetera())->willReturn('Latest reply');

    // After appending 2 new entries (6 total), trimmed to last 4.
    $expectedHistory = [
      ['role' => 'user', 'content' => 'Old message 2'],
      ['role' => 'assistant', 'content' => 'Old reply 2'],
      ['role' => 'user', 'content' => 'New message'],
      ['role' => 'assistant', 'content' => 'Latest reply'],
    ];

    $this->memoryManager->set('chat', 'wf1:42', 'history', $expectedHistory, NULL, 'entity')
      ->shouldBeCalled();

    $this->chatService->chat('wf1', 'New message', []);
  }

  /**
   * Tests getHistory() loads from MemoryManager.
   *
   * @covers ::getHistory
   */
  public function testGetHistoryLoadsFromMemory(): void {
    $storedHistory = [
      ['role' => 'user', 'content' => 'Hello'],
      ['role' => 'assistant', 'content' => 'Hi there'],
    ];

    $this->memoryManager->get('chat', 'wf1:42', 'history', [], 'entity')
      ->willReturn($storedHistory)
      ->shouldBeCalled();

    $result = $this->chatService->getHistory('wf1');

    $this->assertSame($storedHistory, $result);
  }

  /**
   * Tests clearHistory() deletes from MemoryManager.
   *
   * @covers ::clearHistory
   */
  public function testClearHistoryDeletesFromMemory(): void {
    $this->memoryManager->delete('chat', 'wf1:42', 'history', 'entity')
      ->shouldBeCalled();

    $this->chatService->clearHistory('wf1');
  }

}
