<?php

declare(strict_types=1);

namespace Drupal\Tests\conductor\Functional;

use Drupal\Component\Serialization\Json;
use Drupal\conductor\Repository\DraftRepository;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\Tests\ApiRequestTrait;
use GuzzleHttp\RequestOptions;
use PHPUnit\Framework\Attributes\Group;
use Symfony\Component\HttpFoundation\Response;

/**
 * Test for the Draft api.
 */
#[Group("conductor")]
final class ConductorDraftApiControllerTest extends ConductorBrowserTestBase {

  use ApiRequestTrait;

  /**
   * {@inheritdoc}
   *
   * Enable only the module under test for now.
   */
  protected static $modules = [
    'conductor',
    'node',
    'canvas',
  ];

  /**
   * {@inheritdoc}
   *
   * Use a minimal core theme to avoid unrelated dependencies.
   */
  protected $defaultTheme = 'stark';

  /**
   * {@inheritdoc}
   */
  public function setUp(): void {
    parent::setUp();
    $this->generateKey();

    // Create article content type.
    $this->drupalCreateContentType([
      'type' => 'article',
      'name' => 'Article',
    ]);
  }

  /**
   * Utility function to generate a draft.
   *
   * @param string $entity_type
   *   The entity type.
   * @param int $entity_id
   *   The entity id.
   * @param string $draft_id
   *   The draft id.
   *
   * @return void
   *
   * @throws \Drupal\conductor\Exception\UnableToAssignDraftToEntityException
   */
  private function generateDraft(string $entity_type, int $entity_id, string $draft_id): void {
    $this->container->get(DraftRepository::class)->createDraft($entity_type, $entity_id, $draft_id);
  }

  public function testGetDrafts(): void {
    $url = Url::fromRoute('conductor.api.draft', [
      'entity_type' => 'canvas_page',
      'entity_id' => 1,
    ]);

    $anonymous_user = $this->createUser(['access content']);
    \assert($anonymous_user instanceof AccountInterface);
    $this->drupalLogin($anonymous_user);
    $this->drupalGet($url);
    // Anonymous users should not have access to the draft API.
    $this->assertSession()->statusCodeEquals(403);

    $conductor_user = $this->createUser(['use conductor']);
    \assert($conductor_user instanceof AccountInterface);
    $this->drupalLogin($conductor_user);
    $this->drupalGet($url);
    // There should not be any drafts for this node.
    $this->assertSession()->statusCodeEquals(404);

    // Can create drafts for existing nodes.
    $page = $this->generateBasicCanvasPage();
    $this->generateDraft($page->getEntityType()->id(), (int) $page->id(), '12345');
    $url = Url::fromRoute('conductor.api.draft', [
      'entity_type' => $page->getEntityType()->id(),
      'entity_id' => (int) $page->id(),
    ]);

    // Now that we have a draft, we should be able to retrieve it.
    $this->drupalGet($url);
    $this->assertSession()->statusCodeEquals(200);
    $this->assertSession()->responseContains('12345');
  }

  public function testCreateDraft(): void {
    $url = Url::fromRoute('conductor.api.draft', [
      'entity_type' => 'canvas_page',
      'entity_id' => 1,
    ]);

    // Anonymous users can't create drafts.
    $anonymous_user = $this->createUser(['access content']);
    \assert($anonymous_user instanceof AccountInterface);
    $this->drupalLogin($anonymous_user);
    $request_options = [
      RequestOptions::HEADERS => [
        'Content-Type' => 'application/json',
        'X-CSRF-Token' => $this->drupalGet('session/token'),
      ],
      RequestOptions::BODY => Json::encode(['draftId' => '12345']),
    ];
    $response = $this->makeApiRequest('POST', $url, $request_options);
    $this->assertSame(Response::HTTP_FORBIDDEN, $response->getStatusCode());

    // Conductor users.
    // ----------------
    $conductor_user = $this->createUser(['use conductor']);
    \assert($conductor_user instanceof AccountInterface);
    $this->drupalLogin($conductor_user);
    $request_options = [
      RequestOptions::HEADERS => [
        'Content-Type' => 'application/json',
        'X-CSRF-Token' => $this->drupalGet('session/token'),
      ],
      RequestOptions::BODY => Json::encode(['draftId' => '12345']),
    ];

    // Can't create drafts for non-existing nodes.
    $response = $this->makeApiRequest('POST', $url, $request_options);
    $this->assertSame(Response::HTTP_UNPROCESSABLE_ENTITY, $response->getStatusCode());

    // Can create drafts for existing nodes.
    $page = $this->generateBasicCanvasPage();
    $url = Url::fromRoute('conductor.api.draft', [
      'entity_type' => $page->getEntityType()->id(),
      'entity_id' => (int) $page->id(),
    ]);
    $response = $this->makeApiRequest('POST', $url, $request_options);
    $this->assertSame(Response::HTTP_CREATED, $response->getStatusCode());

    // Create again the same draft, but it shouldn't fail.
    $response = $this->makeApiRequest('POST', $url, $request_options);
    $this->assertSame(Response::HTTP_OK, $response->getStatusCode());
  }

  public function testUpdateDraft(): void {
    $page = $this->generateBasicCanvasPage();
    $url = Url::fromRoute('conductor.api.draft', [
      'entity_type' => $page->getEntityType()->id(),
      'entity_id' => (int) $page->id(),
    ]);
    $this->generateDraft($page->getEntityType()->id(), (int) $page->id(), '12345');

    // Anonymous users can't update drafts.
    $anonymous_user = $this->createUser(['access content']);
    \assert($anonymous_user instanceof AccountInterface);
    $this->drupalLogin($anonymous_user);
    $request_options = [
      RequestOptions::HEADERS => [
        'Content-Type' => 'application/json',
        'X-CSRF-Token' => $this->drupalGet('session/token'),
      ],
      RequestOptions::BODY => Json::encode(['draftId' => '12345']),
    ];
    $response = $this->makeApiRequest('PUT', $url, $request_options);
    $this->assertSame(Response::HTTP_FORBIDDEN, $response->getStatusCode());

    // Conductor users.
    // ---------------
    // Update the draft.
    $conductor_user = $this->createUser(['use conductor']);
    \assert($conductor_user instanceof AccountInterface);
    $this->drupalLogin($conductor_user);
    $request_options = [
      RequestOptions::HEADERS => [
        'Content-Type' => 'application/json',
        'X-CSRF-Token' => $this->drupalGet('session/token'),
      ],
      RequestOptions::BODY => Json::encode(['draftId' => '54321']),
    ];
    $response = $this->makeApiRequest('PUT', $url, $request_options);
    $this->assertSame(Response::HTTP_OK, $response->getStatusCode());

    // Expect this new draft to be changed.
    $this->drupalGet($url);
    $this->assertSession()->statusCodeEquals(Response::HTTP_OK);
    $this->assertSession()->responseContains('54321');
  }

  public function testDeleteDraft(): void {
    $page = $this->generateBasicCanvasPage();
    $url = Url::fromRoute('conductor.api.draft', [
      'entity_type' => $page->getEntityType()->id(),
      'entity_id' => (int) $page->id(),
    ]);
    $this->generateDraft($page->getEntityType()->id(), (int) $page->id(), '12345');

    // Anonymous users can't delete drafts.
    $anonymous_user = $this->createUser(['access content']);
    \assert($anonymous_user instanceof AccountInterface);
    $this->drupalLogin($anonymous_user);
    $request_options = [
      RequestOptions::HEADERS => [
        'Content-Type' => 'application/json',
        'X-CSRF-Token' => $this->drupalGet('session/token'),
      ],
      RequestOptions::BODY => Json::encode(['draftId' => '12345']),
    ];
    $response = $this->makeApiRequest('DELETE', $url, $request_options);
    $this->assertSame(Response::HTTP_FORBIDDEN, $response->getStatusCode());

    // Conductor users.
    // ---------------
    // Delete the draft.
    $conductor_user = $this->createUser(['use conductor']);
    \assert($conductor_user instanceof AccountInterface);
    $this->drupalLogin($conductor_user);
    $request_options = [
      RequestOptions::HEADERS => [
        'Content-Type' => 'application/json',
        'X-CSRF-Token' => $this->drupalGet('session/token'),
      ],
    ];
    $response = $this->makeApiRequest('DELETE', $url, $request_options);
    $this->assertSame(Response::HTTP_OK, $response->getStatusCode());

    // Expect this draft to be deleted.
    $this->drupalGet($url);
    $this->assertSession()->statusCodeEquals(Response::HTTP_OK);
    $this->assertStringContainsString('Draft deleted for entity canvas_page 1', (string) $response->getBody());

    // Delete an unexisting draft.
    $url = Url::fromRoute('conductor.api.draft', [
      'entity_type' => 'entity_type_does_not_exist',
      'entity_id' => 9999,
    ]);
    $response = $this->makeApiRequest('DELETE', $url, $request_options);
    $this->assertSame(Response::HTTP_NOT_FOUND, $response->getStatusCode());

  }

}
