<?php

namespace App\Modules\Exports\Repositories;

use App\Modules\Exports\Models\Export;
use App\Modules\Exports\Support\ExportStatus;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;

class ExportRepository
{
    public function findForTenant(int $tenantId, int $id): ?Export
    {
        return Export::query()
            ->withoutGlobalScopes()
            ->where('tenant_id', $tenantId)
            ->whereKey($id)
            ->first();
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public function create(array $data): Export
    {
        return Export::query()->withoutGlobalScopes()->create($data);
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public function update(Export $export, array $data): Export
    {
        Export::query()
            ->withoutGlobalScopes()
            ->where('tenant_id', $export->tenant_id)
            ->whereKey($export->id)
            ->update($data);

        return Export::query()
            ->withoutGlobalScopes()
            ->where('tenant_id', $export->tenant_id)
            ->whereKey($export->id)
            ->firstOrFail();
    }

    public function paginateForTenant(int $tenantId, int $perPage = 20): LengthAwarePaginator
    {
        return Export::query()
            ->withoutGlobalScopes()
            ->where('tenant_id', $tenantId)
            ->with('requester:id,name,email')
            ->orderByDesc('id')
            ->paginate($perPage)
            ->withQueryString();
    }

    /**
     * Atomically move export to processing (pending, or stale processing for crash recovery).
     */
    public function claimForProcessing(int $tenantId, int $id): ?Export
    {
        $staleBefore = now()->subMinutes((int) config('exports.processing_stale_minutes', 30));

        return DB::transaction(function () use ($tenantId, $id, $staleBefore): ?Export {
            $affected = Export::query()
                ->withoutGlobalScopes()
                ->where('tenant_id', $tenantId)
                ->whereKey($id)
                ->where(function ($q) use ($staleBefore): void {
                    $q->where('status', ExportStatus::PENDING)
                        ->orWhere(function ($q2) use ($staleBefore): void {
                            $q2->where('status', ExportStatus::PROCESSING)
                                ->where(function ($q3) use ($staleBefore): void {
                                    $q3->whereNull('started_at')
                                        ->orWhere('started_at', '<', $staleBefore);
                                });
                        });
                })
                ->update([
                    'status' => ExportStatus::PROCESSING,
                    'started_at' => now(),
                ]);
            if ($affected === 0) {
                return null;
            }

            return $this->findForTenant($tenantId, $id);
        });
    }

    /**
     * Idempotent completion: only transitions from processing -> completed.
     */
    public function markCompletedFromProcessing(int $tenantId, int $id): ?Export
    {
        $affected = Export::query()
            ->withoutGlobalScopes()
            ->where('tenant_id', $tenantId)
            ->whereKey($id)
            ->where('status', ExportStatus::PROCESSING)
            ->update([
                'status' => ExportStatus::COMPLETED,
                'completed_at' => now(),
            ]);
        if ($affected === 0) {
            return null;
        }

        return $this->findForTenant($tenantId, $id);
    }

    /**
     * Mark failed only from processing (avoids clobbering a completed export on retry).
     *
     * @param  array<string, mixed>  $extra
     */
    public function markFailedFromProcessing(int $tenantId, int $id, array $extra): ?Export
    {
        $affected = Export::query()
            ->withoutGlobalScopes()
            ->where('tenant_id', $tenantId)
            ->whereKey($id)
            ->where('status', ExportStatus::PROCESSING)
            ->update($extra);
        if ($affected === 0) {
            return null;
        }

        return $this->findForTenant($tenantId, $id);
    }
}
