<?php

declare(strict_types=1);

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\ActivityLog;
use App\Models\Deal;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

/**
 * Bulk-операции над сделками — transition / destroy / restore.
 *
 * Извлечено из DealController (Sprint 3 Phase A, audit O-refactor-01) — раньше
 * один контроллер был 802 строки с 4 разнородными ответственностями (CRUD +
 * bulk + export + helpers). Этот класс отвечает только за многоразовые
 * массовые операции; single-resource действия остаются в DealController.
 *
 * J1 (Sprint 3F): auth:sanctum+tenant, tenant_id из auth()->user().
 *
 * O-perf-01: N+1 устранён.
 *
 *   transition: сначала SELECT всех сделок tenant'а из ids, чтобы отфильтровать
 *   NO-OP (status уже совпадает) и собрать прежние значения для ActivityLog
 *   (event=deal.status_changed требует context.from). Затем bulk-UPDATE
 *   `whereIn(changed_ids)->update(status)` (1 запрос вместо N) +
 *   ActivityLog::insert(массив строк) (1 запрос вместо N). На 100 сделках это
 *   ≈ 200 запросов → 2 запроса.
 *
 *   destroy / restore: WHERE гарантирует idempotency (deleted_at IS NULL для
 *   destroy; deleted_at IS NOT NULL для restore), поэтому контракт «requested
 *   vs deleted/restored» корректен без foreach. Bulk-UPDATE + bulk-INSERT.
 *
 * RLS + defense-in-depth `where(tenant_id)` сохраняется — поведение из
 * DealTransitionTest «не апдейтит чужие сделки» обязательно.
 */
class DealBulkActionController extends Controller
{
    /**
     * POST /api/deals/transition — bulk status-update.
     *
     * Body: {ids: [int...], status: slug}.
     * Response: {updated, requested, status} (updated = реально изменённых,
     * без NO-OP).
     */
    public function transition(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'ids' => 'required|array|min:1|max:1000',
            'ids.*' => 'integer|min:1',
            'status' => 'required|string|max:50',
        ]);

        $tenantId = (int) $request->user()->tenant_id;

        $statusExists = DB::table('lead_statuses')->where('slug', $validated['status'])->exists();
        if (! $statusExists) {
            return response()->json([
                'message' => 'Неизвестный статус.',
                'errors' => ['status' => ['Slug не найден в lead_statuses.']],
            ], 422);
        }

        $updated = DB::transaction(function () use ($validated, $tenantId, $request) {
            DB::statement('SET LOCAL app.current_tenant_id = '.$tenantId);

            // Фаза 1: SELECT — нужны id и предыдущий status для каждой строки,
            // чтобы (а) отфильтровать NO-OP и (б) сохранить prev в context.from.
            // Defense-in-depth where(tenant_id) — защита от кросс-tenant id.
            $rows = Deal::query()
                ->where('tenant_id', $tenantId)
                ->whereIn('id', $validated['ids'])
                ->get(['id', 'status']);

            $changed = $rows->filter(fn (Deal $d) => $d->status !== $validated['status']);
            if ($changed->isEmpty()) {
                return 0;
            }

            $changedIds = $changed->pluck('id')->all();
            $now = now();

            // Фаза 2: bulk-UPDATE 1 запросом вместо N.
            Deal::query()
                ->where('tenant_id', $tenantId)
                ->whereIn('id', $changedIds)
                ->update([
                    'status' => $validated['status'],
                    'updated_at' => $now,
                ]);

            // Фаза 3: bulk-INSERT в activity_log 1 запросом вместо N.
            // ActivityLog::insert() обходит модельный cast `context => array` —
            // массив сериализуем в JSON руками, остальные scalar-поля передаём
            // напрямую. Триггер audit_chain_hash() заполнит log_hash на уровне БД.
            $logRows = $changed->map(fn (Deal $d) => [
                'tenant_id' => $tenantId,
                'user_id' => (int) $request->user()->id,
                'deal_id' => $d->id,
                'event' => ActivityLog::EVENT_DEAL_STATUS_CHANGED,
                'context' => json_encode([
                    'from' => $d->status,
                    'to' => $validated['status'],
                    'source' => 'bulk',
                ], JSON_UNESCAPED_UNICODE),
                'ip_address' => $request->ip(),
                'user_agent' => $request->userAgent(),
                'created_at' => $now,
            ])->all();

            ActivityLog::insert($logRows);

            return count($changedIds);
        });

        return response()->json([
            'updated' => $updated,
            'requested' => count($validated['ids']),
            'status' => $validated['status'],
        ]);
    }

    /**
     * DELETE /api/deals — bulk soft-delete.
     *
     * Body: {ids: [int...]}.
     * Response: {deleted, requested}.
     *
     * Soft-delete сохраняется (см. документацию в DealController.destroy на
     * предыдущей версии): partition deals + CASCADE-FK от webhook_dedup_keys
     * → hard-delete сломает идемпотентность webhook'ов.
     */
    public function destroy(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'ids' => 'required|array|min:1|max:1000',
            'ids.*' => 'integer|min:1',
        ]);

        $tenantId = (int) $request->user()->tenant_id;

        $deleted = DB::transaction(function () use ($validated, $tenantId, $request) {
            DB::statement('SET LOCAL app.current_tenant_id = '.$tenantId);

            // SELECT id'шников живых сделок tenant'а из ids — для bulk-INSERT
            // в activity_log по списку реально удаляемых (NO-OP idempotency).
            $targetIds = Deal::query()
                ->where('tenant_id', $tenantId)
                ->whereIn('id', $validated['ids'])
                ->whereNull('deleted_at')
                ->pluck('id')
                ->all();

            if ($targetIds === []) {
                return 0;
            }

            $now = now();

            Deal::query()
                ->where('tenant_id', $tenantId)
                ->whereIn('id', $targetIds)
                ->whereNull('deleted_at')
                ->update([
                    'deleted_at' => $now,
                    'updated_at' => $now,
                ]);

            $logRows = array_map(fn (int $id) => [
                'tenant_id' => $tenantId,
                'user_id' => (int) $request->user()->id,
                'deal_id' => $id,
                'event' => ActivityLog::EVENT_DEAL_DELETED,
                'context' => json_encode(['source' => 'bulk'], JSON_UNESCAPED_UNICODE),
                'ip_address' => $request->ip(),
                'user_agent' => $request->userAgent(),
                'created_at' => $now,
            ], $targetIds);

            ActivityLog::insert($logRows);

            return count($targetIds);
        });

        return response()->json([
            'deleted' => $deleted,
            'requested' => count($validated['ids']),
        ]);
    }

    /**
     * POST /api/deals/restore — bulk restore soft-deleted.
     *
     * Body: {ids: [int...]}.
     * Response: {restored, requested}.
     */
    public function restore(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'ids' => 'required|array|min:1|max:1000',
            'ids.*' => 'integer|min:1',
        ]);

        $tenantId = (int) $request->user()->tenant_id;

        $restored = DB::transaction(function () use ($validated, $tenantId, $request) {
            DB::statement('SET LOCAL app.current_tenant_id = '.$tenantId);

            // withTrashed обходит SoftDeletes global scope; whereNotNull —
            // NO-OP idempotency для уже живых.
            $targetIds = Deal::query()
                ->withTrashed()
                ->where('tenant_id', $tenantId)
                ->whereIn('id', $validated['ids'])
                ->whereNotNull('deleted_at')
                ->pluck('id')
                ->all();

            if ($targetIds === []) {
                return 0;
            }

            $now = now();

            Deal::query()
                ->withTrashed()
                ->where('tenant_id', $tenantId)
                ->whereIn('id', $targetIds)
                ->whereNotNull('deleted_at')
                ->update([
                    'deleted_at' => null,
                    'updated_at' => $now,
                ]);

            $logRows = array_map(fn (int $id) => [
                'tenant_id' => $tenantId,
                'user_id' => (int) $request->user()->id,
                'deal_id' => $id,
                'event' => ActivityLog::EVENT_DEAL_RESTORED,
                'context' => json_encode(['source' => 'bulk'], JSON_UNESCAPED_UNICODE),
                'ip_address' => $request->ip(),
                'user_agent' => $request->userAgent(),
                'created_at' => $now,
            ], $targetIds);

            ActivityLog::insert($logRows);

            return count($targetIds);
        });

        return response()->json([
            'restored' => $restored,
            'requested' => count($validated['ids']),
        ]);
    }
}
