<?php

declare(strict_types=1);

namespace App\Console\Commands\Pd;

use App\Models\SystemSetting;
use Carbon\CarbonImmutable;
use Illuminate\Console\Command;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;

/**
 * F-P1 / 152-ФЗ ретеншен: анонимизирует ПДн soft-deleted сделок по истечении
 * настраиваемого срока (спека 2026-06-17-fp1-deal-pii-retention-spec).
 *
 * Срок (дней) — в system_settings, ключ `pd_scrub_soft_deleted_deals_days`.
 * Отсутствие ключа или значение < 1 → no-op (юридический срок не зашит в код,
 * выставляется на проде). Паттерн безопасности идентичен PartitionsDropExpired.
 *
 * Значения анонимизации идентичны PdErasureService::eraseSubject. Работает
 * cross-tenant через pgsql_supplier (BYPASSRLS). Идемпотентно: уже затёртые
 * (phone = ANON_PHONE) исключаются из выборки.
 */
class ScrubSoftDeletedDealsCommand extends Command
{
    private const DB = 'pgsql_supplier';

    private const SETTING_KEY = 'pd_scrub_soft_deleted_deals_days';

    private const ANON_PHONE = '+7000XXXXXXX';

    private const ANON_NAME = 'Удалено';

    /** @var string */
    protected $signature = 'pd:scrub-soft-deleted-deals
                            {--dry-run : Показать число кандидатов, не анонимизировать}';

    /** @var string */
    protected $description = 'Анонимизирует ПДн (телефон/имя) soft-deleted сделок старше retention-срока (152-ФЗ, F-P1)';

    public function handle(): int
    {
        $days = $this->resolveRetentionDays();

        if ($days === null) {
            $this->line('<fg=gray>skip</> retention не настроен (system_settings.'.self::SETTING_KEY.' отсутствует или < 1).');

            return self::SUCCESS;
        }

        $cutoff = CarbonImmutable::now()->subDays($days);
        $candidates = $this->candidateQuery($cutoff)->count();

        if ($this->option('dry-run')) {
            $this->line("<fg=yellow>[dry-run]</> кандидатов на анонимизацию: {$candidates} (deleted_at старше {$days} дн.)");

            return self::SUCCESS;
        }

        if ($candidates === 0) {
            $this->info("Кандидатов на анонимизацию нет (retention={$days} дн.).");

            return self::SUCCESS;
        }

        $now = CarbonImmutable::now();

        // Bulk-UPDATE атомарен одним SQL; лог — одна summary-запись. Явная
        // транзакция не нужна и несовместима с shared-PDO в тестах
        // (pgsql_supplier делит сессию с уже открытой транзакцией pgsql).
        $this->candidateQuery($cutoff)->update([
            'phone' => self::ANON_PHONE,
            'contact_name' => self::ANON_NAME,
            'phones' => null,
            'updated_at' => $now,
        ]);

        // Системное действие: оба actor-поля NULL (допускается chk_pd_actor).
        // log_hash заполняется триггером цепочки целостности.
        DB::connection(self::DB)->table('pd_processing_log')->insert([
            'tenant_id' => null,
            'subject_type' => 'deal',
            'subject_id' => null,
            'action' => 'deleted',
            'purpose' => '152-FZ retention scrub',
            'actor_tenant_user_id' => null,
            'actor_admin_user_id' => null,
            'created_at' => $now,
        ]);

        $this->info("Анонимизировано сделок: {$candidates} (retention={$days} дн.).");

        return self::SUCCESS;
    }

    /** Кандидаты: soft-deleted старше cutoff, ещё не анонимизированные. */
    private function candidateQuery(CarbonImmutable $cutoff): Builder
    {
        return DB::connection(self::DB)->table('deals')
            ->whereNotNull('deleted_at')
            ->where('deleted_at', '<', $cutoff)
            ->where('phone', '<>', self::ANON_PHONE);
    }

    /** Срок ретеншена из system_settings; null если ключа нет или значение < 1. */
    private function resolveRetentionDays(): ?int
    {
        $setting = SystemSetting::find(self::SETTING_KEY);

        if ($setting === null) {
            return null;
        }

        $value = (int) $setting->value;

        return $value >= 1 ? $value : null;
    }
}
