<?php

declare(strict_types=1);

namespace Drupal\Tests\conductor\Kernel;

use Drupal\canvas\Entity\Page;
use Drupal\conductor\Exception\UnableToAssignDraftToEntityException;
use Drupal\conductor\Repository\DraftRepository;
use Drupal\Core\Extension\ThemeInstallerInterface;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\conductor\Trait\DraftTrait;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;

/**
 * Draft repository test.
 */
#[Group("conductor")]
final class DraftRepositoryTest extends KernelTestBase {

  use DraftTrait;

  private DraftRepository $draftRepository;

  private Page $page;

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'system',
    'canvas',
    'file',
    'image',
    'link',
    'path',
    'path_alias',
    'media',
    'user',
    'text',
    'options',
    'node',
    'filter',
    'field',
    'editor',
    'ckeditor5',
    'conductor',
    'key',
  ];

  public function setUp(): void {
    parent::setUp();
    $this->draftRepository = $this->container->get(DraftRepository::class);
    $this->installEntitySchema('user');
    $this->installEntitySchema('canvas_page');
    $this->installEntitySchema('node');
    $this->installEntitySchema('media');
    $this->installSchema('conductor', 'conductor_draft_map');
    $this->container->get(ThemeInstallerInterface::class)->install(['stark']);
    $this->config('system.theme')->set('default', 'stark')->save();
    $this->installConfig(['system', 'conductor', 'canvas', 'key']);

    $this->page = Page::create([
      'title' => 'asdf',
      'status' => TRUE,
    ]);
    $this->page->save();
  }

  private function createTestCanvasPage(string $title = 'Test page'): Page {
    $canvas_page = Page::create([
      'title' => $title,
      'status' => TRUE,
    ]);
    $canvas_page->save();
    return $canvas_page;
  }

  /**
   * Tests that getDraft and getDrafts work as expected.
   */
  public function testGetDrafts(): void {
    $drafts = $this->draftRepository->getDrafts();
    $this->assertCount(0, $drafts);

    $page1 = $this->createTestCanvasPage();
    $this->draftRepository->createDraft($page1->getEntityType()->id(), (int) $page1->id(), 'draft-1');
    $drafts = $this->draftRepository->getDrafts();
    $draft = $this->draftRepository->getDraft($page1->getEntityType()->id(), (int) $page1->id());
    $this->assertNotEmpty($draft);
    $this->assertArrayHasKey('draft_id', $draft);
    $this->assertSame('draft-1', $draft['draft_id']);
    $this->assertCount(1, $drafts);
    $this->assertEquals($draft, $drafts[0]);
    $page2 = $this->createTestCanvasPage();
    $this->draftRepository->createDraft($page2->getEntityType()->id(), (int) $page2->id(), 'draft-2');
    $drafts = $this->container->get(DraftRepository::class)->getDrafts();
    $this->assertCount(2, $drafts);
    $draft = $this->draftRepository->getDraft($page2->getEntityType()->id(), (int) $page2->id());
    $this->assertNotEmpty($draft);
    $this->assertArrayHasKey('draft_id', $draft);
    $this->assertSame('draft-2', $draft['draft_id']);

    // Test that a wrong entity-type returns an empty array and don't fail
    $draft = $this->draftRepository->getDraft('wrong_entity_type', 1);
    $this->assertEmpty($draft);
  }

  public function testDraftLimits(): void {
    // Want to test that the drafts are correctly limited to the current year.
    $this->createHistoricalDraft((int) date('Y') - 1);
    $this->createHistoricalDraft(2012);
    $this->container->get('config.factory')->getEditable('conductor.settings')->set('max_drafts', 1)->save();
    $page1 = $this->createTestCanvasPage();
    $this->assertSame(0, $this->draftRepository->getUsedDrafts());
    $this->assertSame(1, $this->draftRepository->getMaxDrafts());
    $this->draftRepository->createDraft($page1->getEntityType()->id(), (int) $page1->id(), 'draft-1');
    $this->assertSame(1, $this->draftRepository->getUsedDrafts());
    $this->assertSame(1, $this->draftRepository->getMaxDrafts());

    $page2 = $this->createTestCanvasPage();
    $this->expectException(UnableToAssignDraftToEntityException::class);
    $this->expectExceptionMessage('Maximum number of drafts reached for this year.');
    $this->draftRepository->createDraft($page2->getEntityType()->id(), (int) $page2->id(), 'draft-2');
    // No new draft should be created.
    $this->assertCount(1, $this->draftRepository->getDrafts());
    $this->assertSame(1, $this->draftRepository->getUsedDrafts());
  }

  #[DataProvider('createExceptionsProvider')]
  public function testUnableToCreateDraftsIfEntityDoesNotExist(string $entity_type, int $entity_id, string $expectedExceptionMessage): void {
    $this->expectException(UnableToAssignDraftToEntityException::class);
    $this->expectExceptionMessage($expectedExceptionMessage);
    // Expect not being able to create a draft for an entity that doesn't exist.
    $this->draftRepository->createDraft($entity_type, $entity_id, $this->randomString());
  }

  public static function createExceptionsProvider(): \Generator {
    yield ['node', 1, 'node 1 not found'];
    yield ['canvas_page', 12, 'canvas_page 12 not found'];
    yield ['media', 1, 'media 1 not found'];
  }

  public function testDeleteDraft(): void {
    // Creating without the schema is already tested in `testUnableToCreateDraftsIfEntityDoesNotExist`
    $page1 = $this->createTestCanvasPage();
    $draft_id = $this->randomString();
    $this->draftRepository->createDraft($page1->getEntityType()->id(), (int) $page1->id(), $draft_id);
    $draft = $this->draftRepository->getDraft($page1->getEntityType()->id(), (int) $page1->id());
    $this->assertNotEmpty($draft);
    $this->assertArrayHasKey('draft_id', $draft);
    $this->assertSame($draft_id, $draft['draft_id']);

    // The draft exist, test delete a non-existing draft.
    $this->assertFalse($this->draftRepository->deleteDraft('node', (int) $page1->id()));
    // But this should return TRUE, since the draft exists.
    $this->assertTrue($this->draftRepository->deleteDraft($page1->getEntityType()->id(), (int) $page1->id()));
    $draft = $this->draftRepository->getDraft($page1->getEntityType()->id(), (int) $page1->id());
    $this->assertNotEmpty($draft);
    $this->assertSame('1', $draft['deleted']);
  }

  public function testUpdateDraft(): void {
    $page = $this->createTestCanvasPage();
    $draft_id = $this->randomString();
    $this->draftRepository->createDraft('canvas_page', (int) $page->id(), $draft_id);
    $draft = $this->draftRepository->getDraft('canvas_page', (int) $page->id());
    $this->assertNotEmpty($draft);
    $this->assertArrayHasKey('draft_id', $draft);
    $this->assertSame($draft_id, $draft['draft_id']);
    // Forcing to have a different `changed` timestamp.
    sleep(1);
    $this->draftRepository->updateDraft('canvas_page', (int) $page->id(), $this->randomString());
    $updated_draft = $this->draftRepository->getDraft('canvas_page', (int) $page->id());
    $this->assertNotEmpty($updated_draft);
    $this->assertArrayHasKey('draft_id', $updated_draft);
    $this->assertNotSame($draft_id, $updated_draft['draft_id']);
    $this->assertNotSame($updated_draft['changed'], $draft['changed']);
  }

}
