<?php

namespace App\Http\Controllers;

use Carbon\Carbon;
use Illuminate\Database\Query\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpFoundation\StreamedResponse;

class TimeDomainGraphDataController extends Controller
{
    /**
     * Display filtered time-domain graph data.
     */
    public function index(Request $request)
    {
        $validated = $request->validate([
            'device_id' => ['nullable', 'string', 'max:100'],
            'from'      => ['nullable', 'date'],
            'to'        => ['nullable', 'date'],
            'per_page'  => ['nullable', 'integer', 'in:25,50,100,200,500'],
        ]);

        $this->validateDateRange(
            $validated['from'] ?? null,
            $validated['to'] ?? null
        );

        $query = $this->buildFilteredQuery($request);

        $perPage = (int) $request->input('per_page', 50);

        $records = $query
            ->orderByDesc('created_at')
            ->orderByDesc('id')
            ->paginate($perPage)
            ->withQueryString();

        /*
         * Load device IDs for the dropdown.
         * If the table contains millions of rows, it is better to load
         * device IDs from your master devices table instead.
         */
        $deviceIds = DB::table('time_domain_graph_data')
            ->whereNotNull('device_id')
            ->where('device_id', '<>', '')
            ->distinct()
            ->orderBy('device_id')
            ->pluck('device_id');

        return view('time_domain_data.index', compact(
            'records',
            'deviceIds'
        ));
    }

    /**
     * Export the filtered records as a CSV file.
     */
    public function exportCsv(Request $request): StreamedResponse
    {
        $validated = $request->validate([
            'device_id' => ['nullable', 'string', 'max:100'],
            'from'      => ['nullable', 'date'],
            'to'        => ['nullable', 'date'],
        ]);

        $this->validateDateRange(
            $validated['from'] ?? null,
            $validated['to'] ?? null
        );

        $query = $this->buildFilteredQuery($request)
            ->orderBy('created_at')
            ->orderBy('id');

        $fileName = $this->generateCsvFileName($request);

        return response()->streamDownload(function () use ($query) {
            $output = fopen('php://output', 'w');

            if ($output === false) {
                throw new \RuntimeException('Unable to open CSV output stream.');
            }

            /*
             * Add UTF-8 BOM so Microsoft Excel opens the CSV properly.
             */
            fwrite($output, "\xEF\xBB\xBF");

            fputcsv($output, [
                'ID',
                'Device ID',
                'V1',
                'V2',
                'V3',
                'I1',
                'I2',
                'I3',
                'Channel Group',
                'Created At',
            ]);

            /*
             * chunkById avoids loading all records into memory.
             */
            $query->chunkById(5000, function ($records) use ($output) {
                foreach ($records as $record) {
                    fputcsv($output, [
                        $record->id,
                        $record->device_id,
                        $record->v1,
                        $record->v2,
                        $record->v3,
                        $record->i1,
                        $record->i2,
                        $record->i3,
                        $record->channel_group,
                        $record->created_at,
                    ]);
                }
            }, 'id');

            fclose($output);
        }, $fileName, [
            'Content-Type'        => 'text/csv; charset=UTF-8',
            'Cache-Control'       => 'no-store, no-cache, must-revalidate',
            'Content-Disposition' => 'attachment; filename="' . $fileName . '"',
        ]);
    }

    /**
     * Build the common filtered database query.
     */
    private function buildFilteredQuery(Request $request): Builder
    {
        $query = DB::table('time_domain_graph_data')
            ->select([
                'id',
                'device_id',
                'v1',
                'v2',
                'v3',
                'i1',
                'i2',
                'i3',
                'channel_group',
                'created_at',
            ]);

        if ($request->filled('device_id')) {
            $query->where('device_id', $request->string('device_id')->trim());
        }

        if ($request->filled('from')) {
            $from = Carbon::parse($request->input('from'))
                ->startOfMinute();

            $query->where('created_at', '>=', $from);
        }

        if ($request->filled('to')) {
            $to = Carbon::parse($request->input('to'))
                ->endOfMinute();

            $query->where('created_at', '<=', $to);
        }

        return $query;
    }

    /**
     * Ensure that the start date is not after the end date.
     */
    private function validateDateRange(?string $from, ?string $to): void
    {
        if (!$from || !$to) {
            return;
        }

        $fromDate = Carbon::parse($from);
        $toDate   = Carbon::parse($to);

        if ($fromDate->greaterThan($toDate)) {
            throw ValidationException::withMessages([
                'from' => 'The From date must be earlier than or equal to the To date.',
            ]);
        }
    }

    /**
     * Generate a meaningful CSV filename.
     */
    private function generateCsvFileName(Request $request): string
    {
        $parts = ['time-domain-data'];

        if ($request->filled('device_id')) {
            $deviceId = preg_replace(
                '/[^A-Za-z0-9_-]/',
                '-',
                $request->input('device_id')
            );

            $parts[] = $deviceId;
        }

        if ($request->filled('from')) {
            $parts[] = 'from-' . Carbon::parse(
                $request->input('from')
            )->format('Ymd-His');
        }

        if ($request->filled('to')) {
            $parts[] = 'to-' . Carbon::parse(
                $request->input('to')
            )->format('Ymd-His');
        }

        $parts[] = now()->format('Ymd-His');

        return implode('-', $parts) . '.csv';
    }
}