<?php

declare(strict_types=1);

namespace Drupal\flowdrop_playground\Controller;

use Drupal\flowdrop_workflow\FlowDropWorkflowInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Session\AccountInterface;
use Drupal\flowdrop\Constants\Permissions as FlowDropPermissions;
use Drupal\flowdrop\Controller\Api\ApiResponseTrait;
use Drupal\flowdrop_playground\Constants\Permissions;
use Drupal\flowdrop_session\Constants\Permissions as SessionPermissions;
use Drupal\flowdrop_session\Entity\FlowDropSessionInterface;
use Drupal\flowdrop_session\Entity\FlowDropSessionMessageInterface;
use Drupal\flowdrop_session\Service\SessionService;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

/**
 * API Controller for FlowDrop Playground operations.
 *
 * Provides RESTful endpoints for managing playground sessions and messages.
 *
 * @internal This class is not part of the public API and may change without notice.
 */
final class PlaygroundApiController extends ControllerBase {

  use ApiResponseTrait;

  /**
   * The session service.
   *
   * @var \Drupal\flowdrop_session\Service\SessionService
   */
  protected SessionService $sessionService;

  /**
   * Constructs a PlaygroundApiController object.
   *
   * @param \Drupal\flowdrop_session\Service\SessionService $session_service
   *   The session service.
   */
  public function __construct(
    SessionService $session_service,
  ) {
    $this->sessionService = $session_service;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container): static {
    return new static(
      $container->get('flowdrop_session.service'),
    );
  }

  /**
   * Lists playground sessions for a workflow.
   *
   * @param string $workflow_id
   *   The workflow ID.
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request object.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   JSON response with sessions data.
   */
  public function listWorkflowSessions(string $workflow_id, Request $request): JsonResponse {
    try {
      // Verify workflow exists.
      $workflow_storage = $this->entityTypeManager()->getStorage('flowdrop_workflow');
      $workflow = $workflow_storage->load($workflow_id);

      if ($workflow === NULL) {
        return $this->errorResponse('Workflow not found', Response::HTTP_NOT_FOUND);
      }

      // Get query parameters with standardized defaults.
      $limit = min(100, max(1, (int) $request->query->get('limit', 50)));
      $offset = max(0, (int) $request->query->get('offset', 0));

      // Determine if user can view all sessions or only their own.
      $current_user = $this->currentUser();
      $user_id = NULL;

      if (!$current_user->hasPermission(SessionPermissions::VIEW_ANY)) {
        $user_id = (int) $current_user->id();
      }

      // Get sessions.
      $sessions = $this->sessionService->getSessionsForWorkflow(
        $workflow_id,
        $user_id,
        $limit,
        $offset
      );

      // Format sessions for response.
      $data = [];
      foreach ($sessions as $session) {
        $data[] = $this->sessionService->formatSessionForApi($session);
      }

      // Get total count for pagination.
      $count_query = $this->entityTypeManager()
        ->getStorage('flowdrop_session')
        ->getQuery()
        ->condition('workflow_id', $workflow_id)
        ->accessCheck(TRUE)
        ->count();

      if ($user_id !== NULL) {
        $count_query->condition('uid', $user_id);
      }

      $total = (int) $count_query->execute();

      return $this->paginatedResponse($data, $total, $limit, $offset);

    }
    catch (\Exception $e) {
      $this->getLogger('flowdrop_playground')->error('Error listing sessions: @message', [
        '@message' => $e->getMessage(),
      ]);

      return $this->errorResponse('Internal server error', Response::HTTP_INTERNAL_SERVER_ERROR);
    }
  }

  /**
   * Creates a new playground session for a workflow.
   *
   * @param string $workflow_id
   *   The workflow ID.
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request object.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   JSON response with the created session.
   */
  public function createSession(string $workflow_id, Request $request): JsonResponse {
    try {
      // Verify workflow exists.
      $workflow_storage = $this->entityTypeManager()->getStorage('flowdrop_workflow');
      $workflow = $workflow_storage->load($workflow_id);

      if (!$workflow instanceof FlowDropWorkflowInterface) {
        return $this->errorResponse('Workflow not found', Response::HTTP_NOT_FOUND);
      }

      // Parse request body (optional for session creation).
      $content = $request->getContent();
      $data = [];

      if (!empty($content)) {
        $decoded = json_decode($content, TRUE);
        if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
          $data = $decoded;
        }
      }

      $name = isset($data['name']) && is_string($data['name']) ? $data['name'] : NULL;
      $metadata = isset($data['metadata']) && is_array($data['metadata']) ? $data['metadata'] : [];

      // Create session.
      $session = $this->sessionService->createSession($workflow, $name, $metadata);

      return $this->successResponse(
        $this->sessionService->formatSessionForApi($session),
        Response::HTTP_CREATED
      );

    }
    catch (\Exception $e) {
      $this->getLogger('flowdrop_playground')->error('Error creating session: @message', [
        '@message' => $e->getMessage(),
      ]);

      return $this->errorResponse('Failed to create session', Response::HTTP_INTERNAL_SERVER_ERROR);
    }
  }

  /**
   * Gets a playground session by ID.
   *
   * @param string $session_id
   *   The session UUID.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   JSON response with session data.
   */
  public function getSession(string $session_id): JsonResponse {
    try {
      $session = $this->loadSessionByUuid($session_id);

      if ($session === NULL) {
        return $this->errorResponse('Session not found', Response::HTTP_NOT_FOUND);
      }

      return $this->successResponse(
        $this->sessionService->formatSessionForApi($session)
      );

    }
    catch (\Exception $e) {
      $this->getLogger('flowdrop_playground')->error('Error getting session: @message', [
        '@message' => $e->getMessage(),
      ]);

      return $this->errorResponse('Internal server error', Response::HTTP_INTERNAL_SERVER_ERROR);
    }
  }

  /**
   * Deletes a playground session.
   *
   * @param string $session_id
   *   The session UUID.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   JSON response confirming deletion.
   */
  public function deleteSession(string $session_id): JsonResponse {
    try {
      $session = $this->loadSessionByUuid($session_id);

      if ($session === NULL) {
        return $this->errorResponse('Session not found', Response::HTTP_NOT_FOUND);
      }

      $this->sessionService->deleteSession($session);

      return $this->successResponse(NULL);

    }
    catch (\Exception $e) {
      $this->getLogger('flowdrop_playground')->error('Error deleting session: @message', [
        '@message' => $e->getMessage(),
      ]);

      return $this->errorResponse('Failed to delete session', Response::HTTP_INTERNAL_SERVER_ERROR);
    }
  }

  /**
   * Gets messages from a playground session.
   *
   * @param string $session_id
   *   The session UUID.
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request object.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   JSON response with messages data.
   */
  public function getMessages(string $session_id, Request $request): JsonResponse {
    try {
      $session = $this->loadSessionByUuid($session_id);

      if ($session === NULL) {
        return $this->errorResponse('Session not found', Response::HTTP_NOT_FOUND);
      }

      // Get query parameters with standardized defaults.
      $limit = min(100, max(1, (int) $request->query->get('limit', 50)));
      $offset = max(0, (int) $request->query->get('offset', 0));
      $since = $request->query->get('since');

      $since_timestamp = NULL;
      if ($since !== NULL && is_string($since)) {
        $parsed = strtotime($since);
        if ($parsed !== FALSE) {
          $since_timestamp = $parsed;
        }
      }

      // Get session ID as int.
      $entity_id = $session->id();
      if ($entity_id === NULL) {
        return $this->errorResponse('Invalid session', Response::HTTP_INTERNAL_SERVER_ERROR);
      }

      // Get messages.
      $messages = $this->sessionService->getMessages(
        (int) $entity_id,
        $since_timestamp,
        $limit
      );

      // Format messages for response.
      $data = [];
      foreach ($messages as $message) {
        $data[] = $this->sessionService->formatMessageForApi($message);
      }

      // Get total count for pagination.
      $count_query = $this->entityTypeManager()
        ->getStorage('flowdrop_session_message')
        ->getQuery()
        ->condition('session_id', (int) $entity_id)
        ->accessCheck(TRUE)
        ->count();

      if ($since_timestamp !== NULL) {
        $count_query->condition('timestamp', $since_timestamp, '>=');
      }

      $total = (int) $count_query->execute();

      return new JsonResponse([
        'success' => TRUE,
        'data' => $data,
        'pagination' => [
          'total' => $total,
          'limit' => $limit,
          'offset' => $offset,
          'has_more' => ($offset + $limit) < $total,
        ],
        'sessionStatus' => $session->getStatus(),
      ]);

    }
    catch (\Exception $e) {
      $this->getLogger('flowdrop_playground')->error('Error getting messages: @message', [
        '@message' => $e->getMessage(),
      ]);

      return $this->errorResponse('Internal server error', Response::HTTP_INTERNAL_SERVER_ERROR);
    }
  }

  /**
   * Sends a message to a playground session.
   *
   * @param string $session_id
   *   The session UUID.
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request object.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   JSON response with the sent message.
   */
  public function sendMessage(string $session_id, Request $request): JsonResponse {
    try {
      $session = $this->loadSessionByUuid($session_id);

      if ($session === NULL) {
        return $this->errorResponse('Session not found', Response::HTTP_NOT_FOUND);
      }

      // Check if session is already executing.
      if ($session->isRunning()) {
        return $this->errorResponse('Session is already executing', Response::HTTP_CONFLICT);
      }

      // Parse request body.
      $data = $this->decodeJsonRequest($request);

      // Validate required fields.
      if (!isset($data['content']) || !is_string($data['content'])) {
        return $this->errorResponse('Message content is required', Response::HTTP_BAD_REQUEST);
      }

      $content = $data['content'];
      $inputs = isset($data['inputs']) && is_array($data['inputs']) ? $data['inputs'] : [];

      // Send message and trigger execution.
      $message = $this->sessionService->sendMessage($session, $content, $inputs);

      return $this->successResponse(
        $this->sessionService->formatMessageForApi($message)
      );

    }
    catch (\RuntimeException $e) {
      $this->getLogger('flowdrop_playground')->error('Conflict sending message: @message', [
        '@message' => $e->getMessage(),
      ]);
      return $this->errorResponse('Failed to send message due to a conflict', Response::HTTP_CONFLICT);
    }
    catch (\Exception $e) {
      $this->getLogger('flowdrop_playground')->error('Error sending message: @message', [
        '@message' => $e->getMessage(),
      ]);

      return $this->errorResponse('Failed to send message', Response::HTTP_INTERNAL_SERVER_ERROR);
    }
  }

  /**
   * Stops the execution of a playground session.
   *
   * @param string $session_id
   *   The session UUID.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   JSON response confirming stop.
   */
  public function stopExecution(string $session_id): JsonResponse {
    try {
      $session = $this->loadSessionByUuid($session_id);

      if ($session === NULL) {
        return $this->errorResponse('Session not found', Response::HTTP_NOT_FOUND);
      }

      if (!$session->isRunning()) {
        return $this->errorResponse('No execution is running', Response::HTTP_CONFLICT);
      }

      $this->sessionService->stopExecution($session);

      return $this->successResponse(NULL);

    }
    catch (\Exception $e) {
      $this->getLogger('flowdrop_playground')->error('Error stopping execution: @message', [
        '@message' => $e->getMessage(),
      ]);

      return $this->errorResponse('Failed to stop execution', Response::HTTP_INTERNAL_SERVER_ERROR);
    }
  }

  /**
   * Resets a playground session to idle state.
   *
   * This endpoint allows users to recover from stuck sessions by resetting
   * the session status to 'idle', cancelling any pending or processing
   * messages, and clearing any pending interrupt state.
   *
   * @param string $session_id
   *   The session UUID.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   JSON response confirming reset.
   */
  public function resetSession(string $session_id): JsonResponse {
    try {
      $session = $this->loadSessionByUuid($session_id);

      if ($session === NULL) {
        return $this->errorResponse('Session not found', Response::HTTP_NOT_FOUND);
      }

      $resetCount = $this->sessionService->resetSession($session);

      return $this->successResponse([
        'session' => $this->sessionService->formatSessionForApi($session),
        'resetCount' => $resetCount,
      ]);

    }
    catch (\Exception $e) {
      $this->getLogger('flowdrop_playground')->error('Error resetting session: @message', [
        '@message' => $e->getMessage(),
      ]);

      return $this->errorResponse('Failed to reset session', Response::HTTP_INTERNAL_SERVER_ERROR);
    }
  }

  /**
   * Access callback for session endpoints.
   *
   * @param \Drupal\Core\Session\AccountInterface $account
   *   The user account.
   * @param string $session_id
   *   The session UUID.
   *
   * @return \Drupal\Core\Access\AccessResultInterface
   *   The access result.
   */
  public function accessSession(AccountInterface $account, string $session_id): AccessResultInterface {
    // Global super-admin bypass.
    if ($account->hasPermission(FlowDropPermissions::ADMINISTER_FLOWDROP)) {
      return AccessResult::allowed()->cachePerPermissions();
    }

    // Module administrator access.
    if ($account->hasPermission(Permissions::ADMINISTER)) {
      return AccessResult::allowed()->cachePerPermissions();
    }

    // Check if user can view any session.
    if ($account->hasPermission(SessionPermissions::VIEW_ANY)) {
      return AccessResult::allowed()->cachePerPermissions();
    }

    // Load session to check ownership.
    $session = $this->loadSessionByUuid($session_id);

    if ($session === NULL) {
      return AccessResult::forbidden('Session not found.');
    }

    // Check if user can view their own session.
    if ($account->hasPermission(SessionPermissions::VIEW_OWN)) {
      $is_owner = $session->getOwnerId() === (int) $account->id();
      return AccessResult::allowedIf($is_owner)
        ->cachePerPermissions()
        ->cachePerUser()
        ->addCacheableDependency($session);
    }

    return AccessResult::forbidden()->cachePerPermissions();
  }

  /**
   * Access callback for delete session endpoint.
   *
   * @param \Drupal\Core\Session\AccountInterface $account
   *   The user account.
   * @param string $session_id
   *   The session UUID.
   *
   * @return \Drupal\Core\Access\AccessResultInterface
   *   The access result.
   */
  public function accessDeleteSession(AccountInterface $account, string $session_id): AccessResultInterface {
    // Global super-admin bypass.
    if ($account->hasPermission(FlowDropPermissions::ADMINISTER_FLOWDROP)) {
      return AccessResult::allowed()->cachePerPermissions();
    }

    // Module administrator access.
    if ($account->hasPermission(Permissions::ADMINISTER)) {
      return AccessResult::allowed()->cachePerPermissions();
    }

    // Check if user can delete any session.
    if ($account->hasPermission(SessionPermissions::DELETE_ANY)) {
      return AccessResult::allowed()->cachePerPermissions();
    }

    // Load session to check ownership.
    $session = $this->loadSessionByUuid($session_id);

    if ($session === NULL) {
      return AccessResult::forbidden('Session not found.');
    }

    // Check if user can delete their own session.
    if ($account->hasPermission(SessionPermissions::DELETE_OWN)) {
      $is_owner = $session->getOwnerId() === (int) $account->id();
      return AccessResult::allowedIf($is_owner)
        ->cachePerPermissions()
        ->cachePerUser()
        ->addCacheableDependency($session);
    }

    return AccessResult::forbidden()->cachePerPermissions();
  }

  /**
   * Gets a single message by ID.
   *
   * @param string $session_id
   *   The session UUID.
   * @param string $message_id
   *   The message UUID.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   JSON response with message data.
   */
  public function getMessage(string $session_id, string $message_id): JsonResponse {
    try {
      $session = $this->loadSessionByUuid($session_id);

      if ($session === NULL) {
        return $this->errorResponse('Session not found', Response::HTTP_NOT_FOUND);
      }

      $message = $this->loadMessageByUuid($message_id);

      if ($message === NULL) {
        return $this->errorResponse('Message not found', Response::HTTP_NOT_FOUND);
      }

      // Verify message belongs to session.
      if ($message->getSessionId() !== $session->id()) {
        return $this->errorResponse('Message does not belong to this session', Response::HTTP_NOT_FOUND);
      }

      return $this->successResponse(
        $this->sessionService->formatMessageForApi($message)
      );

    }
    catch (\Exception $e) {
      $this->getLogger('flowdrop_playground')->error('Error getting message: @message', [
        '@message' => $e->getMessage(),
      ]);

      return $this->errorResponse('Internal server error', Response::HTTP_INTERNAL_SERVER_ERROR);
    }
  }

  /**
   * Gets the status of a message (lightweight polling endpoint).
   *
   * @param string $session_id
   *   The session UUID.
   * @param string $message_id
   *   The message UUID.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   JSON response with message status.
   */
  public function getMessageStatus(string $session_id, string $message_id): JsonResponse {
    try {
      $session = $this->loadSessionByUuid($session_id);

      if ($session === NULL) {
        return $this->errorResponse('Session not found', Response::HTTP_NOT_FOUND);
      }

      $message = $this->loadMessageByUuid($message_id);

      if ($message === NULL) {
        return $this->errorResponse('Message not found', Response::HTTP_NOT_FOUND);
      }

      // Verify message belongs to session.
      if ($message->getSessionId() !== $session->id()) {
        return $this->errorResponse('Message does not belong to this session', Response::HTTP_NOT_FOUND);
      }

      return $this->successResponse([
        'id' => $message->uuid(),
        'status' => $message->getStatus(),
        'sequenceNumber' => $message->getSequenceNumber(),
        'timestamp' => date('c', $message->getTimestamp()),
      ]);

    }
    catch (\Exception $e) {
      $this->getLogger('flowdrop_playground')->error('Error getting message status: @message', [
        '@message' => $e->getMessage(),
      ]);

      return $this->errorResponse('Internal server error', Response::HTTP_INTERNAL_SERVER_ERROR);
    }
  }

  /**
   * Loads a session by UUID.
   *
   * @param string $uuid
   *   The session UUID.
   *
   * @return \Drupal\flowdrop_session\Entity\FlowDropSessionInterface|null
   *   The session entity or NULL if not found.
   */
  protected function loadSessionByUuid(string $uuid): ?FlowDropSessionInterface {
    $storage = $this->entityTypeManager()->getStorage('flowdrop_session');

    $entities = $storage->loadByProperties(['uuid' => $uuid]);

    if (empty($entities)) {
      return NULL;
    }

    $entity = reset($entities);
    return $entity instanceof FlowDropSessionInterface ? $entity : NULL;
  }

  /**
   * Loads a message by UUID.
   *
   * @param string $uuid
   *   The message UUID.
   *
   * @return \Drupal\flowdrop_session\Entity\FlowDropSessionMessageInterface|null
   *   The message entity or NULL if not found.
   */
  protected function loadMessageByUuid(string $uuid): ?FlowDropSessionMessageInterface {
    $storage = $this->entityTypeManager()->getStorage('flowdrop_session_message');

    $entities = $storage->loadByProperties(['uuid' => $uuid]);

    if (empty($entities)) {
      return NULL;
    }

    $entity = reset($entities);
    return $entity instanceof FlowDropSessionMessageInterface ? $entity : NULL;
  }

}
