<?php
// app/services/JobsService.php

declare(strict_types=1);

namespace ScMedia\Services;

use ScMedia\Db\Db;
use Exception;

final class JobsService {
  private Db $db;
  private ?LogService $logger;

  public function __construct(Db $db, ?LogService $logger = null) {
    $this->db = $db;
    $this->logger = $logger;
  }

  public function create(string $type, string $title, ?array $payload = null): int {
    $payloadJson = $payload === null ? null : json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

    $this->db->exec("
      INSERT INTO jobs (type, status, title, payload_json)
      VALUES (:type, 'queued', :title, :payload)
    ", [
      ':type' => $type,
      ':title' => $title,
      ':payload' => $payloadJson,
    ]);

    $row = $this->db->fetchOne("SELECT LAST_INSERT_ID() AS id");
    return (int)($row['id'] ?? 0);
  }

  public function get(int $id): ?array {
    return $this->db->fetchOne("SELECT * FROM jobs WHERE id=:id", [':id' => $id]);
  }

  public function getStatus(int $id): ?string {
    $row = $this->db->fetchOne("SELECT status FROM jobs WHERE id=:id", [':id' => $id]);
    return $row ? (string)$row['status'] : null;
  }

  public function fetchNextQueued(): ?array {
    return $this->db->fetchOne("
      SELECT * FROM jobs
      WHERE status='queued'
      ORDER BY id ASC
      LIMIT 1
    ");
  }

  public function start(int $id): void {
    $this->db->exec("
      UPDATE jobs
      SET status='running', started_at=NOW(), error_message=NULL, cancel_requested=0, last_heartbeat=NOW()
      WHERE id=:id AND status='queued'
    ", [':id' => $id]);
  }

  public function setProgress(int $id, int $current, int $total): void {
    $pct = 0;
    if ($total > 0) {
      $pct = (int)floor(($current / $total) * 100);
      if ($pct < 0) $pct = 0;
      if ($pct > 100) $pct = 100;
    }

    $this->db->exec("
      UPDATE jobs
      SET progress_current=:c, progress_total=:t, progress_pct=:p, last_heartbeat=NOW()
      WHERE id=:id
    ", [
      ':id' => $id,
      ':c' => $current,
      ':t' => $total,
      ':p' => $pct,
    ]);
  }

  public function log(int $jobId, string $level, string $message): void {
    $level = in_array($level, ['info','warn','error','debug'], true) ? $level : 'info';
    $this->db->exec("
      INSERT INTO job_logs (job_id, level, message)
      VALUES (:job, :level, :msg)
    ", [
      ':job' => $jobId,
      ':level' => $level,
      ':msg' => $message,
    ]);

    $this->db->exec("
      UPDATE jobs
      SET last_heartbeat=NOW()
      WHERE id=:id
    ", [':id' => $jobId]);

    if ($this->logger) {
      $this->logger->log($level, $message, ['job_id' => $jobId]);
    }
  }

  public function finish(int $id): void {
    $this->db->exec("
      UPDATE jobs
      SET status='done', finished_at=NOW()
      WHERE id=:id
    ", [':id' => $id]);
  }

  public function fail(int $id, string $errorMessage): void {
    $this->db->exec("
      UPDATE jobs
      SET status='error', error_message=:e, finished_at=NOW()
      WHERE id=:id
    ", [
      ':id' => $id,
      ':e' => $errorMessage,
    ]);
  }

  public function requestCancel(int $id): void {
    $this->db->exec("
      UPDATE jobs
      SET cancel_requested=1
      WHERE id=:id AND status IN ('queued','running')
    ", [':id' => $id]);
  }

  public function cancelNow(int $id, string $reason = 'canceled'): void {
    $this->db->exec("
      UPDATE jobs
      SET status='canceled', error_message=:e, finished_at=NOW()
      WHERE id=:id AND status IN ('queued','running')
    ", [
      ':id' => $id,
      ':e' => $reason,
    ]);
  }

  public function isCancelRequested(int $id): bool {
    $row = $this->db->fetchOne("SELECT cancel_requested, status FROM jobs WHERE id=:id", [':id' => $id]);
    if (!$row) return false;
    if ((string)($row['status'] ?? '') === 'canceled') return true;
    return !empty($row['cancel_requested']);
  }

  public function cancelIfRequested(int $id): bool {
    if ($this->isCancelRequested($id)) {
      $this->cancelNow($id, 'cancel requested');
      return true;
    }
    return false;
  }

  public function markStalled(int $minutes): int {
    $minutes = max(1, $minutes);
    return $this->db->exec("
      UPDATE jobs
      SET status='error', error_message='stalled watchdog', finished_at=NOW()
      WHERE status='running' AND last_heartbeat IS NOT NULL
        AND last_heartbeat < (NOW() - INTERVAL {$minutes} MINUTE)
    ");
  }
}
