<?php

namespace Drupal\nodehive_api_key_management\Service;

use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Flood\FloodInterface;
use Drupal\Component\Datetime\TimeInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\RequestStack;

/**
 * Service for managing API keys.
 */
class ApiKeyManager {

  /**
   * The database connection.
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected $database;

  /**
   * The flood service.
   *
   * @var \Drupal\Core\Flood\FloodInterface
   */
  protected $flood;

  /**
   * The logger service.
   *
   * @var \Psr\Log\LoggerInterface
   */
  protected $logger;

  /**
   * The time service.
   *
   * @var \Drupal\Component\Datetime\TimeInterface
   */
  protected $time;

  /**
   * The request stack.
   *
   * @var \Symfony\Component\HttpFoundation\RequestStack
   */
  protected $requestStack;

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * Constructs an ApiKeyManager object.
   */
  public function __construct(
    Connection $database,
    FloodInterface $flood,
    LoggerInterface $logger,
    TimeInterface $time,
    RequestStack $request_stack,
    EntityTypeManagerInterface $entity_type_manager,
  ) {
    $this->database = $database;
    $this->flood = $flood;
    $this->logger = $logger;
    $this->time = $time;
    $this->requestStack = $request_stack;
    $this->entityTypeManager = $entity_type_manager;
  }

  /**
   * Generates a new API key.
   *
   * @return string
   *   The generated API key (full key, not hashed).
   */
  public function generateKey(): string {
    // Generate cryptographically secure random key.
    // Format: nhk_<random_string>.
    $randomBytes = random_bytes(32);
    $randomString = bin2hex($randomBytes);
    return 'nhk_' . $randomString;
  }

  /**
   * Hashes an API key.
   *
   * @param string $key
   *   The plain text API key.
   *
   * @return string
   *   The SHA-256 hash of the key.
   */
  public function hashKey(string $key): string {
    return hash('sha256', $key);
  }

  /**
   * Extracts the prefix from an API key for identification.
   *
   * @param string $key
   *   The full API key.
   *
   * @return string
   *   The prefix (e.g., "nhk_abc").
   */
  public function extractPrefix(string $key): string {
    // Return first 10 characters for identification.
    return substr($key, 0, 10);
  }

  /**
   * Creates a new API key record.
   *
   * @param string $name
   *   The name of the API key.
   * @param string $key
   *   The full API key (will be hashed before storage).
   * @param int $uid
   *   The user ID this API key authenticates as.
   * @param array $options
   *   Optional settings: expiry, rate_limit, ip_allowlist.
   *
   * @return int|false
   *   The new key ID or FALSE on failure.
   */
  public function createKey(string $name, string $key, int $uid, array $options = []) {
    // Validate that the user exists.
    $user = $this->entityTypeManager->getStorage('user')->load($uid);
    if (!$user) {
      $this->logger->error('Attempted to create API key for non-existent user ID @uid', ['@uid' => $uid]);
      return FALSE;
    }

    $now = $this->time->getRequestTime();

    $fields = [
      'name' => $name,
      'key_hash' => $this->hashKey($key),
      'key_prefix' => $this->extractPrefix($key),
      'uid' => $uid,
      'status' => 1,
      'expiry' => $options['expiry'] ?? NULL,
      'rate_limit' => $options['rate_limit'] ?? 0,
      'ip_allowlist' => isset($options['ip_allowlist']) ? json_encode($options['ip_allowlist']) : NULL,
      'created' => $now,
      'changed' => $now,
    ];

    try {
      return $this->database->insert('nodehive_api_keys')
        ->fields($fields)
        ->execute();
    }
    catch (\Exception $e) {
      $this->logger->error('Failed to create API key: @error', ['@error' => $e->getMessage()]);
      return FALSE;
    }
  }

  /**
   * Updates an existing API key.
   *
   * @param int $key_id
   *   The key ID.
   * @param array $fields
   *   Fields to update (name, uid, status, expiry, rate_limit, ip_allowlist).
   *
   * @return bool
   *   TRUE on success, FALSE on failure.
   */
  public function updateKey(int $key_id, array $fields): bool {
    $fields['changed'] = $this->time->getRequestTime();

    // JSON encode array fields if present.
    if (isset($fields['ip_allowlist']) && is_array($fields['ip_allowlist'])) {
      $fields['ip_allowlist'] = json_encode($fields['ip_allowlist']);
    }

    try {
      $this->database->update('nodehive_api_keys')
        ->fields($fields)
        ->condition('id', $key_id)
        ->execute();
      return TRUE;
    }
    catch (\Exception $e) {
      $this->logger->error('Failed to update API key @id: @error', [
        '@id' => $key_id,
        '@error' => $e->getMessage(),
      ]);
      return FALSE;
    }
  }

  /**
   * Deletes an API key.
   *
   * @param int $key_id
   *   The key ID.
   *
   * @return bool
   *   TRUE on success, FALSE on failure.
   */
  public function deleteKey(int $key_id): bool {
    try {
      $this->database->delete('nodehive_api_keys')
        ->condition('id', $key_id)
        ->execute();
      return TRUE;
    }
    catch (\Exception $e) {
      $this->logger->error('Failed to delete API key @id: @error', [
        '@id' => $key_id,
        '@error' => $e->getMessage(),
      ]);
      return FALSE;
    }
  }

  /**
   * Revokes an API key (sets status to 0).
   *
   * @param int $key_id
   *   The key ID.
   *
   * @return bool
   *   TRUE on success, FALSE on failure.
   */
  public function revokeKey(int $key_id): bool {
    return $this->updateKey($key_id, ['status' => 0]);
  }

  /**
   * Loads an API key by ID.
   *
   * @param int $key_id
   *   The key ID.
   *
   * @return object|false
   *   The key record or FALSE if not found.
   */
  public function loadKey(int $key_id) {
    return $this->database->select('nodehive_api_keys', 'k')
      ->fields('k')
      ->condition('id', $key_id)
      ->execute()
      ->fetchObject();
  }

  /**
   * Loads all API keys.
   *
   * @return array
   *   Array of key records.
   */
  public function loadAllKeys(): array {
    return $this->database->select('nodehive_api_keys', 'k')
      ->fields('k')
      ->orderBy('created', 'DESC')
      ->execute()
      ->fetchAll();
  }

  /**
   * Validates an API key and returns the key record if valid.
   *
   * @param string $key
   *   The plain text API key to validate.
   *
   * @return object|false
   *   The key record if valid, FALSE otherwise.
   */
  public function validateKey(string $key) {
    $key_hash = $this->hashKey($key);

    // Load the key by hash.
    $record = $this->database->select('nodehive_api_keys', 'k')
      ->fields('k')
      ->condition('key_hash', $key_hash)
      ->execute()
      ->fetchObject();

    if (!$record) {
      return FALSE;
    }

    // Check if key is active.
    if ($record->status != 1) {
      $this->logger->warning('Attempt to use revoked API key: @prefix', [
        '@prefix' => $record->key_prefix,
      ]);
      return FALSE;
    }

    // Check expiry.
    if ($record->expiry && $record->expiry < $this->time->getRequestTime()) {
      $this->logger->warning('Attempt to use expired API key: @prefix', [
        '@prefix' => $record->key_prefix,
      ]);
      return FALSE;
    }

    // Check IP allowlist.
    if ($record->ip_allowlist) {
      $allowed_ips = json_decode($record->ip_allowlist, TRUE);
      $client_ip = $this->requestStack->getCurrentRequest()->getClientIp();

      if (!in_array($client_ip, $allowed_ips)) {
        $this->logger->warning('API key @prefix used from unauthorized IP: @ip', [
          '@prefix' => $record->key_prefix,
          '@ip' => $client_ip,
        ]);
        return FALSE;
      }
    }

    // Check rate limit.
    if ($record->rate_limit > 0) {
      $flood_identifier = 'api_key_' . $record->id;
      if (!$this->flood->isAllowed($flood_identifier, $record->rate_limit, 3600)) {
        $this->logger->warning('Rate limit exceeded for API key: @prefix', [
          '@prefix' => $record->key_prefix,
        ]);
        return FALSE;
      }
      $this->flood->register($flood_identifier, 3600);
    }

    // Update usage statistics.
    $this->database->update('nodehive_api_keys')
      ->fields([
        'last_used' => $this->time->getRequestTime(),
        'use_count' => $record->use_count + 1,
      ])
      ->condition('id', $record->id)
      ->execute();

    $this->logger->info('API key @prefix authenticated successfully', [
      '@prefix' => $record->key_prefix,
    ]);

    return $record;
  }

}
