<?php

declare(strict_types=1);

namespace App\Services\Billing;

use App\Jobs\SyncSupplierProjectJob;
use App\Mail\BalanceUnfrozenMail;
use App\Models\Project;
use App\Models\Tenant;
use App\Repositories\PricingTierRepository;
use App\Services\Supplier\SupplierExportMode;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;

/**
 * Единый путь разблокировки по балансу (F/J, 23.06.2026, спека balance-lock-unify-FJ):
 * снимает ОБА замка — клиентскую заморозку (tenants.frozen_by_balance_at) и проектный
 * блок (projects.preflight_blocked_at) — по политике «всё-или-ничего».
 *
 * Расчёт required — единый, через Tenant::requiredLeadsForTomorrow() (полный лимит, D2).
 * Вызывается из BillingTopupService::topup (пополнение, B) и из BalancePreflightSweepJob
 * (вечерний пересчёт 18:00, passes-ветка, F).
 *
 * Иерархия (J): заморозка — главный замок. Хватает (passes) → размораживаем клиента
 * (если был заморожен) и снимаем блоки всех проектов; не хватает → не трогаем ничего
 * (заморозку ставит сам sweep отдельно).
 */
final class ProjectBlockReleaseService
{
    public function __construct(
        private readonly BalancePreflightService $preflight = new BalancePreflightService,
        private readonly PricingTierRepository $tiers = new PricingTierRepository,
    ) {}

    /**
     * @return array{released:int, unfrozen:bool, passes:bool, required_leads:int, capacity_leads:int, deficit_leads:int}
     */
    public function releaseForTenant(int $tenantId): array
    {
        $none = ['released' => 0, 'unfrozen' => false, 'passes' => true, 'required_leads' => 0, 'capacity_leads' => 0, 'deficit_leads' => 0];

        // Косяк 01: берём ДЕЙСТВУЮЩУЮ версию тарифа по дате (как списание/витрина),
        // а не «по-простому» — иначе при нескольких версиях цены садимся на устаревшую.
        $tiers = $this->tiers->activeAt(now('Europe/Moscow'));
        if ($tiers->isEmpty()) {
            return $none; // биллинг не настроен — нечего пересчитывать.
        }

        $tenant = Tenant::find($tenantId);
        if ($tenant === null) {
            return $none;
        }

        // Единый расчёт (D2): полный лимит активных проектов.
        $required = $tenant->requiredLeadsForTomorrow();
        $result = $this->preflight->evaluate(
            balanceRub: (string) $tenant->balance_rub,
            deliveredInMonth: (int) $tenant->delivered_in_month,
            requiredLeads: $required,
            tiers: $tiers,
        );

        $wasFrozen = $tenant->frozen_by_balance_at !== null;

        if (! $result->passes) {
            // Не хватает на весь заказ — не снимаем ни один замок (всё-или-ничего).
            return [
                'released' => 0,
                'unfrozen' => false,
                'passes' => false,
                'required_leads' => $required,
                'capacity_leads' => $result->capacityLeads,
                'deficit_leads' => $result->deficitLeads,
            ];
        }

        // passes → снять оба замка.
        // 1. Разморозка клиента (если был заморожен): зеркалит sweep frozen→active.
        if ($wasFrozen) {
            $frozenAtWas = $tenant->frozen_by_balance_at;
            $tenant->frozen_by_balance_at = null;
            $tenant->save();

            DB::connection('pgsql_supplier')->table('projects')
                ->where('tenant_id', $tenant->id)
                ->where('paused_at', '>=', $frozenAtWas)
                ->update(['paused_at' => null]);

            // ВАЖНО (23.06.2026): balance_freeze_log пишем через ГЛАВНОЕ соединение, а не
            // pgsql_supplier. Эта таблица FK-ссылается на tenants → INSERT берёт FOR KEY SHARE
            // на строку tenant. Топап держит FOR UPDATE на той же строке (lockForUpdate) в
            // ГЛАВНОЙ транзакции; INSERT через ОТДЕЛЬНОЕ соединение pgsql_supplier ждал бы этот
            // лок вечно (межсессионный self-lock — подтверждён pg_blocking_pids на докалке).
            // В одной транзакции FK-лок совместим с собственным FOR UPDATE. RLS соблюдена:
            // app.current_tenant_id выставлен и в HTTP (middleware), и в sweep (SET LOCAL);
            // INSERT-грант на balance_freeze_log есть у app-ролей. paused_at — UPDATE не-FK-
            // колонки, родителя не лочит, поэтому остаётся на pgsql_supplier без изменений.
            DB::table('balance_freeze_log')->insert([
                'tenant_id' => $tenant->id,
                'event_type' => 'unfrozen',
                'triggered_by' => 'balance_release',
                'balance_rub_at_event' => $tenant->balance_rub,
                'required_leads' => $result->requiredLeads,
                'capacity_leads' => $result->capacityLeads,
                'total_daily_limit' => $result->requiredLeads,
                'details' => json_encode(['deficit_leads' => $result->deficitLeads]),
                'created_at' => now(),
            ]);

            Mail::queue(new BalanceUnfrozenMail($tenant, $result));
        }

        // 2. Снятие блоков проектов.
        $releasedIds = [];
        $blocked = Project::where('tenant_id', $tenantId)
            ->where('is_active', true)
            ->whereNotNull('preflight_blocked_at')
            ->get(['id']);
        foreach ($blocked as $p) {
            Project::where('id', $p->id)->update(['preflight_blocked_at' => null]);
            $releasedIds[] = (int) $p->id;
        }

        // 3. Синк к поставщику.
        if ($wasFrozen) {
            // Разморозка: пересинк всех активных незаблокированных проектов (зеркало sweep, online-gated).
            if (SupplierExportMode::isOnline()) {
                $ids = Project::where('tenant_id', $tenantId)
                    ->where('is_active', true)
                    ->whereNull('preflight_blocked_at')
                    ->pluck('id');
                foreach ($ids as $id) {
                    SyncSupplierProjectJob::dispatch((int) $id);
                }
            }
        } else {
            // Только снятие блока: синк снятых проектов (как было в E).
            foreach ($releasedIds as $id) {
                SyncSupplierProjectJob::dispatch($id);
            }
        }

        return [
            'released' => count($releasedIds),
            'unfrozen' => $wasFrozen,
            'passes' => true,
            'required_leads' => $required,
            'capacity_leads' => $result->capacityLeads,
            'deficit_leads' => 0,
        ];
    }
}
