<?php

/*
 * This file is part of the Serenity package.
 *
 * (c) CP Creation <web@cpcreation.fr>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Serenity\CoreBundle\Seed\Entity;

use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Serenity\CoreBundle\Seed\Doctrine\Repository\SeedRunRepository;
use Serenity\CoreBundle\Seed\SeedPayload;
use Serenity\CoreBundle\Shared\Common\IdentifierTrait;
use Serenity\CoreBundle\Shared\Common\TimestampTrait;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid;

#[ORM\Entity(repositoryClass: SeedRunRepository::class)]
#[ORM\Table(name: 'serenity_core_seed_run')]
#[ORM\HasLifecycleCallbacks]
class SeedRun
{
    use IdentifierTrait {
        IdentifierTrait::__construct as private identifierConstruct;
    }
    use TimestampTrait;

    public const string STATUS_PENDING = 'pending';
    public const string STATUS_QUEUED = 'queued';
    public const string STATUS_SENT = 'sent';
    public const string STATUS_RETRYING = 'retrying';
    public const string STATUS_RUNNING = 'running';
    public const string STATUS_SUCCESS = 'success';
    public const string STATUS_FAILED = 'failed';
    public const string STATUS_LOCKED = 'lock';

    #[ORM\Column(type: UuidType::NAME, unique: true)]
    public private(set) Uuid $runId;

    #[ORM\Column(type: Types::STRING, length: 20)]
    public private(set) string $status = self::STATUS_PENDING;

    #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)]
    public private(set) ?\DateTimeImmutable $startedAt = null;

    #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)]
    public private(set) ?\DateTimeImmutable $finishedAt = null;

    #[ORM\Column(type: 'json_document')]
    public private(set) SeedPayload $payload;

    #[ORM\Column(type: Types::TEXT, nullable: true)]
    public private(set) ?string $error = null;

    #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)]
    private ?\DateTimeImmutable $lastRetryAt = null;

    #[ORM\Column(type: Types::INTEGER)]
    private int $attempt = 0;

    private function __construct()
    {
        $this->identifierConstruct();
    }

    public static function pending(SeedPayload $optionsPayload): self
    {
        $self = new self();
        $self->payload = $optionsPayload;
        $self->runId = $optionsPayload->runId;
        $self->status = self::STATUS_PENDING;
        $self->startedAt = null;

        return $self;
    }

    public function markQueued(): void
    {
        $this->status = self::STATUS_QUEUED;
    }

    public function markSent(?string $transportMessageId = null): void
    {
        $this->status = self::STATUS_SENT;
    }

    public function markRunning(int $attempt): void
    {
        $this->status = self::STATUS_RUNNING;
        $this->startedAt ??= new \DateTimeImmutable();
        $this->attempt = $attempt;
        $this->error = null;
    }

    public function markRetrying(\Throwable $e, int $attempt): void
    {
        $this->status = self::STATUS_RETRYING;
        $this->lastRetryAt = new \DateTimeImmutable();
        $this->attempt = $attempt;
        $this->error = $this->formatError($e, $attempt);
    }

    public function markSuccess(): void
    {
        $this->status = self::STATUS_SUCCESS;
        $this->finishedAt = new \DateTimeImmutable();
        $this->error = null;
    }

    public function markFailed(\Throwable $e, int $attempt): void
    {
        $this->status = self::STATUS_FAILED;
        $this->finishedAt = new \DateTimeImmutable();
        $this->error = $this->formatError($e, $attempt);
    }

    public function markIgnoredBecauseLocked(string $message): void
    {
        $this->status = self::STATUS_LOCKED;
        $this->finishedAt = new \DateTimeImmutable();
        $this->error = $message;
    }

    private function formatError(\Throwable $e, int $attempt): string
    {
        $msg = \sprintf('[attempt=%d] %s: %s', $attempt, $e::class, $e->getMessage());

        // optionnel: trace tronquée
        // $msg .= "\n" . substr($e->getTraceAsString(), 0, 60000);
        return $msg;
    }
}
