<?php

declare(strict_types=1);

namespace Autogedal\FeedMonitor\Model;

use Magento\Framework\App\ResourceConnection;

/**
 * Execution history (autogedal_feed_monitor_run).
 */
class RunRepository
{
    public const STATUS_RUNNING = 'running';
    public const STATUS_OK = 'ok';
    public const STATUS_ERROR = 'error';

    private const TABLE = 'autogedal_feed_monitor_run';

    public function __construct(
        private readonly ResourceConnection $resource,
        private readonly Clock $clock
    ) {
    }

    public function start(string $jobCode, string $feedCode = ''): int
    {
        $connection = $this->resource->getConnection();
        $connection->insert($this->table(), [
            'job_code' => $jobCode,
            'feed_code' => $feedCode,
            'status' => self::STATUS_RUNNING,
            'started_at' => $this->clock->format(),
        ]);

        return (int)$connection->lastInsertId($this->table());
    }

    /**
     * A run that already ended elsewhere (e.g. a Firebear import), with its own start and end.
     */
    public function record(
        string $jobCode,
        string $feedCode,
        string $status,
        ?string $problem,
        int $startedAt,
        int $finishedAt,
        ?int $itemsRead,
        ?int $itemsUpdated,
        string $message
    ): void {
        $this->resource->getConnection()->insert($this->table(), [
            'job_code' => $jobCode,
            'feed_code' => $feedCode,
            'status' => $status,
            'problem' => $problem,
            'started_at' => $this->clock->format($startedAt),
            'finished_at' => $this->clock->format($finishedAt),
            'items_read' => $itemsRead,
            'items_updated' => $itemsUpdated,
            'message' => mb_strcut($message, 0, 60000),
        ]);
    }

    public function finish(
        int $runId,
        string $status,
        ?string $problem,
        ?int $itemsRead,
        ?int $itemsUpdated,
        string $message
    ): void {
        $this->resource->getConnection()->update($this->table(), [
            'status' => $status,
            'problem' => $problem,
            'finished_at' => $this->clock->format(),
            'items_read' => $itemsRead,
            'items_updated' => $itemsUpdated,
            // TEXT holds 65535 bytes: cut by bytes, on a character boundary
            'message' => mb_strcut($message, 0, 60000),
        ], ['run_id = ?' => $runId]);
    }

    /**
     * Last run that ended well and read a number of items (the reference for "incomplete").
     */
    public function getLastSuccessful(string $jobCode, string $feedCode = ''): ?array
    {
        $connection = $this->resource->getConnection();
        $row = $connection->fetchRow($connection->select()
            ->from($this->table())
            ->where('job_code = ?', $jobCode)
            ->where('feed_code = ?', $feedCode)
            ->where('status = ?', self::STATUS_OK)
            ->where('items_read IS NOT NULL')
            ->order('run_id DESC')
            ->limit(1));

        return $row ?: null;
    }

    public function getLastRun(string $jobCode, string $feedCode = ''): ?array
    {
        $connection = $this->resource->getConnection();
        $row = $connection->fetchRow($connection->select()
            ->from($this->table())
            ->where('job_code = ?', $jobCode)
            ->where('feed_code = ?', $feedCode)
            ->order('run_id DESC')
            ->limit(1));

        return $row ?: null;
    }

    /**
     * Runs started before the limit and never finished (fatal error, exit, killed process).
     *
     * @return array<int, array<string, mixed>>
     */
    public function getStuckRuns(int $olderThanMinutes): array
    {
        $connection = $this->resource->getConnection();

        return $connection->fetchAll($connection->select()
            ->from($this->table())
            ->where('status = ?', self::STATUS_RUNNING)
            ->where('started_at < ?', $this->clock->format($this->clock->now() - $olderThanMinutes * 60))
            ->order('run_id ASC'));
    }

    public function deleteOlderThan(int $days): int
    {
        return $this->resource->getConnection()->delete($this->table(), [
            'started_at < ?' => $this->clock->format($this->clock->now() - $days * 86400),
        ]);
    }

    private function table(): string
    {
        return $this->resource->getTableName(self::TABLE);
    }
}
