<?php

declare(strict_types=1);

namespace App\Jobs\Supplier;

use App\Exceptions\Supplier\SupplierClientException;
use App\Exceptions\Supplier\SupplierTransientException;
use App\Models\SupplierProject;
use App\Models\SupplierSyncLog;
use App\Services\Supplier\SupplierPortalClient;
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\DB;
use Illuminate\Support\Facades\Log;

/**
 * Daily 02:00 МСК cron-job: чистит supplier_projects, на которые больше не
 * ссылаются активные Лидерра-projects, с 180-дневным TTL и safety ordering.
 *
 * Алгоритм (3 фазы со строгим порядком Phase A → B → C):
 *   Phase A (re-activate, СНАЧАЛА для safety): если supplier_project имеет
 *           inactive_since IS NOT NULL и при этом существует active Лидерра-project
 *           с FK на него → сбрасываем inactive_since = NULL. Phase A до Phase C —
 *           чтобы недавно вернувшийся supplier_project не был удалён.
 *   Phase B (mark): если supplier_project имеет inactive_since IS NULL и на него
 *           нет ни одного active Лидерра-project через supplier_b{1,2,3}_project_id →
 *           помечаем inactive_since = NOW().
 *   Phase C (delete): для supplier_project с inactive_since < NOW() - 180 days
 *           → DELETE на supplier через rt-project-delete + локальный DELETE +
 *           audit row в supplier_sync_log. 404 от поставщика = trust 'already deleted'
 *           + локальный DELETE + audit row с http_status=404.
 *
 * Active liderra subquery: DISTINCT id supplier_projects, на которые ссылается
 * хотя бы один Project WHERE is_active=true через любой из трёх FK supplier_b{1,2,3}_project_id.
 *
 * SupplierProject НЕ имеет SoftDeletes (см. app/Models/SupplierProject.php) — `->delete()`
 * = hard delete. Audit-trail сохраняется через supplier_sync_log (FK ON DELETE SET NULL).
 *
 * NOTE про connection: Job's $connection — queue connection, не DB. Используем
 * Eloquent::on('pgsql_supplier') для cross-tenant видимости (Plan 3 Task 3 learning).
 *
 * Spec:
 *   - docs/superpowers/specs/2026-05-10-supplier-integration-design.md §3.3, §4.4
 *   - docs/superpowers/specs/2026-05-11-plan3-supplier-sync-design.md §4
 */
class CleanupInactiveSupplierProjectsJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public const DB_CONNECTION = 'pgsql_supplier';

    private const INACTIVE_TTL_DAYS = 180;

    public function handle(?SupplierPortalClient $client = null): void
    {
        $client ??= app(SupplierPortalClient::class);

        // Источник истинности активности — `project_supplier_links` pivot (Plan 3+).
        // Legacy FK `supplier_b{1,2,3}_project_id` оставлены для read-compat,
        // но не определяют активность.
        $activeIdsSubquery = <<<'SQL'
            SELECT DISTINCT psl.supplier_project_id AS id
            FROM project_supplier_links psl
            INNER JOIN projects p ON p.id = psl.project_id
            WHERE p.is_active = true
        SQL;

        // Phase A — re-activate (СНАЧАЛА для safety: до Phase C, чтобы недавно
        // вернувшийся supplier_project не был удалён в Phase C).
        $reactivated = DB::connection(self::DB_CONNECTION)->update(<<<SQL
            UPDATE supplier_projects
            SET inactive_since = NULL
            WHERE inactive_since IS NOT NULL
              AND id IN ({$activeIdsSubquery})
        SQL);
        Log::info('supplier.cleanup.phase_a_reactivated', ['count' => $reactivated]);

        // Phase B — mark newly orphaned (нет ни одного active Лидерра-project).
        $marked = DB::connection(self::DB_CONNECTION)->update(<<<SQL
            UPDATE supplier_projects
            SET inactive_since = NOW()
            WHERE inactive_since IS NULL
              AND id NOT IN ({$activeIdsSubquery})
        SQL);
        Log::info('supplier.cleanup.phase_b_marked', ['count' => $marked]);

        // Phase C — delete supplier_projects, inactive > 180 days.
        $deleted = 0;
        $cutoff = now()->subDays(self::INACTIVE_TTL_DAYS);

        SupplierProject::on(self::DB_CONNECTION)
            ->where('inactive_since', '<', $cutoff)
            ->cursor()
            ->each(function (SupplierProject $sp) use ($client, &$deleted): void {
                $this->deleteOne($sp, $client, $deleted);
            });

        Log::info('supplier.cleanup.phase_c_deleted', ['count' => $deleted]);
    }

    private function deleteOne(SupplierProject $sp, SupplierPortalClient $client, int &$deleted): void
    {
        try {
            if ($sp->supplier_external_id !== null) {
                $client->deleteProject((int) $sp->supplier_external_id);
            }
            // Audit row ДО локального delete: FK ON DELETE SET NULL занулит
            // supplier_project_id у этого лога при последующем delete'е, но
            // строка останется в журнале (audit-trail durability).
            // request_payload сохраняет id/external_id/key для post-delete-трассировки.
            SupplierSyncLog::on(self::DB_CONNECTION)->create([
                'supplier_project_id' => $sp->id,
                'action' => 'delete',
                'http_status' => 200,
                'request_payload' => $this->auditPayload($sp),
                'created_at' => now(),
            ]);
            $sp->delete();
            $deleted++;
        } catch (SupplierClientException $e) {
            if ($e->httpStatus === 404) {
                // 'Already deleted' on supplier side — trust + local delete.
                SupplierSyncLog::on(self::DB_CONNECTION)->create([
                    'supplier_project_id' => $sp->id,
                    'action' => 'delete',
                    'http_status' => 404,
                    'error_message' => 'supplier returned 404 (already deleted)',
                    'request_payload' => $this->auditPayload($sp),
                    'created_at' => now(),
                ]);
                $sp->delete();
                $deleted++;

                return;
            }

            SupplierSyncLog::on(self::DB_CONNECTION)->create([
                'supplier_project_id' => $sp->id,
                'action' => 'delete',
                'http_status' => $e->httpStatus,
                'error_message' => substr($e->getMessage(), 0, 500),
                'request_payload' => $this->auditPayload($sp),
                'created_at' => now(),
            ]);
            report($e);
        } catch (SupplierTransientException $e) {
            SupplierSyncLog::on(self::DB_CONNECTION)->create([
                'supplier_project_id' => $sp->id,
                'action' => 'delete',
                'http_status' => $e->httpStatus,
                'error_message' => substr($e->getMessage(), 0, 500),
                'request_payload' => $this->auditPayload($sp),
                'created_at' => now(),
            ]);
        }
    }

    /**
     * Денормализованный snapshot supplier_project для audit-trail durability.
     * FK ON DELETE SET NULL зануляет supplier_project_id, но request_payload
     * сохраняет идентификаторы для post-delete-трассировки в журнале.
     *
     * @return array<string, mixed>
     */
    private function auditPayload(SupplierProject $sp): array
    {
        return [
            'supplier_project_id' => $sp->id,
            'supplier_external_id' => $sp->supplier_external_id,
            'platform' => $sp->platform,
            'signal_type' => $sp->signal_type,
            'unique_key' => $sp->unique_key,
        ];
    }
}
