<?php

declare(strict_types=1);

namespace App\Jobs;

use App\Domain\Email\Exceptions\MailboxProvisioningException;
use App\Domain\Email\Models\EmailAccount;
use App\Domain\Email\Services\MailboxProvisioner;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use InvalidArgumentException;
use Throwable;

/**
 * EmailController → bu job → MailboxProvisioner zinciri.
 *
 * Action'lar:
 *   - 'create'         → provision($account, $plainPassword)
 *   - 'terminate'      → terminate($account, $deleteData)
 *   - 'updatePassword' → updatePassword($account, $plainPassword)
 *   - 'updateQuota'    → updateQuota($account, $quotaMb)
 *
 * Retry policy: 3 deneme, exponential backoff (10s, 30s, 60s).
 * Sysapi tarafında rollback'li script'ler — başarısızlık idempotent.
 */
class MailboxProvisioningJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $timeout = 180;

    public int $tries = 3;

    /** @var array<int, int> */
    public array $backoff = [10, 30, 60];

    /**
     * @param  array<string, mixed>  $options  ('delete_data' for terminate, 'quota_mb' for updateQuota)
     */
    public function __construct(
        public readonly int $emailAccountId,
        public readonly string $action,
        public readonly ?string $plainPassword = null,
        public readonly array $options = [],
    ) {
        if (! in_array($action, ['create', 'terminate', 'updatePassword', 'updateQuota'], strict: true)) {
            throw new InvalidArgumentException("Geçersiz mailbox action: {$action}");
        }
    }

    public function handle(MailboxProvisioner $provisioner): void
    {
        /** @var EmailAccount|null $account */
        $account = EmailAccount::withTrashed()->find($this->emailAccountId);

        if (! $account) {
            Log::warning('MailboxProvisioningJob: EmailAccount bulunamadı', [
                'id' => $this->emailAccountId,
                'action' => $this->action,
            ]);

            return;
        }

        try {
            match ($this->action) {
                'create' => $provisioner->provision($account, (string) $this->plainPassword),
                'terminate' => $provisioner->terminate(
                    $account,
                    (bool) ($this->options['delete_data'] ?? true),
                ),
                'updatePassword' => $provisioner->updatePassword($account, (string) $this->plainPassword),
                'updateQuota' => $provisioner->updateQuota(
                    $account,
                    (int) ($this->options['quota_mb'] ?? $account->quota_mb),
                ),
            };
        } catch (MailboxProvisioningException $e) {
            Log::error('MailboxProvisioningJob: provisioner exception', [
                'email_account_id' => $account->id,
                'email' => $account->email,
                'action' => $this->action,
                'operation' => $e->operation,
                'exit_code' => $e->sysapiResult?->exitCode,
                'stderr' => $e->sysapiResult?->stderr,
                'attempt' => $this->attempts(),
            ]);

            throw $e; // retry mekanizması devreye girer
        }
    }

    public function failed(Throwable $exception): void
    {
        Log::critical('MailboxProvisioningJob: 3 deneme sonrası başarısız', [
            'email_account_id' => $this->emailAccountId,
            'action' => $this->action,
            'error' => $exception->getMessage(),
        ]);

        // En son deneme başarısız → EmailAccount status'unu 'suspended'a al ki UI doğru gözüksün
        $account = EmailAccount::withTrashed()->find($this->emailAccountId);
        if ($account && $this->action === 'create') {
            $account->update(['status' => 'suspended', 'enabled' => false]);
        }
    }

    /**
     * Queue serializer için $plainPassword'ün loglara/storage'a göründüğü
     * yerlerde 6 karakter sonrası maskelenir. (Laravel default'da queue
     * payload'u DB'ye yazılır; production'da Redis kullanıyorsak in-memory
     * ama horizon dashboard payload'u açabilir — bu yüzden raw password
     * displayName'e dahil edilmez.)
     */
    public function displayName(): string
    {
        return sprintf('MailboxProvisioningJob[%s:%d]', $this->action, $this->emailAccountId);
    }

    /**
     * @return array<int, string>
     */
    public function tags(): array
    {
        return ['mailbox', "action:{$this->action}", "email_account:{$this->emailAccountId}"];
    }
}
