<?php

declare(strict_types=1);

namespace Drupal\ffmpeg_media\Hook;

use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Hook\Attribute\Hook;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\media\MediaInterface;

/**
 * Hook implementations for private file download access control.
 *
 * Grants download access to converted output files stored via private://
 * by verifying entity-level view access on the parent media entity.
 * This is the Drupal-standard mechanism for private file serving.
 */
final class FileDownloadHooks {

  /**
   * Constructs a FileDownloadHooks object.
   *
   * @param \Drupal\Core\Database\Connection $database
   *   Database connection.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   Entity type manager.
   * @param \Drupal\Core\Session\AccountProxyInterface $currentUser
   *   The current user.
   */
  public function __construct(
    private readonly Connection $database,
    private readonly EntityTypeManagerInterface $entityTypeManager,
    private readonly AccountProxyInterface $currentUser,
  ) {}

  /**
   * Implements hook_file_download().
   *
   * Controls access to private output files generated by the conversion
   * pipeline. Looks up the file in the ffmpeg_media_outputs table and
   * checks view access on the parent media entity.
   *
   * @param string $uri
   *   The file URI being requested.
   *
   * @return array<string, string>|int|null
   *   Headers array to allow, -1 to deny, or NULL to abstain.
   */
  #[Hook('file_download')]
  public function fileDownload(string $uri): array|int|null {
    if (!str_starts_with($uri, 'private://')) {
      return NULL;
    }

    $outputRecord = $this->findOutputByUri($uri);
    if ($outputRecord === NULL) {
      return NULL;
    }

    $media = $this->entityTypeManager->getStorage('media')
      ->load($outputRecord['media_id']);

    if (!$media instanceof MediaInterface) {
      return -1;
    }

    if (!$media->access('view', $this->currentUser)) {
      return -1;
    }

    return [
      'Content-Type' => $outputRecord['filemime'],
      'Content-Length' => (string) $outputRecord['filesize'],
      'Cache-Control' => 'private, max-age=3600',
    ];
  }

  /**
   * Finds an output record by file URI.
   *
   * Joins the ffmpeg_media_outputs table with file_managed to match
   * the requested URI to a specific output and its parent media entity.
   *
   * @param string $uri
   *   The private:// file URI.
   *
   * @return array{media_id: int, filemime: string, filesize: int}|null
   *   Output metadata or NULL if the URI is not a managed output.
   */
  private function findOutputByUri(string $uri): ?array {
    $query = $this->database->select('ffmpeg_media_outputs', 'o');
    $query->fields('o', ['media_id']);
    $query->join('file_managed', 'f', 'f.fid = o.file_id');
    $query->fields('f', ['filemime', 'filesize']);
    $query->condition('f.uri', $uri);
    $query->range(0, 1);
    $row = $query->execute()?->fetchAssoc();

    if ($row === FALSE || $row === NULL) {
      return NULL;
    }

    return [
      'media_id' => (int) $row['media_id'],
      'filemime' => (string) ($row['filemime'] ?? 'application/octet-stream'),
      'filesize' => (int) ($row['filesize'] ?? 0),
    ];
  }

}
