<?php

namespace App\Http\Controllers;

use App\Exports\ScheduleCalendarExport;
use App\Exports\TrafficDetailedReportExport;
use App\Exports\TrafficReportPeriodExport;
use App\Http\Requests\StartPhotoReportRequest;
use App\Jobs\GeneratePhotoReport;
use App\Models\Point;
use App\Models\PointPlan;
use App\Exports\TrafficReportExport;
use App\Models\SchedulePlan;
use App\Services\CalendarService;
use App\Services\TrafficReportCalculator;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Maatwebsite\Excel\Facades\Excel;
use App\Exports\ScheduleTemplateExport;
use App\Models\District;
use App\Models\PhotoReport;


class ReportController extends Controller
{
    /* -------------------------------------------------------------
     * VALIDATION‑правила вынесены в одно место
     * ----------------------------------------------------------- */
    protected array $rules = [
        'periodType' => 'nullable|in:month,quarter,half,nine,year',
        'year'       => 'nullable|integer|min:2000',
        'month'      => 'required_if:periodType,month|nullable|integer|between:1,12',
        'quarter'    => 'required_if:periodType,quarter|nullable|integer|between:1,4',
        'half'       => 'required_if:periodType,half|nullable|integer|in:1,2',
        'group_manager_id'    => 'nullable|exists:users,id',
        'subgroup_manager_id' => 'nullable|exists:users,id',
        'district_id'         => 'nullable|exists:districts,id',
        'request_id'          => 'nullable|integer|exists:requests,id',
    ];

    protected function periodBounds(array $p): array
    {
        $year = (int)$p['year'];

        return match ($p['periodType']) {
            'month' => [
                Carbon::create($year, $p['month'], 1)->startOfDay(),
                Carbon::create($year, $p['month'], 1)->endOfMonth(),
            ],
            'quarter' => (function () use ($year, $p) {
                $m = ($p['quarter'] - 1) * 3 + 1;                     // 1,4,7,10
                return [
                    Carbon::create($year, $m, 1)->startOfDay(),
                    Carbon::create($year, $m + 2, 1)->endOfMonth(),
                ];
            })(),
            'half' => (function () use ($year, $p) {
                $m = ($p['half'] - 1) * 6 + 1;                         // 1 или 7
                return [
                    Carbon::create($year, $m, 1)->startOfDay(),
                    Carbon::create($year, $m + 5, 1)->endOfMonth(),
                ];
            })(),
            'nine' => [
                Carbon::create($year, 5, 1)->startOfDay(),       // 1 мая
                Carbon::create($year + 1, 2, 28)->endOfMonth(),      // 28 февраля
            ],
            default => [
                Carbon::create($year, 1, 1)->startOfDay(),
                Carbon::create($year, 12, 31)->endOfDay(),
            ],
        };
    }

    protected function collectPlans(array $p)
    {
        [$from, $to] = $this->periodBounds($p);

        $allowedDistrictIds = $this->getAllowedDistrictIdsForCurrentUser();

        // если список пуст – вернём пустую коллекцию, чтобы не грузить БД
        if (empty($allowedDistrictIds)) {
            return collect();
        }

        $q = PointPlan::with(['point.district','measurements'])
            ->whereBetween('day', [$from, $to])
            ->whereHas('point.district', fn($q) => $q->whereIn('id', $allowedDistrictIds));

        /* ---- новые фильтры ---- */
        if (!empty($p['group_manager_id'])) {
            $q->forGroupManager((int)$p['group_manager_id']);
        }

        if (!empty($p['subgroup_manager_id'])) {
            $q->forSubgroupManager((int)$p['subgroup_manager_id']);
        }

        if (!empty($p['district_id'])) {
            $q->forDistrict((int)$p['district_id']);
        }

        return $q->get();
    }

    public function exportExcel(PointPlan $plan)
    {
        $filename = "traffic_report_plan_{$plan->id}.xlsx";
        return Excel::download(new TrafficReportExport($plan), $filename);
    }

    public function trafficJson(Request $req)
    {
        $req->validate([
            'periodType' => 'required|in:month,quarter,half,year',
            'year' => 'required|integer|min:2000',
            'month' => 'nullable|integer|between:1,12',
            'quarter' => 'nullable|integer|between:1,4',
            'half' => 'nullable|integer|in:1,2',
        ]);

        // Логика та же, что и в экспорте, но возвращаем JSON
        $export = new TrafficReportPeriodExport($req->only([
            'periodType', 'year', 'month', 'quarter', 'half'
        ]));

        $rows = $export->array();
        return response()->json(['data' => $rows]);
    }


    /* --------------------------------------------------------------------
     * 2.  JSON‑отдача для фронта
     * ------------------------------------------------------------------ */
    public function trafficDetailedJson(Request $req)
    {
        $p = $req->validate(array_merge($this->rules, [
            'request_id' => 'nullable|integer|exists:requests,id',
        ]));
        if (! empty($p['request_id']) && empty($p['periodType'])) {
            // 1) same base query as in exportDetailed
            $q = PointPlan::with(['point.district','measurements'])
                ->where('request_id', (int)$p['request_id'])
                ->where('is_active', true);

            // менеджер группы
            if (!empty($p['group_manager_id'])) {
                $q->forGroupManager((int)$p['group_manager_id']);
            }

            // менеджер подгруппы
            if (!empty($p['subgroup_manager_id'])) {
                $q->forSubgroupManager((int)$p['subgroup_manager_id']);
            }

            // район
            if (!empty($p['district_id'])) {
                $q->forDistrict((int)$p['district_id']);
            }

            // 2) group by point_id and pick the latest day only
            $plans = $q->get()
                ->groupBy('point_id')
                ->map(fn($group) => $group->sortByDesc('day')->first())
                ->values();
        }
        else {
            $plans = $this->collectPlans($p);
        }
        $result = $plans->map(function (PointPlan $plan) {
            $calc = (new TrafficReportCalculator($plan))->calculate();
            return [
                'id' => $plan->id,
                'record_number' => $plan->point->record_number,
                'name' => $plan->point->name,
                'km_from' => $kmFrom = $this->toKm($plan->point->accounting_point),
                'km_to' => $kmFrom + $plan->point->length,
                'length' => (float)$plan->point->length,
                'dailyByCat' => array_values($calc['detail']),   // 14 шт
                'totalDaily' => $calc['totalNcs'],
                'totalCarEq' => $calc['totalCarEq'],
                'hourly' => $calc['totalNcs'] / 24,
                'hourlyEq' => $calc['totalCarEq'] / 24,
                'peak50' => $calc['meta']['peak50'],
            ];
        });

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

    public function exportTrafficReport(Request $req)
    {
        // 1) Валидация
        $p = $req->validate([
            'periodType' => 'required|in:month,quarter,half,year',
            'year'       => 'required|integer|min:2000',
            'month'      => 'nullable|integer|between:1,12',
            'quarter'    => 'nullable|integer|between:1,4',
            'half'       => 'nullable|integer|in:1,2',
            'group_manager_id'    => 'nullable|exists:users,id',
            'subgroup_manager_id' => 'nullable|exists:users,id',
            'district_id'         => 'nullable|exists:districts,id',
            'request_id'          => 'nullable|exists:requests,id',
        ]);

        // 2) Собираем только те планы, что попадают в выбранный период и по фильтрам
        $plans = $this->collectPlans($p);

        // 3) Делаем экспорт
        $filename = 'traffic_report_' . now()->format('Ymd_His') . '.xlsx';
        return Excel::download(
            new TrafficDetailedReportExport($plans),
            $filename
        );
    }

    public function exportDetailed(Request $req)
    {
        $p = $req->validate(array_merge($this->rules, [
            'request_id' => 'nullable|integer|exists:requests,id',
        ]));

        $districtName = '';
        $managerName = '';
        if (!empty($p['district_id'])) {
            $districtName = District::findOrFail($p['district_id'])->name;

            $district = District::with(['subgroupManager','groupManager'])->find($p['district_id']);
            $mgr = $district->subgroupManager;
            $managerName = $mgr->full_name ?? '';

        }
        /* ───────────────────────────────────────────────────────────── */
        /* 1. ЗАЯВКА БЕЗ ПЕРИОДА → только самый свежий день каждой точки */
        /* ───────────────────────────────────────────────────────────── */
        if (!empty($p['request_id']) && empty($p['periodType'])) {

            $q = PointPlan::with(['point.district','measurements'])
                ->where('request_id', (int)$p['request_id'])
                ->where('is_active', true);

            // дополнительные фильтры
            if (!empty($p['group_manager_id']))    $q->forGroupManager((int)$p['group_manager_id']);
            if (!empty($p['subgroup_manager_id'])) $q->forSubgroupManager((int)$p['subgroup_manager_id']);
            if (!empty($p['district_id']))         $q->forDistrict((int)$p['district_id']);

            /* ← в этом режиме оставляем только последний день каждой точки */
            $plans = $q->get()
                ->groupBy('point_id')
                ->map(fn($g) => $g->sortByDesc('day')->first())
                ->values();

            $filename = sprintf('detailed_request_%d_%s.xlsx',
                $p['request_id'], now()->format('Ymd_His'));
        }

        /* ───────────────────────────────────────────────────────────── */
        /* 2. Есть periodType (месяц / квартал / …)                     */
        /*    → передаём в экспорт ВСЕ планы внутри выбранного периода  */
        /* ───────────────────────────────────────────────────────────── */
        else {

            /* collectPlans уже применяет все временные и менеджер-фильтры */
            $plans = $this->collectPlans($p);   // ⚠️ БЕЗ обрезки до “latest”

            $filename = sprintf(
                'detailed_traffic_%s_%s_%s.xlsx',
                $p['periodType'] ?? 'all',
                $p['year']      ?? 'any',
                now()->format('Ymd_His')
            );
        }

        return Excel::download(new TrafficDetailedReportExport($plans, $districtName, $managerName), $filename);
    }

    /**
     * Переводит строку вида «0+150» в 0.150 (и «25» в 25.000)
     */
    private function toKm(string|int|float $raw): float
    {
        $s = (string)$raw;
        if (str_contains($s, '+')) {
            $s = str_replace('+', '.', $s);
        }
        return (float)$s;
    }

    public function exportScheduleExcel(Request $request, int $scheduleId)
    {
        // 1) Приведение "null"→NULL (когда из JS приходит строка 'null')
        if ($request->query('request_id') === 'null') {
            $request->merge(['request_id' => null]);
        }

        // 2) Валидация входных параметров
        $request->validate([
            'month'      => 'required|integer|between:1,12',
            'district'   => 'nullable|string',
            'freq'       => 'nullable|in:1,2',
            'request_id' => 'nullable|integer|exists:requests,id',
        ]);

        $month   = (int) $request->input('month');
        $filters = $request->only(['district', 'freq', 'request_id']);

        // 3) Берём год из плана-графика
        $schedule = SchedulePlan::findOrFail($scheduleId);
        $year     = $schedule->year;

        // 4) Строим календарь и оставляем только выбранный месяц
        $fullCalendar = CalendarService::build($year);
        $calendar     = [ $fullCalendar[$month - 1] ];

        // 5) Объединяем фильтрацию **точно** как на фронте:
        //    – по списку доступных районов текущего пользователя
        //    – по выбранному «району» (districtFilter)
        //    – по frequency (freqFilter)
        //    – по привязке к заявке (request_id)
        //    – НЕ ограничиваем сами точки по месяцу: остаются все
        //      отфильтрованные, у которых plans может быть пусто
        $allowedDistricts = $this->getAllowedDistrictIdsForCurrentUser();

        $pts = Point::query()
            ->with([
                'plans' => fn($q) => $q->where('schedule_id', $scheduleId),
                'requests',
                'district',
            ])
            ->whereIn('district_id', $allowedDistricts)
            ->when($filters['district'], fn($q, $v) =>
                $q->whereHas('district', fn($q2) => $q2->where('name', $v))
            )
            ->when($filters['freq'], fn($q, $v) =>
                $q->where('monitoring_freq', $v)
            )
            ->when($filters['request_id'], fn($q, $v) =>
                $q->whereHas('requests', fn($q2) =>
                    $q2->whereKey($v)
                )
            )
            ->orderBy('record_number')
            ->get();

        // 6) Генерируем и отдаём Excel
        $export = new ScheduleCalendarExport($pts, $calendar, $year, $month);
        $filename = sprintf('schedule_%d_%02d_%s.xlsx',
            $scheduleId, $month, now()->format('Ymd_His')
        );

        return Excel::download($export, $filename);
    }

    public function downloadks2(Request $request)
    {
        $validated = $request->validate([
            'request_id'  => 'required|integer|exists:requests,id',
            'district_id' => 'nullable|integer|exists:districts,id',
            'season'      => 'required|in:1,2', // 1=Лето, 2=Зима
            'act_date'     => 'required|date',
        ]);

        $template = $validated['season'] == 1
            ? storage_path('app/templates/schedule-template-s.xlsx')
            : storage_path('app/templates/schedule-template-w.xlsx');

        // 2) Название «района» (или области, если не выбран район)
        // Явно вытаскиваем district_id (или null)
        $districtId = $validated['district_id'] ?? null;

        $districtName = $districtId
            ? District::findOrFail($districtId)->name
            : '';

        $actDate  = Carbon::parse( $validated['act_date'])->format('d.m.Y');

        // 4) границы работ по заявке
        $minDay = PointPlan::where('request_id', $validated['request_id'])->min('day');
        $maxDay = PointPlan::where('request_id', $validated['request_id'])->max('day');

        $startWork = $minDay ? Carbon::parse($minDay)->format('d.m.Y') : '';
        $endWork   = $maxDay ? Carbon::parse($maxDay)->format('d.m.Y') : '';

        // **количество точек в заявке для выбранного района**
        $pointsCount = Point::whereHas('requests', fn($q) =>
        $q->whereKey($validated['request_id'])
        )
            ->when($districtId, fn($q) => $q->where('district_id', $districtId))
            ->count();

        // Вычисляем ФИО:
        $managerName = '';
        if ($districtId) {
            $district = District::with(['subgroupManager','groupManager'])->find($districtId);
            // если есть менеджер подгруппы — берём его, иначе группового
            $mgr = $district->subgroupManager ?? $district->groupManager;
            $managerName = $mgr?->full_name ?? '';
        }

        // Собираем ваши строки (array или Collection)
        $rows = collect([]);
        $startCell = 'A1';

        // Создаём экспорт и отдаем файл
        $export   = new ScheduleTemplateExport(
            $rows,
            $template,
            $startCell,
            $districtName,
            $actDate,
            $startWork,
            $endWork,
            $pointsCount,
            $managerName,
        );
        $fileName = 'schedule-filled_' . now()->format('Ymd_His') . '.xlsx';

        return Excel::download($export, $fileName);
    }

    /**
     * Генерация фотоотчета на сервере через очередь
     * @param Request $req
     * @return JsonResponse
     */
    public function startPhotoReport(StartPhotoReportRequest $request)
    {
        $data = $request->validated();

        // 1) Сохраняем запись о задаче
        $report = PhotoReport::create([
            'user_id'     => Auth::id(),
            'request_id'  => $data['request_id'],
            'district_id' => $data['district_id'] ?? null,
            'status'      => 'pending',
        ]);
        // Создаём задачу в очереди. В ответ отдадим ID задачи
        $job = GeneratePhotoReport::dispatch($report->id);

        return response()->json([
            'jobId'   => $report->id,
        ], 202);
    }

    public function photoReportStatus ($jobId)
    {
        $report = PhotoReport::findOrFail($jobId);

        $url = null;
        if ($report->status === 'done') {
            // если бакет публичный:
            $url = Storage::disk('s3')->url($report->output_path);

        }

        return response()->json([
            'status' => $report->status,
            'url'    => $url,
            'error'  => $report->error_message,
        ]);
    }

    /**
     * Вернуть все отчёты текущего пользователя
     */
    public function listPhotoReports()
    {
        $reports = PhotoReport::with(['district', 'request'])
            ->where('user_id', Auth::id())
            ->orderByDesc('created_at')
            ->get()
            ->map(fn($r) => [
                'jobId'        => $r->id,
                'requestName'  => $r->request->name,
                'districtName' => $r->district?->name ?: 'Вся область',
                'createdAt'    => $r->created_at->format('Y-m-d H:i'),
                'status'       => $r->status,
                'url'          => $r->status === 'done'
                    ? Storage::disk('s3')->url($r->output_path)
                    : null,
                'error'        => $r->error_message,
            ]);

        return response()->json(['data' => $reports]);
    }

}
