<?php

declare(strict_types=1);

namespace Drupal\lrs_xapi\Controller;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\DependencyInjection\AutowireTrait;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Site\Settings;
use Drupal\lrs_xapi\XapiStatementStorage;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;

/**
 * Xapi LRS resources.
 */
final class LrsXapiEndpoint implements ContainerInjectionInterface {

  public const CONFIG_NAME = 'lrs_xapi.settings';

  use AutowireTrait;

  public function __construct(
    private readonly XapiStatementStorage $statementStorage,
    private readonly ConfigFactoryInterface $configFactory,
  ) {}

  /**
   * State Resource.
   *
   * See https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Communication.md#23-state-resource.
   */
  public function activityState(Request $request): Response {
    $this->authorize($request);
    $parameters = $request->query->all();

    // Validate request.
    // @todo Support querying by user and activity ID
    // (not needed for the current application).
    if (!\array_key_exists('registration', $parameters)) {
      throw new BadRequestHttpException('registration not specified.');
    }
    if (!\array_key_exists('stateId', $parameters)) {
      throw new BadRequestHttpException('stateId not specified.');
    }

    if ($request->getMethod() === 'GET') {
      // Not needed it seems.
      if ($parameters['stateId'] === 'cumulative_time') {
        return new Response('0');
      }
      $statements = $this->statementStorage->getActivityStateData([
        'registration' => $parameters['registration'],
        'state_id' => $parameters['stateId'],
      ]);
      if (\count($statements) === 0) {
        return new Response('');
      }
      return new Response(\reset($statements));
    }
    elseif ($request->getMethod() === 'PUT') {
      // Not needed it seems.
      if ($parameters['stateId'] === 'cumulative_time') {
        return new Response('OK');
      }
      $this->statementStorage->setActivityState(
        $parameters['registration'],
        $parameters['stateId'],
        $request->getContent(),
      );
    }
    else {
      $this->logRequest($request);
    }
    return new Response('OK');
  }

  /**
   * Statement resource.
   *
   * See https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Communication.md#21-statement-resource.
   */
  public function statements(Request $request): Response {
    $this->authorize($request);
    if ($request->getMethod() === 'PUT' || $request->getMethod() === 'POST') {
      $statement_data = $request->getContent();
      $this->statementStorage->setStatement($statement_data);
    }
    elseif ($request->getMethod() === 'GET') {
      // @todo Specification doesn't specify how to include filters so we
      // can go with our own implementation for now
      // (looks like just simple URL query parameters though).
      $parameters = $request->query->all();

      // Special case: limit.
      if (\array_key_exists('limit', $parameters)) {
        $limit = (int) $parameters['limit'];
        unset($parameters['limit']);
      }
      else {
        $limit = 0;
      }

      $content = '{"statements":' . '[' . \implode(',', $this->statementStorage->getStatements($parameters, $limit)) . '],"more":false}';
      return new Response($content, 200, [
        'Content-Type' => 'application/json',
      ]);
    }
    else {
      $this->logRequest($request);
    }
    return new Response('OK', 200);
  }

  /**
   * Authorize request.
   */
  private function authorize(Request $request): void {
    if (Settings::get('lms_xapi_disable_auth') === TRUE) {
      return;
    }
    $config = $this->configFactory->get(self::CONFIG_NAME);
    $username = $config->get('username');
    $password = $config->get('password');
    if (
      $username === '' ||
      $username === NULL ||
      $password === '' ||
      $password === NULL
    ) {
      throw new BadRequestHttpException('LRS endpoint must be protected.');
    }

    if (
      $username !== $request->getUser() ||
      $password !== $request->getPassword()
    ) {
      throw new BadRequestHttpException('Invalid user name or password.');
    }
  }

  /**
   * Dev.
   *
   * @todo Remove when this is stable.
   */
  private function logRequest(Request $request): void {
    $fh = \fopen('requests.txt', 'a');
    if ($fh !== FALSE) {
      \fwrite($fh, PHP_EOL . PHP_EOL . (string) $request);
    }
  }

}
