<?php

declare(strict_types=1);

namespace App\Services\Pd;

use Carbon\CarbonImmutable;
use Illuminate\Support\Facades\DB;
use InvalidArgumentException;

/**
 * Сервис анонимизации ПДн субъекта по 152-ФЗ (право на удаление, ст.21).
 *
 * Использует соединение pgsql_supplier (BYPASSRLS / crm_supplier_worker),
 * чтобы читать и писать cross-tenant без RLS-ограничений.
 *
 * Реальные колонки схемы v8.19:
 *   users:          email, first_name, last_name, phone
 *   supplier_leads: phone, raw_payload (JSONB) — нет contact_email/contact_phone
 *   deals:          phone, contact_name         — нет отдельного contact_email
 *   (webhook_log удалён в миграции 2026_05_24_140000_drop_legacy_webhook_artefacts)
 */
class PdErasureService
{
    private const DB = 'pgsql_supplier';

    /**
     * Анонимизировать все ПДн субъекта по email и/или телефону.
     *
     * @param  string|null  $email  Email субъекта (один из двух обязателен)
     * @param  string|null  $phone  Телефон субъекта (один из двух обязателен)
     * @param  int|null  $tenantId  Ограничить поиск одним тенантом (null = все)
     * @param  int  $actorAdminId  ID saas_admin_users
     * @param  string|null  $requestId  ID pd_subject_requests для авто-закрытия
     * @return array{users: int, leads: int, deals: int}
     *
     * @throws InvalidArgumentException если оба email и phone null
     */
    public function eraseSubject(
        ?string $email,
        ?string $phone,
        ?int $tenantId,
        int $actorAdminId,
        ?string $requestId = null,
    ): array {
        if ($email === null && $phone === null) {
            throw new InvalidArgumentException('Необходимо указать email или телефон субъекта.');
        }

        $counts = ['users' => 0, 'leads' => 0, 'deals' => 0];

        DB::connection(self::DB)->transaction(function () use (
            $email, $phone, $tenantId, $actorAdminId, $requestId, &$counts
        ): void {
            $now = CarbonImmutable::now();

            // ------------------------------------------------------------------
            // 1. users
            // ------------------------------------------------------------------
            $userQuery = DB::connection(self::DB)->table('users');
            $userQuery->where(function ($q) use ($email, $phone): void {
                if ($email !== null) {
                    $q->orWhere('email', $email);
                }
                if ($phone !== null) {
                    $q->orWhere('phone', $phone);
                }
            });
            if ($tenantId !== null) {
                $userQuery->where('tenant_id', $tenantId);
            }

            $users = $userQuery->get(['id', 'tenant_id']);

            foreach ($users as $user) {
                $userId = (int) $user->id;
                $userTenantId = (int) $user->tenant_id;

                DB::connection(self::DB)->table('users')
                    ->where('id', $userId)
                    ->update([
                        'email' => 'erased-'.$userId.'@deleted.local',
                        'first_name' => 'Удалено',
                        'last_name' => null,
                        'phone' => '+7000'.str_pad((string) $userId, 7, '0', STR_PAD_LEFT),
                        'updated_at' => $now,
                    ]);

                $this->writePdLog(
                    tenantId: $userTenantId,
                    subjectType: 'user',
                    subjectId: $userId,
                    actorAdminId: $actorAdminId,
                    now: $now,
                );
            }

            $counts['users'] = $users->count();

            // ------------------------------------------------------------------
            // 2. supplier_leads  (phone + raw_payload JSONB)
            //    NB: нет contact_email / contact_phone — поиск только по phone
            // ------------------------------------------------------------------
            $leadQuery = DB::connection(self::DB)->table('supplier_leads');
            if ($phone !== null) {
                $leadQuery->where('phone', $phone);
            } else {
                // Только email — ищем в raw_payload JSONB
                $leadQuery->whereRaw('raw_payload::text LIKE ?', ['%'.$email.'%']);
            }

            $leads = $leadQuery->get(['id']);

            foreach ($leads as $lead) {
                $leadId = (int) $lead->id;

                DB::connection(self::DB)->table('supplier_leads')
                    ->where('id', $leadId)
                    ->update([
                        'phone' => '+7000XXXXXXX',
                        'raw_payload' => DB::connection(self::DB)->raw(
                            "JSONB_BUILD_OBJECT('erased', TRUE, 'erased_at', NOW()::TEXT)"
                        ),
                    ]);

                $this->writePdLog(
                    tenantId: $tenantId,
                    subjectType: 'lead',
                    subjectId: $leadId,
                    actorAdminId: $actorAdminId,
                    now: $now,
                );
            }

            $counts['leads'] = $leads->count();

            // ------------------------------------------------------------------
            // 3. deals  (скалярный phone + JSONB phones — доп. телефоны субъекта)
            //    Email в deals не хранится (нет колонки и нет JSONB-поля с email),
            //    поэтому erasure только по email для сделок — корректный no-op:
            //    сопоставлять нечего, counts['deals'] остаётся 0 (F-P1b, решение 2).
            //    Сделки партиционированы — UPDATE по id на parent работает на PG 11+.
            // ------------------------------------------------------------------
            if ($phone !== null) {
                $dealQuery = DB::connection(self::DB)->table('deals');
                $dealQuery->where(function ($q) use ($phone): void {
                    $q->where('phone', $phone)
                        ->orWhereRaw('phones @> ?::jsonb', [json_encode([$phone])]);
                });
                if ($tenantId !== null) {
                    $dealQuery->where('tenant_id', $tenantId);
                }

                $deals = $dealQuery->get(['id', 'phone', 'phones']);

                // Хирургическое удаление телефона субъекта из JSONB-массива доп.
                // телефонов: остаются все элементы, не равные $phone; пустой
                // результат -> NULL. COALESCE — NULL-безопасность; порядок
                // со-контактов сохраняется (152-ФЗ: чужие телефоны не трогаем).
                $phonesScrubExpr = '(SELECT JSONB_AGG(elem) '
                    ."FROM JSONB_ARRAY_ELEMENTS(COALESCE(phones, '[]'::jsonb)) AS elem "
                    .'WHERE elem <> TO_JSONB(?::text))';

                foreach ($deals as $deal) {
                    $dealId = (int) $deal->id;

                    if ($deal->phone === $phone) {
                        // Субъект — владелец сделки: анонимизируем скалярные ПДн
                        // и хирургически чистим массив доп. телефонов.
                        DB::connection(self::DB)->update(
                            'UPDATE deals SET phone = ?, contact_name = ?, '
                            ."phones = {$phonesScrubExpr}, updated_at = ? WHERE id = ?",
                            ['+7000XXXXXXX', 'Удалено', $phone, $now, $dealId],
                        );
                    } else {
                        // Субъект — лишь доп. телефон чужой сделки: скалярные
                        // phone/contact_name владельца НЕ трогаем, чистим только массив.
                        DB::connection(self::DB)->update(
                            "UPDATE deals SET phones = {$phonesScrubExpr}, "
                            .'updated_at = ? WHERE id = ?',
                            [$phone, $now, $dealId],
                        );
                    }
                }

                $counts['deals'] = $deals->count();
            }

            // ------------------------------------------------------------------
            // 4. Обновить pd_subject_requests если requestId передан
            // (webhook_log удалён в миграции 2026_05_24_140000_drop_legacy_webhook_artefacts)
            // ------------------------------------------------------------------
            if ($requestId !== null) {
                $summary = "Удалено: users={$counts['users']}, leads={$counts['leads']}, "
                    ."deals={$counts['deals']}";

                DB::connection(self::DB)->table('pd_subject_requests')
                    ->where('id', $requestId)
                    ->update([
                        'status' => 'completed',
                        'completed_at' => $now,
                        'response_text' => $summary,
                    ]);
            }
        });

        return $counts;
    }

    /**
     * Вставить запись в pd_processing_log через BYPASSRLS-соединение.
     */
    private function writePdLog(
        ?int $tenantId,
        string $subjectType,
        int $subjectId,
        int $actorAdminId,
        CarbonImmutable $now,
    ): void {
        DB::connection(self::DB)->table('pd_processing_log')->insert([
            'tenant_id' => $tenantId,
            'subject_type' => $subjectType,
            'subject_id' => $subjectId,
            'action' => 'deleted',
            'purpose' => '152-FZ erasure',
            'actor_admin_user_id' => $actorAdminId,
            'created_at' => $now,
        ]);
    }
}
