<?php

declare(strict_types=1);

namespace Drupal\Tests\ai_chatbot\Unit;

use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Theme\ActiveTheme;
use Drupal\Core\Theme\ThemeManagerInterface;
use Drupal\Tests\UnitTestCase;
use Drupal\ai_chatbot\Hook\ChatbotHooks;
use Drupal\block\Entity\Block;

/**
 * Tests the ai_chatbot hook implementations.
 *
 * @group ai_chatbot
 */
class ChatbotHooksTest extends UnitTestCase {

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

    $container = new ContainerBuilder();
    $container->set('string_translation', $this->getStringTranslationStub());
    \Drupal::setContainer($container);
  }

  /**
   * Tests topbar() is a no-op without the "access deepchat api" permission.
   */
  public function testTopbarSkippedWithoutPermission(): void {
    $currentUser = $this->createMock(AccountProxyInterface::class);
    $currentUser->method('hasPermission')->with('access deepchat api')->willReturn(FALSE);

    $entityTypeManager = $this->createMock(EntityTypeManagerInterface::class);
    $entityTypeManager->expects($this->never())->method('getStorage');

    $hooks = new ChatbotHooks($currentUser, $entityTypeManager, $this->createMock(ThemeManagerInterface::class));

    $variables = [];
    $hooks->topbar($variables);

    $this->assertSame([], $variables);
  }

  /**
   * Tests topbar() adds the cache tag but no button when no block exists.
   */
  public function testTopbarSkipsButtonWithoutDeepChatBlock(): void {
    $hooks = $this->createHooks(TRUE, []);

    $variables = [];
    $hooks->topbar($variables);

    $this->assertSame(['block:ai_deepchat_block'], $variables['#cache']['tags']);
    $this->assertArrayNotHasKey('tools', $variables);
  }

  /**
   * Tests topbar() adds the button when an enabled DeepChat block exists.
   */
  public function testTopbarAddsButtonWithEnabledDeepChatBlock(): void {
    $hooks = $this->createHooks(TRUE, [$this->mockBlock(TRUE)]);

    $variables = [];
    $hooks->topbar($variables);

    $this->assertCount(1, $variables['tools']);
    $this->assertSame('button', $variables['tools'][0]['#tag']);
  }

  /**
   * Tests topbar() skips the button when the DeepChat block is disabled.
   */
  public function testTopbarSkipsButtonWithDisabledDeepChatBlock(): void {
    $hooks = $this->createHooks(TRUE, [$this->mockBlock(FALSE)]);

    $variables = [];
    $hooks->topbar($variables);

    $this->assertArrayNotHasKey('tools', $variables);
  }

  /**
   * Tests toolbar() returns nothing without permission.
   */
  public function testToolbarSkippedWithoutPermission(): void {
    $currentUser = $this->createMock(AccountProxyInterface::class);
    $currentUser->method('hasPermission')->with('access deepchat api')->willReturn(FALSE);

    $hooks = new ChatbotHooks($currentUser, $this->createMock(EntityTypeManagerInterface::class), $this->createMock(ThemeManagerInterface::class));

    $this->assertNull($hooks->toolbar());
  }

  /**
   * Tests toolbar() returns the assistant tab when an enabled block exists.
   */
  public function testToolbarReturnsItemWithEnabledDeepChatBlock(): void {
    $hooks = $this->createHooks(TRUE, [$this->mockBlock(TRUE)]);

    $items = $hooks->toolbar();

    $this->assertArrayHasKey('ai_chatbot', $items);
    $this->assertSame(['block:ai_deepchat_block'], $items['ai_chatbot']['#cache']['tags']);
  }

  /**
   * Tests theme_suggestions_ai_deepchat_alter() adds a placement suggestion.
   */
  public function testThemeSuggestionsAiDeepchatAlterAddsPlacementSuggestion(): void {
    $hooks = new ChatbotHooks(
      $this->createMock(AccountProxyInterface::class),
      $this->createMock(EntityTypeManagerInterface::class),
      $this->createMock(ThemeManagerInterface::class),
    );

    $suggestions = [];
    $hooks->themeSuggestionsAiDeepchatAlter($suggestions, ['settings' => ['placement' => 'bottom-right']]);

    $this->assertSame(['ai_deepchat__bottom_right'], $suggestions);
  }

  /**
   * Tests hasDeepChatBlock() fails gracefully when the theme lookup throws.
   */
  public function testTopbarFailsGracefullyOnException(): void {
    $currentUser = $this->createMock(AccountProxyInterface::class);
    $currentUser->method('hasPermission')->willReturn(TRUE);

    $themeManager = $this->createMock(ThemeManagerInterface::class);
    $themeManager->method('getActiveTheme')->willThrowException(new \Exception('No active theme.'));

    $hooks = new ChatbotHooks($currentUser, $this->createMock(EntityTypeManagerInterface::class), $themeManager);

    $variables = [];
    $hooks->topbar($variables);

    $this->assertArrayNotHasKey('tools', $variables);
  }

  /**
   * Builds a ChatbotHooks instance wired with the given blocks.
   *
   * @param bool $hasPermission
   *   Whether the mocked current user has the deepchat permission.
   * @param array $blocks
   *   Blocks returned by the block storage.
   */
  private function createHooks(bool $hasPermission, array $blocks): ChatbotHooks {
    $currentUser = $this->createMock(AccountProxyInterface::class);
    $currentUser->method('hasPermission')->with('access deepchat api')->willReturn($hasPermission);

    $blockStorage = $this->createMock(EntityStorageInterface::class);
    $blockStorage->method('loadByProperties')
      ->with(['theme' => 'olivero', 'plugin' => 'ai_deepchat_block'])
      ->willReturn($blocks);

    $entityTypeManager = $this->createMock(EntityTypeManagerInterface::class);
    $entityTypeManager->method('getStorage')->with('block')->willReturn($blockStorage);

    $activeTheme = $this->createMock(ActiveTheme::class);
    $activeTheme->method('getName')->willReturn('olivero');

    $themeManager = $this->createMock(ThemeManagerInterface::class);
    $themeManager->method('getActiveTheme')->willReturn($activeTheme);

    return new ChatbotHooks($currentUser, $entityTypeManager, $themeManager);
  }

  /**
   * Creates a mocked block entity with the given enabled status.
   */
  private function mockBlock(bool $status): Block {
    $block = $this->createMock(Block::class);
    $block->method('get')->with('status')->willReturn($status);

    return $block;
  }

}
