<?php

namespace GriffinForms\Includes\Pipelines;

use GriffinForms\Config;
use GriffinForms\Log;
use GriffinForms\Traits\Singleton;

class JobRepository
{
    use Singleton;

    /** @var string */
    protected $table;

    /** @var \wpdb */
    protected $wpdb;

    /** @var \GriffinForms\Log|null */
    protected $log;

    protected function setTable(): void
    {
        $config = Config::getInstance();
        $this->table = $config->getTable('job');
    }

    protected function setWpdb(): void
    {
        global $wpdb;
        $this->wpdb = $wpdb;
    }

    protected function setLog(): void
    {
        $this->log = Log::getInstance();
    }

    public function enqueue(string $job_type, string $integration_id, int $submission_id, int $form_id, array $payload, int $priority = 5, int $delay_seconds = 0): int
    {
        $available_at = gmdate('Y-m-d H:i:s', time() + max(0, $delay_seconds));
        $payload_json = wp_json_encode($payload);

        $result = $this->wpdb->insert(
            $this->table,
            [
                'job_type'       => sanitize_text_field($job_type),
                'integration_id' => sanitize_text_field($integration_id),
                'submission_id'  => $submission_id,
                'form_id'        => $form_id,
                'payload'        => $payload_json,
                'priority'       => max(0, $priority),
                'available_at'   => $available_at,
            ],
            [ '%s', '%s', '%d', '%d', '%s', '%d', '%s' ]
        );

        if ($result === false) {
            $this->logFailure('insert', $submission_id, $this->wpdb->last_error);
            return 0;
        }

        return (int) $this->wpdb->insert_id;
    }

    public function fetchPending(int $limit = 10): array
    {
        $limit = max(1, $limit);
        $now = gmdate('Y-m-d H:i:s');
        $sql = $this->wpdb->prepare(
            "SELECT * FROM {$this->table} WHERE status = %s AND available_at <= %s ORDER BY priority DESC, available_at ASC, id ASC LIMIT %d",
            'pending',
            $now,
            $limit
        );

        $results = $this->wpdb->get_results($sql, ARRAY_A);
        return $results ? $results : [];
    }

    public function markProcessing(int $job_id): bool
    {
        $updated = $this->wpdb->update(
            $this->table,
            [
                'status'   => 'processing',
                'locked_at'=> gmdate('Y-m-d H:i:s'),
            ],
            [ 'id' => $job_id ],
            [ '%s', '%s' ],
            [ '%d' ]
        );

        if ($updated === false) {
            $this->logFailure('markProcessing', 0, $this->wpdb->last_error);
            return false;
        }

        return true;
    }

    public function markCompleted(int $job_id): bool
    {
        $updated = $this->wpdb->update(
            $this->table,
            [
                'status'    => 'completed',
                'locked_at' => null,
                'last_error'=> null,
            ],
            [ 'id' => $job_id ],
            [ '%s', '%s', '%s' ],
            [ '%d' ]
        );

        if ($updated === false) {
            $this->logFailure('markCompleted', 0, $this->wpdb->last_error);
            return false;
        }

        return true;
    }

    public function markFailed(int $job_id, string $error_message, bool $retry = false, int $delay_seconds = 0): bool
    {
        $available_at = gmdate('Y-m-d H:i:s', time() + max(0, $delay_seconds));
        $status = $retry ? 'pending' : 'failed';

        $sql = $this->wpdb->prepare(
            "UPDATE {$this->table} SET attempts = attempts + 1, status = %s, last_error = %s, locked_at = NULL, available_at = %s WHERE id = %d",
            $status,
            $error_message,
            $available_at,
            $job_id
        );

        $result = $this->wpdb->query($sql);
        if ($result === false) {
            $this->logFailure('markFailed', 0, $this->wpdb->last_error);
            return false;
        }

        return true;
    }

    private function logFailure(string $action, int $submission_id, string $error): void
    {
        if (!$this->log) {
            return;
        }

        $this->log->add(
            'error',
            'submission',
            $submission_id,
            get_current_user_id(),
            sprintf('JobRepository action %s failed: %s', $action, $error ?: 'unknown error'),
            'Integration (pipeline)'
        );
    }
}
