<?php

declare(strict_types=1);

namespace Drupal\flowdrop_trigger\Service;

use Drupal\Core\State\StateInterface;

/**
 * Service for tracking cron trigger execution state and history.
 *
 * Manages per-trigger execution state using Drupal's State API, including:
 * - Last run timestamp
 * - Total execution count
 * - Execution history (last N executions with timestamps and status)
 *
 * State is stored with key format: flowdrop_trigger.cron_state.{trigger_id}
 *
 * @internal This class is not part of the public API and may change without notice.
 */
class CronTriggerStateService {

  /**
   * State key prefix for cron trigger state.
   */
  protected const STATE_PREFIX = "flowdrop_trigger.cron_state.";

  /**
   * Maximum number of history entries to keep per trigger.
   */
  protected const MAX_HISTORY_ENTRIES = 10;

  /**
   * Constructs a CronTriggerStateService object.
   *
   * @param \Drupal\Core\State\StateInterface $state
   *   The Drupal state service.
   */
  public function __construct(
    protected readonly StateInterface $state,
  ) {}

  /**
   * Get the state key for a trigger.
   *
   * @param string $triggerId
   *   The trigger configuration ID.
   *
   * @return string
   *   The state key.
   */
  protected function getStateKey(string $triggerId): string {
    return self::STATE_PREFIX . $triggerId;
  }

  /**
   * Get the full state for a trigger.
   *
   * @param string $triggerId
   *   The trigger configuration ID.
   *
   * @return array<string, mixed>
   *   The trigger state containing:
   *   - last_run: Unix timestamp of last execution (0 if never run)
   *   - run_count: Total execution count
   *   - history: Array of execution history entries
   */
  public function getState(string $triggerId): array {
    $state = $this->state->get($this->getStateKey($triggerId), []);

    // Ensure all required keys exist with defaults.
    return [
      "last_run" => $state["last_run"] ?? 0,
      "run_count" => $state["run_count"] ?? 0,
      "history" => $state["history"] ?? [],
    ];
  }

  /**
   * Get the last run timestamp for a trigger.
   *
   * @param string $triggerId
   *   The trigger configuration ID.
   *
   * @return int
   *   The Unix timestamp of the last execution, or 0 if never run.
   */
  public function getLastRun(string $triggerId): int {
    $state = $this->getState($triggerId);
    return $state["last_run"];
  }

  /**
   * Get the execution count for a trigger.
   *
   * @param string $triggerId
   *   The trigger configuration ID.
   *
   * @return int
   *   The total number of times this trigger has executed.
   */
  public function getRunCount(string $triggerId): int {
    $state = $this->getState($triggerId);
    return $state["run_count"];
  }

  /**
   * Get the execution history for a trigger.
   *
   * @param string $triggerId
   *   The trigger configuration ID.
   *
   * @return array<array<string, mixed>>
   *   Array of history entries, each containing:
   *   - timestamp: Unix timestamp of execution
   *   - status: Execution status ('success', 'error', 'skipped')
   *   - message: Optional message describing the execution result
   *   - duration: Execution duration in milliseconds (optional)
   */
  public function getHistory(string $triggerId): array {
    $state = $this->getState($triggerId);
    return $state["history"];
  }

  /**
   * Record a trigger execution.
   *
   * Updates the last run timestamp, increments the run count, and adds
   * an entry to the execution history.
   *
   * @param string $triggerId
   *   The trigger configuration ID.
   * @param string $status
   *   The execution status ('success', 'error', 'skipped').
   * @param string $message
   *   Optional message describing the execution result.
   * @param int|null $duration
   *   Optional execution duration in milliseconds.
   */
  public function recordExecution(
    string $triggerId,
    string $status = "success",
    string $message = "",
    ?int $duration = NULL,
  ): void {
    $state = $this->getState($triggerId);
    $now = time();

    // Update last run timestamp.
    $state["last_run"] = $now;

    // Increment run count.
    $state["run_count"] = ($state["run_count"] ?? 0) + 1;

    // Add history entry.
    $historyEntry = [
      "timestamp" => $now,
      "status" => $status,
      "message" => $message,
      "execution_number" => $state["run_count"],
    ];

    if ($duration !== NULL) {
      $historyEntry["duration"] = $duration;
    }

    // Prepend to history (newest first).
    array_unshift($state["history"], $historyEntry);

    // Trim history to max entries.
    $state["history"] = array_slice($state["history"], 0, self::MAX_HISTORY_ENTRIES);

    // Save state.
    $this->state->set($this->getStateKey($triggerId), $state);
  }

  /**
   * Record a skipped execution (trigger didn't fire due to schedule).
   *
   * This is useful for tracking when the schedule conditions were not met.
   *
   * @param string $triggerId
   *   The trigger configuration ID.
   * @param string $reason
   *   The reason the trigger was skipped.
   */
  public function recordSkipped(string $triggerId, string $reason): void {
    // For skipped executions, we don't update last_run or run_count,
    // but we do add a history entry for debugging purposes.
    $state = $this->getState($triggerId);
    $now = time();

    $historyEntry = [
      "timestamp" => $now,
      "status" => "skipped",
      "message" => $reason,
      "execution_number" => NULL,
    ];

    // Prepend to history (newest first).
    array_unshift($state["history"], $historyEntry);

    // Trim history to max entries.
    $state["history"] = array_slice($state["history"], 0, self::MAX_HISTORY_ENTRIES);

    // Save state (note: last_run and run_count unchanged).
    $this->state->set($this->getStateKey($triggerId), $state);
  }

  /**
   * Reset the state for a trigger.
   *
   * This clears all execution history and resets counters.
   *
   * @param string $triggerId
   *   The trigger configuration ID.
   */
  public function resetState(string $triggerId): void {
    $this->state->delete($this->getStateKey($triggerId));
  }

  /**
   * Check if a trigger has ever run.
   *
   * @param string $triggerId
   *   The trigger configuration ID.
   *
   * @return bool
   *   TRUE if the trigger has run at least once.
   */
  public function hasRun(string $triggerId): bool {
    return $this->getLastRun($triggerId) > 0;
  }

  /**
   * Get the time since the last execution.
   *
   * @param string $triggerId
   *   The trigger configuration ID.
   *
   * @return int|null
   *   The number of seconds since the last execution, or NULL if never run.
   */
  public function getTimeSinceLastRun(string $triggerId): ?int {
    $lastRun = $this->getLastRun($triggerId);

    if ($lastRun === 0) {
      return NULL;
    }

    return time() - $lastRun;
  }

  /**
   * Check if a trigger should be disabled due to max executions.
   *
   * @param string $triggerId
   *   The trigger configuration ID.
   * @param int $maxExecutions
   *   The maximum number of executions allowed (0 = unlimited).
   *
   * @return bool
   *   TRUE if max executions has been reached.
   */
  public function hasReachedMaxExecutions(string $triggerId, int $maxExecutions): bool {
    if ($maxExecutions <= 0) {
      return FALSE;
    }

    return $this->getRunCount($triggerId) >= $maxExecutions;
  }

  /**
   * Get state for multiple triggers at once.
   *
   * @param array<string> $triggerIds
   *   Array of trigger configuration IDs.
   *
   * @return array<string, array<string, mixed>>
   *   Array of states keyed by trigger ID.
   */
  public function getMultipleStates(array $triggerIds): array {
    $states = [];

    foreach ($triggerIds as $triggerId) {
      $states[$triggerId] = $this->getState($triggerId);
    }

    return $states;
  }

  /**
   * Delete state for a trigger (cleanup when trigger is deleted).
   *
   * @param string $triggerId
   *   The trigger configuration ID.
   */
  public function deleteState(string $triggerId): void {
    $this->state->delete($this->getStateKey($triggerId));
  }

}
