<?php

declare(strict_types=1);

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\Tenant;
use App\Repositories\PricingTierRepository;
use App\Services\Billing\BalanceToLeadsConverter;
use App\Services\Billing\RunwayCalculator;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

/**
 * Дашборд — агрегат для DashboardView (audit C1/J3).
 *
 * GET /api/dashboard/summary?tenant_id={id}&range=today|7d|30d
 *
 * На MVP без auth-middleware (tenant_id параметром, как DealController).
 * Production: middleware('auth:sanctum','tenant') → tenant_id из user.
 *
 * Все агрегаты — tenant-scoped, deleted_at IS NULL, is_test=false.
 * RLS-обёртка SET LOCAL app.current_tenant_id (PgBouncer-safe), как DealController.
 */
class DashboardController extends Controller
{
    private const RU_WEEKDAYS = ['вс', 'пн', 'вт', 'ср', 'чт', 'пт', 'сб'];

    public function summary(Request $request): JsonResponse
    {
        // Go-live (audit J3): tenant_id из authed-user (auth:sanctum + tenant
        // middleware), НЕ из параметра запроса — закрывает кросс-tenant утечку KPI.
        $tenantId = (int) $request->user()->tenant_id;

        $tenant = Tenant::find($tenantId);
        if ($tenant === null) {
            return response()->json(['message' => 'Тенант не найден.'], 404);
        }

        $range = in_array($request->query('range'), ['today', '7d', '30d'], true)
            ? (string) $request->query('range')
            : '7d';

        // MSK: activity-бакеты и range-границы должны совпадать с SQL
        // `AT TIME ZONE 'Europe/Moscow'`. config('app.timezone') = UTC.
        $now = CarbonImmutable::now('Europe/Moscow');
        [$windowStart, $prevStart] = match ($range) {
            'today' => [$now->startOfDay(), $now->startOfDay()->subDay()],
            '30d' => [$now->subDays(30), $now->subDays(60)],
            default => [$now->subDays(7), $now->subDays(14)],
        };

        $data = DB::transaction(function () use ($tenantId, $tenant, $now, $range, $windowStart, $prevStart) {
            DB::statement('SET LOCAL app.current_tenant_id = '.$tenantId);

            $base = fn () => DB::table('deals')
                ->where('tenant_id', $tenantId)
                ->whereNull('deleted_at')
                ->where('is_test', false);

            // --- leads received: текущее + предыдущее окно ---
            $curLeads = (clone $base())->whereBetween('received_at', [$windowStart, $now])->count();
            $prevLeads = (clone $base())->whereBetween('received_at', [$prevStart, $windowStart])->count();

            // --- conversion: % статуса 'won' в окне ---
            $curPaid = (clone $base())->where('status', 'won')
                ->whereBetween('received_at', [$windowStart, $now])->count();
            $prevPaid = (clone $base())->where('status', 'won')
                ->whereBetween('received_at', [$prevStart, $windowStart])->count();
            $curConv = $curLeads > 0 ? round($curPaid / $curLeads * 100, 1) : 0.0;
            $prevConv = $prevLeads > 0 ? round($prevPaid / $prevLeads * 100, 1) : 0.0;

            // --- active projects ---
            $activeProjects = DB::table('projects')
                ->where('tenant_id', $tenantId)
                ->where('is_active', true)
                ->count();

            // --- activity: 7 daily-бакетов по received_at (MSK) ---
            $activityStart = $now->subDays(6)->startOfDay();
            $byDay = (clone $base())
                ->where('received_at', '>=', $activityStart)
                ->selectRaw("to_char((received_at AT TIME ZONE 'Europe/Moscow')::date, 'YYYY-MM-DD') AS d, COUNT(*) AS c")
                ->groupBy('d')
                ->pluck('c', 'd');
            $points = [];
            $labels = [];
            for ($i = 6; $i >= 0; $i--) {
                $day = $now->subDays($i);
                $key = $day->format('Y-m-d');
                $points[] = (int) ($byDay[$key] ?? 0);
                $labels[] = $i === 0 ? 'сегодня' : self::RU_WEEKDAYS[(int) $day->format('w')];
            }
            $maxPoint = max(0, ...$points);
            $axisMax = max(10, (int) (ceil($maxPoint / 10) * 10));

            // --- funnel: текущий снимок по статусам ---
            $funnel = (clone $base())
                ->selectRaw('status, COUNT(*) AS c')
                ->groupBy('status')
                ->pluck('c', 'status')
                ->map(fn ($c) => (int) $c)
                ->toArray();

            // --- runway (F3, 17.06.2026: единый источник с биллингом) ---
            // Раньше дашборд считал от legacy `balance_leads` (после Billing v2 ≈0
            // для рублёвых тенантов) → расходился с биллингом «0 дней ↔ N дней».
            // Теперь — affordable leads от рублёвого баланса по тарифу
            // (BalanceToLeadsConverter) + общий RunwayCalculator.
            $activeTiers = app(PricingTierRepository::class)
                ->activeAt(Carbon::now('Europe/Moscow'));
            $conversion = app(BalanceToLeadsConverter::class)->convert(
                (string) $tenant->balance_rub,
                (int) ($tenant->delivered_in_month ?? 0),
                $activeTiers,
            );
            $affordableLeads = (int) $conversion['leads'];
            // B1-2 (UX-аудит 25.06): null (нет активных проектов) НЕ приводим к 0 —
            // иначе дашборд врал «хватит на 0 дней» при полном балансе, расходясь с
            // биллингом «∞». null → фронт показывает «нет активных проектов».
            $runwayDays = app(RunwayCalculator::class)
                ->daysLeft($tenantId, $affordableLeads);

            // --- средняя стоимость лида (F5): среднее фактически списанных rub-сумм
            // за окно периода. Только charge_source='rub' (у prepaid цена 0 по CHECK —
            // иначе среднее занижается); источник тот же, что у карточки сделки (F2).
            // null, если в окне нет rub-списаний (ничего ещё не списано).
            $avgKopecks = DB::table('lead_charges')
                ->where('tenant_id', $tenantId)
                ->where('charge_source', 'rub')
                ->whereBetween('charged_at', [$windowStart, $now])
                ->avg('price_per_lead_kopecks');
            $avgLeadCostRub = $avgKopecks !== null ? round((float) $avgKopecks / 100, 2) : null;

            return [
                'range' => $range,
                'leads_received' => self::deltaBlock($curLeads, $prevLeads, 'delta_pct', self::pctDelta($curLeads, $prevLeads)),
                'conversion' => self::deltaBlock($curConv, $prevConv, 'delta_pp', round($curConv - $prevConv, 1)),
                'active_projects' => ['active' => $activeProjects],
                'balance' => [
                    'amount_rub' => (string) $tenant->balance_rub,
                    'runway_days' => $runwayDays,
                    'runway_leads' => $affordableLeads,
                ],
                'activity' => ['points' => $points, 'labels' => $labels, 'max' => $axisMax],
                'funnel' => (object) $funnel,
                'avg_lead_cost_rub' => $avgLeadCostRub,
            ];
        });

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

    /** Процентная дельта current vs previous; 0.0 если previous=0. */
    private static function pctDelta(float $cur, float $prev): float
    {
        return $prev > 0 ? round(($cur - $prev) / $prev * 100, 1) : 0.0;
    }

    /** Блок {value, <deltaKey>, delta_dir}. */
    private static function deltaBlock(float $value, float $prev, string $deltaKey, float $delta): array
    {
        $dir = $value > $prev ? 'up' : ($value < $prev ? 'down' : 'neutral');

        return ['value' => $value, $deltaKey => $delta, 'delta_dir' => $dir];
    }
}
