<?php

declare(strict_types=1);

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Http\Requests\BulkProjectActionRequest;
use App\Http\Requests\StoreProjectRequest;
use App\Http\Requests\UpdateProjectRequest;
use App\Http\Resources\ProjectResource;
use App\Jobs\SyncSupplierProjectJob;
use App\Models\Project;
use App\Models\Tenant;
use App\Repositories\PricingTierRepository;
use App\Services\Billing\BalancePreflightService;
use App\Services\Project\ProjectService;
use App\Services\Requisites\RequisitesService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

/**
 * Проекты tenant'а — расширенный API для ProjectsView + NewDealDialog.
 *
 * index: фильтры по signal_type/status/search, пагинация, batch-fetch по ids.
 * show:  детальная карточка проекта с supplier_links.
 *
 * Auth: auth:sanctum + tenant middleware (устанавливает app.current_tenant_id для RLS).
 * Task 2 Plan 5 заменяет MVP-версию (tenant_id параметром, без auth).
 */
class ProjectController extends Controller
{
    public function __construct(
        private readonly ProjectService $projects,
        private readonly RequisitesService $requisites,
    ) {}

    /** GET /api/projects */
    public function index(Request $request): JsonResponse
    {
        $query = Project::query()
            ->with(['supplierB1', 'supplierB2', 'supplierB3']) // eager-load to avoid N+1 in aggregation helpers
            ->withCount('supplierProjects') // ProjectResource::source_locked — анти-N+1 (hasLinks без per-row запроса)
            ->where('tenant_id', $request->user()->tenant_id);

        // Batch-fetch по ids — возвращает без пагинации (для dropdown'ов и т.п.)
        if ($ids = $request->query('ids')) {
            // '?ids=' batch fetch. Non-numeric and zero values silently dropped via intval+filter
            // (intval('abc')=0 → array_filter drops 0). Acceptable for a read-only dropdown:
            // invalid input produces empty result, not 422.
            $idArray = array_filter(array_map('intval', explode(',', (string) $ids)));
            $items = $query->whereIn('id', $idArray)->get();

            return response()->json(['data' => ProjectResource::collection($items)]);
        }

        // Фильтр по типу сигнала
        if ($type = $request->query('signal_type')) {
            $query->where('signal_type', $type);
        }

        // Фильтр по статусу жизненного цикла
        $status = $request->query('status');
        if ($status === 'active') {
            $query->where('is_active', true);
        } elseif ($status === 'paused') {
            $query->where('is_active', false);
        }
        // default → no extra filter

        // Поиск по name и signal_identifier
        if ($search = $request->query('search')) {
            $query->where(function ($q) use ($search) {
                $q->where('name', 'ilike', "%{$search}%")
                    ->orWhere('signal_identifier', 'ilike', "%{$search}%");
            });
        }

        // #7: фильтр по региону (subject code 1..89). Проект под фильтр попадает, если
        // его regions[] содержит код ИЛИ пуст (= вся РФ, имплицитно покрывает любой регион).
        $region = (int) $request->query('region', '0');
        if ($region >= 1 && $region <= 89) {
            $query->where(function ($q) use ($region) {
                $q->whereRaw('regions @> ARRAY[?]::int[]', [$region])
                    ->orWhereRaw("regions = '{}'::int[]")
                    ->orWhereNull('regions');
            });
        }

        // #7: фильтр по дню недели приёма (0=Пн..6=Вс — same bit-index, как в UI dayLabels).
        $day = $request->query('delivery_day');
        if ($day !== null && $day !== '' && (int) $day >= 0 && (int) $day <= 6) {
            $bit = 1 << (int) $day;
            $query->whereRaw('(delivery_days_mask & ?) <> 0', [$bit]);
        }

        // #7: сортировка. Whitelist + опциональный '-' для desc. Default = '-delivered_today'
        // (карточки с активной доставкой за сегодня видны сверху, как просил заказчик).
        $sortRaw = (string) $request->query('sort', '-delivered_today');
        $desc = str_starts_with($sortRaw, '-');
        $sortField = ltrim($sortRaw, '-');
        $sortable = ['delivered_today', 'delivered_in_month', 'daily_limit_target', 'name', 'created_at'];
        if (! in_array($sortField, $sortable, true)) {
            $sortField = 'delivered_today';
            $desc = true;
        }

        // #6: per_page до 200 (было 100). UI-селектор: 20/50/100/200.
        $perPage = min((int) $request->query('per_page', '20'), 200);
        $projects = $query
            ->orderBy($sortField, $desc ? 'desc' : 'asc')
            ->orderBy('id', 'desc') // стабильный tie-breaker для пагинации
            ->paginate($perPage);

        return response()->json([
            'data' => ProjectResource::collection($projects->items()),
            'meta' => [
                'current_page' => $projects->currentPage(),
                'per_page' => $projects->perPage(),
                'total' => $projects->total(),
            ],
        ]);
    }

    /** POST /api/projects */
    public function store(StoreProjectRequest $request): JsonResponse
    {
        $validated = $request->validated();
        $tenant = $request->user()->tenant;

        // G1/SP2: гейт первого проекта — нельзя создать первый проект без минимальных реквизитов.
        if (Project::where('tenant_id', $tenant->id)->count() === 0
            && ! $this->requisites->isLightComplete($tenant)) {
            return response()->json(['error' => 'requisites_required'], 422);
        }

        $forceSaveBlocked = (bool) ($validated['force_save_blocked'] ?? false);
        unset($validated['force_save_blocked']);

        // Spec C §3.4: преfflight баланса при создании. existingLimit учитывает только активные.
        $existingLimit = (int) Project::where('tenant_id', $tenant->id)
            ->where('is_active', true)
            ->whereNull('preflight_blocked_at')
            ->sum('daily_limit_target');
        $wouldBeRequired = $existingLimit + (int) $validated['daily_limit_target'];

        $preflight = $this->runPreflight($tenant, $wouldBeRequired);

        if (! $preflight['passes'] && ! $forceSaveBlocked) {
            return response()->json([
                'error' => 'balance_insufficient',
                'current_balance_rub' => (string) $tenant->balance_rub,
                'current_capacity_leads' => $preflight['capacity_leads'],
                'would_be_required_leads' => $wouldBeRequired,
                'deficit_leads' => $preflight['deficit_leads'],
            ], 409);
        }

        if (! $preflight['passes'] && $forceSaveBlocked) {
            $validated['preflight_blocked_at'] = now();
        }

        $project = $this->projects->create($tenant, $validated);

        return response()->json(['data' => new ProjectResource($project->loadCount('supplierProjects'))], 201);
    }

    /** PATCH /api/projects/{id} */
    public function update(UpdateProjectRequest $request, int $id): JsonResponse
    {
        $project = Project::where('tenant_id', $request->user()->tenant_id)->findOrFail($id);
        $validated = $request->validated();
        $tenant = $request->user()->tenant;
        $forceSaveBlocked = (bool) ($validated['force_save_blocked'] ?? false);
        unset($validated['force_save_blocked']);

        // Spec C §3.4: преfflight при изменении лимита — учитываем новое значение для ЭТОГО
        // проекта + лимиты остальных активных не-blocked.
        if (array_key_exists('daily_limit_target', $validated)) {
            $existingLimit = (int) Project::where('tenant_id', $tenant->id)
                ->where('id', '!=', $project->id)
                ->where('is_active', true)
                ->whereNull('preflight_blocked_at')
                ->sum('daily_limit_target');
            $wouldBeRequired = $existingLimit + (int) $validated['daily_limit_target'];

            $preflight = $this->runPreflight($tenant, $wouldBeRequired);

            if (! $preflight['passes'] && ! $forceSaveBlocked) {
                return response()->json([
                    'error' => 'balance_insufficient',
                    'current_balance_rub' => (string) $tenant->balance_rub,
                    'current_capacity_leads' => $preflight['capacity_leads'],
                    'would_be_required_leads' => $wouldBeRequired,
                    'deficit_leads' => $preflight['deficit_leads'],
                ], 409);
            }

            if (! $preflight['passes'] && $forceSaveBlocked) {
                $validated['preflight_blocked_at'] = now();
            }
        }

        $updated = $this->projects->update($project, $validated);

        return response()->json(['data' => new ProjectResource($updated->loadCount('supplierProjects'))]);
    }

    /**
     * @return array{passes: bool, capacity_leads: int, deficit_leads: int}
     */
    private function runPreflight(Tenant $tenant, int $requiredLeads): array
    {
        // Косяк 01: действующая версия тарифа по дате (как списание/витрина), а не «по-простому».
        $tiers = app(PricingTierRepository::class)->activeAt(now('Europe/Moscow'));

        // Safe fallback: без активных pricing_tiers биллинг не настроен —
        // преfflight не имеет смысла, пропускаем (legacy-окружения / тесты).
        if ($tiers->isEmpty()) {
            return ['passes' => true, 'capacity_leads' => PHP_INT_MAX, 'deficit_leads' => 0];
        }

        $result = (new BalancePreflightService)->evaluate(
            balanceRub: (string) $tenant->balance_rub,
            deliveredInMonth: (int) $tenant->delivered_in_month,
            requiredLeads: $requiredLeads,
            tiers: $tiers,
        );

        return [
            'passes' => $result->passes,
            'capacity_leads' => $result->capacityLeads,
            'deficit_leads' => $result->deficitLeads,
        ];
    }

    /** GET /api/projects/{id} */
    public function show(Request $request, int $id): JsonResponse
    {
        $project = Project::with(['supplierB1', 'supplierB2', 'supplierB3']) // eager-load to avoid N+1
            ->withCount('supplierProjects') // ProjectResource::source_locked — анти-N+1
            ->where('tenant_id', $request->user()->tenant_id)
            ->findOrFail($id);

        return response()->json(['data' => new ProjectResource($project)]);
    }

    /** DELETE /api/projects/{id} — hard delete (guard по сделкам: 422 если есть сделки) */
    public function destroy(Request $request, int $id): JsonResponse
    {
        $project = Project::where('tenant_id', $request->user()->tenant_id)->findOrFail($id);
        $this->projects->delete($project);

        return response()->json(null, 204);
    }

    /** POST /api/projects/{id}/sync — re-dispatch SyncSupplierProjectJob */
    public function sync(Request $request, int $id): JsonResponse
    {
        $project = Project::where('tenant_id', $request->user()->tenant_id)->findOrFail($id);
        $this->projects->triggerSync($project);

        return response()->json(['queued' => true, 'sync_status' => 'pending'], 202);
    }

    /** PATCH /api/projects/{id}/toggle-active — flip is_active flag */
    public function toggleActive(Request $request, int $id): JsonResponse
    {
        $request->validate(['is_active' => ['required', 'boolean']]);
        $project = Project::where('tenant_id', $request->user()->tenant_id)->findOrFail($id);

        // Spec: docs/superpowers/plans/2026-05-26-supplier-snapshot-guard.md (Task 11).
        // paused_at — anchor для SupplierSnapshotGuard grace-расчёта.
        $newActive = $request->boolean('is_active');
        $project->update([
            'is_active' => $newActive,
            'paused_at' => $newActive ? null : now(),
        ]);

        // #10: pause/resume must reach the supplier. The job's group recompute pushes
        // status=paused when no active project of the group remains (resume → active).
        // G (балансовый блок): заблокированный за нехваткой баланса проект не
        // возобновляется/синхронизируется у поставщика (зеркалит create-гард).
        if ($project->preflight_blocked_at === null) {
            SyncSupplierProjectJob::dispatch($project->id);
        }

        return response()->json(['data' => new ProjectResource($project->fresh()->loadCount('supplierProjects'))]);
    }

    /** POST /api/projects/bulk — batch pause/resume/delete/update_regions/update_days/update_limit */
    public function bulk(BulkProjectActionRequest $request): JsonResponse
    {
        $tenantId = $request->user()->tenant_id;
        $ids = $this->projects->resolveBulkScope(
            $tenantId,
            $request->validated('ids'),
            $request->validated('scope.filter'),
        );

        if (count($ids) > ProjectService::BULK_MAX) {
            return response()->json([
                'errors' => ['scope' => ['Слишком много проектов под фильтр (>500). Уточните фильтры или выберите вручную.']],
            ], 422);
        }

        $payload = array_merge($request->validated(), ['ids' => $ids]);

        $result = $this->projects->bulkAction($tenantId, $request->validated('action'), $payload);

        return response()->json($result);
    }
}
