<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Attivita;
use App\Models\Dispositivo;
use App\Models\Cucina;
use App\Models\Endpoint;
use App\Models\Piatto;
use App\Models\VariantePiatto;
use App\DataTables\DispositivoDataTable;
use App\DataTables\DispositivoDataTableEditor;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;
use Illuminate\Routing\Controllers\Middleware;

class MappaDispositiviController extends Controller
{
    public static $permission_group = "Mappa Dispositivi";
    public static $permissions = [
        'view-mappa_dispositivi' => 'Vedi',
        'create-mappa_dispositivi' => 'Crea',
        'edit-mappa_dispositivi' => 'Modifica',
        'delete-mappa_dispositivi' => 'Elimina',
    ];
    
    public static function middleware(): array
    {
        return [
            new Middleware('permission:view-mappa_dispositivi', only: ['index']),
            new Middleware('permission:create-mappa_dispositivi|edit-mappa_dispositivi|delete-mappa_dispositivi', only: ['store', 'update', 'destroy']),
        ];
    }
    
    public function index(DispositivoDataTable $dataTable)
    {
        if (request()->ajax()) {
            return $dataTable->ajax();
        }

        $attivitaCorrenteId = (int) Session::get('attivita_attuale');
        $attivitaCorrente = null;
        if ($attivitaCorrenteId > 0) {
            $attivitaCorrente = Attivita::query()
                ->where('id', $attivitaCorrenteId)
                ->where('user_id', Auth::id())
                ->where('is_attiva', true)
                ->first(['id', 'nome']);
        }

        $topologia = self::buildTopologyByAttivita($attivitaCorrente?->id, (int) Auth::id());
        $dispositiviByTipo = self::buildDispositiviByTipo();
        $dispositiviFlat = collect($dispositiviByTipo)->flatten(1);

        return view('mappa_dispositivi.index', [
            'topologia' => $topologia,
            'attivitaCorrente' => $attivitaCorrente,
            'dispositiviByTipo' => $dispositiviByTipo,
            'dispositiviAttivitaCorrente' => self::buildDispositiviAttivitaCorrente($attivitaCorrente?->id),
            'tipiDispositivo' => Dispositivo::getTipoDispositivo(),
            'totaleDispositivi' => $dispositiviFlat->count(),
            'totaleAttivi' => $dispositiviFlat->where('is_attivo', true)->count(),
            'puoModificarePv' => Auth::user()->can('edit-punto_vendita'),
            'guidaStampanti' => self::buildGuidaStampanti($attivitaCorrente?->id, (int) Auth::id()),
            'attivitaList' => Attivita::query()
                ->where('is_attiva', true)
                ->orderBy('nome')
                ->where('user_id', Auth::user()->id)
                ->get(['id', 'nome']),
        ]);
    }

    public function store(DispositivoDataTableEditor $editor)
    {
        $request = request();
        $input = $request->all();
        if($request->has('action')){
            switch($input['action']){
                case 'create':
                    if(!Auth::user()->can('create-mappa_dispositivi')) return;
                    break;
                case 'edit':
                    if(!Auth::user()->can('edit-mappa_dispositivi')) return;
                    break;
                case 'remove':
                    if(!Auth::user()->can('delete-mappa_dispositivi')) return;
                    break;
            }
        }
        return $editor->process($request);
    }
    public function show($dispositivo_id)
    {
        $dispositivo = Dispositivo::find($dispositivo_id);
        return view('mappa_dispositivi.show', ['dispositivo' => $dispositivo]);
    }

    /**
     * Elenco dispositivi raggruppati per tipologia (tab mappa).
     */
    public static function buildDispositiviByTipo(): array
    {
        $tipi = Dispositivo::getTipoDispositivo()->keys()->all();
        $grouped = array_fill_keys($tipi, []);
        $grouped['_altro'] = [];

        $attivitaAttiveIds = Attivita::query()
            ->where('is_attiva', true)
            ->pluck('id');

        $devices = Dispositivo::query()
            ->with('attivita:id,nome')
            ->where(function ($q) use ($attivitaAttiveIds) {
                $q->whereIn('attivita_id', $attivitaAttiveIds)
                    ->orWhereNull('attivita_id');
            })
            ->orderBy('nome')
            ->get(['id', 'nome', 'ubicazione', 'tipo', 'is_attivo', 'occupato', 'attivita_id', 'binding_token']);

        foreach ($devices as $device) {
            $tipo = $device->tipo ?? '_altro';
            $bucket = isset($grouped[$tipo]) ? $tipo : '_altro';
            $grouped[$bucket][] = self::serializeDispositivoMappa($device);
        }

        return $grouped;
    }

    /**
     * Dispositivi dell'attività in sessione, raggruppati per tipologia.
     */
    public static function buildDispositiviAttivitaCorrente(?int $attivitaId): array
    {
        $tipi = Dispositivo::getTipoDispositivo()->keys()->all();
        $grouped = array_fill_keys($tipi, []);
        $grouped['_altro'] = [];

        if ($attivitaId === null || $attivitaId <= 0) {
            return [
                'by_tipo' => $grouped,
                'totale' => 0,
                'totale_attivi' => 0,
            ];
        }

        $devices = Dispositivo::query()
            ->with('attivita:id,nome')
            ->where('attivita_id', $attivitaId)
            ->orderBy('nome')
            ->get(['id', 'nome', 'ubicazione', 'tipo', 'is_attivo', 'occupato', 'attivita_id', 'binding_token']);

        foreach ($devices as $device) {
            $tipo = $device->tipo ?? '_altro';
            $bucket = isset($grouped[$tipo]) ? $tipo : '_altro';
            $grouped[$bucket][] = self::serializeDispositivoMappa($device);
        }

        $flat = collect($grouped)->flatten(1);

        return [
            'by_tipo' => $grouped,
            'totale' => $flat->count(),
            'totale_attivi' => $flat->where('is_attivo', true)->count(),
        ];
    }

    private static function serializeDispositivoMappa(Dispositivo $device): array
    {
        return [
            'id' => (int) $device->id,
            'nome' => (string) $device->nome,
            'tipo' => $device->tipo !== null ? (string) $device->tipo : null,
            'ubicazione' => $device->ubicazione !== null ? (string) $device->ubicazione : null,
            'is_attivo' => (bool) $device->is_attivo,
            'occupato' => (bool) $device->occupato,
            'has_binding' => ! empty($device->binding_token),
            'attivita_id' => $device->attivita_id !== null ? (int) $device->attivita_id : null,
            'attivita_nome' => $device->attivita?->nome,
            'is_punto_vendita' => in_array($device->tipo, [
                Dispositivo::CASSA,
                Dispositivo::KIOSK,
                Dispositivo::CAMERIERE,
            ], true),
        ];
    }

    /**
     * Bozza mappa: contenitore = attività → punto vendita (cassa/kiosk/cameriere) + cucine → stampante.
     */
    public static function buildTopologyByAttivita(?int $attivitaId = null, ?int $userId = null): array
    {
        $pvTipi = [Dispositivo::CASSA, Dispositivo::KIOSK, Dispositivo::CAMERIERE];

        $attivitaQuery = Attivita::query()
            ->where('is_attiva', true)
            ->orderBy('nome');
        if ($userId !== null && $userId > 0) {
            $attivitaQuery->where('user_id', $userId);
        }
        if ($attivitaId !== null && $attivitaId > 0) {
            $attivitaQuery->where('id', $attivitaId);
        }
        $attivitaRows = $attivitaQuery->get(['id', 'nome']);

        $attivitaIds = $attivitaRows->pluck('id')->all();
        if (empty($attivitaIds)) {
            return [];
        }

        $devices = Dispositivo::query()
            ->whereIn('attivita_id', $attivitaIds)
            ->orderBy('nome')
            ->get(['id', 'nome', 'ubicazione', 'tipo', 'is_attivo', 'attivita_id', 'url_stampante', 'endpoint_id']);

        $devicesGrouped = $devices->groupBy(fn (Dispositivo $d) => (int) $d->attivita_id);

        $cucineByAttivita = Cucina::query()
            ->whereIn('attivita_id', $attivitaIds)
            ->orderBy('nome')
            ->get(['id', 'nome', 'stampante_id', 'attivita_id'])
            ->groupBy(fn (Cucina $c) => (int) $c->attivita_id);

        $cucinaIds = $cucineByAttivita
            ->flatten(1)
            ->pluck('id')
            ->map(fn ($id) => (int) $id)
            ->filter()
            ->unique()
            ->values()
            ->all();

        $piattiCountByCucina = [];
        $variantiCountByCucina = [];
        if (!empty($cucinaIds)) {
            $piattiCountByCucina = Piatto::query()
                ->selectRaw('cucina_id, COUNT(*) as aggregate')
                ->whereIn('cucina_id', $cucinaIds)
                ->groupBy('cucina_id')
                ->pluck('aggregate', 'cucina_id')
                ->map(fn ($count) => (int) $count)
                ->all();

            $variantiCountByCucina = VariantePiatto::query()
                ->selectRaw('cucina_id, COUNT(*) as aggregate')
                ->whereIn('cucina_id', $cucinaIds)
                ->groupBy('cucina_id')
                ->pluck('aggregate', 'cucina_id')
                ->map(fn ($count) => (int) $count)
                ->all();
        }

        $stampanteIds = Cucina::query()
            ->whereIn('attivita_id', $attivitaIds)
            ->whereNotNull('stampante_id')
            ->pluck('stampante_id')
            ->unique()
            ->filter()
            ->values();

        $stampanti = Dispositivo::query()
            ->whereIn('id', $stampanteIds)
            ->with('endpoint:id,label')
            ->get(['id', 'nome', 'ubicazione', 'tipo', 'is_attivo', 'endpoint_id'])
            ->keyBy('id');

        $serialize = static function (Dispositivo $d): array {
            $attrs = $d->getAttributes();
            $ip = null;
            if (array_key_exists('ip', $attrs) && $attrs['ip'] !== null) {
                $ip = (string) $attrs['ip'];
            }

            return [
                'id' => (int) $d->id,
                'nome' => (string) $d->nome,
                'ubicazione' => $d->ubicazione !== null ? (string) $d->ubicazione : null,
                'tipo' => $d->tipo !== null ? (string) $d->tipo : null,
                'is_attivo' => (bool) $d->is_attivo,
                'endpoint_id' => $d->endpoint_id ? (int) $d->endpoint_id : null,
                'endpoint_label' => $d->relationLoaded('endpoint') ? $d->endpoint?->label : null,
                'ip' => $ip,
            ];
        };

        $out = [];

        foreach ($attivitaRows as $att) {
            $aid = (int) $att->id;
            $devices = $devicesGrouped->get($aid, collect());
            $pvRows = $devices->whereIn('tipo', $pvTipi)->values();
            $pvIds = $pvRows->pluck('id')->all();
            $pvWithDeps = Dispositivo::query()
                ->whereIn('id', $pvIds)
                ->with([
                    'endpoint:id,label',
                    'hasCucine:id,nome,stampante_id',
                ])
                ->get()
                ->keyBy('id');

            $puntiVendita = $pvRows->map(function (Dispositivo $pv) use ($serialize, $pvWithDeps, $stampanti) {
                $pvData = $serialize($pv);
                $fullPv = $pvWithDeps->get($pv->id);
                $pvData['endpoint_id'] = $fullPv?->endpoint_id ? (int) $fullPv->endpoint_id : null;
                $pvData['endpoint_label'] = $fullPv?->endpoint?->label;

                $stampanteCassa = null;
                $stampanteRef = $fullPv?->url_stampante;
                if (is_numeric($stampanteRef)) {
                    $stampanteModel = Dispositivo::query()->find((int) $stampanteRef);
                    if ($stampanteModel) {
                        $stampanteCassa = $serialize($stampanteModel);
                    }
                }
                $pvData['stampante_cassa'] = $stampanteCassa;

                $pvData['cucine_collegate'] = $fullPv
                    ? $fullPv->hasCucine->map(function ($c) use ($serialize, $stampanti) {
                        $stampante = null;
                        if ($c->stampante_id && $stampanti->has((int) $c->stampante_id)) {
                            $stampante = $serialize($stampanti->get((int) $c->stampante_id));
                        } elseif ($c->stampante_id) {
                            $fallback = Dispositivo::query()
                                ->with('endpoint:id,label')
                                ->find((int) $c->stampante_id, ['id', 'nome', 'ubicazione', 'tipo', 'is_attivo', 'endpoint_id']);
                            if ($fallback) {
                                $stampante = $serialize($fallback);
                            }
                        }

                        return [
                            'id' => (int) $c->id,
                            'nome' => (string) $c->nome,
                            'stampante_id' => $c->stampante_id ? (int) $c->stampante_id : null,
                            'stampante' => $stampante,
                        ];
                    })->values()->all()
                    : [];

                return $pvData;
            })->all();
            $altri = $devices->whereNotIn('tipo', $pvTipi)->values()->map($serialize)->all();

            $cucinePayload = [];
            foreach ($cucineByAttivita->get($aid, collect()) as $cucina) {
                $st = null;
                if ($cucina->stampante_id && $stampanti->has((int) $cucina->stampante_id)) {
                    $st = $serialize($stampanti->get((int) $cucina->stampante_id));
                } elseif ($cucina->stampante_id) {
                    $fallback = Dispositivo::query()
                        ->with('endpoint:id,label')
                        ->find((int) $cucina->stampante_id, ['id', 'nome', 'ubicazione', 'tipo', 'is_attivo', 'endpoint_id']);
                    if ($fallback) {
                        $st = $serialize($fallback);
                    }
                }
                $cucinaId = (int) $cucina->id;
                $cucinePayload[] = [
                    'id' => $cucinaId,
                    'nome' => (string) $cucina->nome,
                    'stampante' => $st,
                    'piatti_count' => $piattiCountByCucina[$cucinaId] ?? 0,
                    'varianti_count' => $variantiCountByCucina[$cucinaId] ?? 0,
                ];
            }

            $out[] = [
                'id' => $aid,
                'nome' => (string) $att->nome,
                'punti_vendita' => $puntiVendita,
                'cucine' => $cucinePayload,
                'altri' => $altri,
            ];
        }

        return $out;
    }

    /**
     * Dati per la guida setup stampanti (tab mappa dispositivi).
     */
    public static function buildGuidaStampanti(?int $attivitaId, ?int $userId = null): array
    {
        if ($attivitaId === null || $attivitaId <= 0) {
            return ['ready' => false];
        }

        $topologia = self::buildTopologyByAttivita($attivitaId, $userId);
        $att = $topologia[0] ?? null;

        $endpoints = Endpoint::query()
            ->where('attivita_id', $attivitaId)
            ->when($userId !== null && $userId > 0, fn ($q) => $q->where('user_id', $userId))
            ->withCount('stampanti')
            ->orderBy('label')
            ->get(['id', 'label', 'status', 'last_heartbeat', 'deeplink_token', 'product_key']);

        $endpointRows = $endpoints->map(function (Endpoint $ep) {
            $meta = Endpoint::getStati()->get($ep->status);
            $deeplink = is_array($ep->deeplink_token) ? $ep->deeplink_token : [];
            $deeplinkStatus = $deeplink['status'] ?? null;
            $deeplinkLabel = match ($deeplinkStatus) {
                'paired' => 'Computer collegato',
                'pending' => 'In attesa su FestAgent',
                default => null,
            };
            $deeplinkClass = match ($deeplinkStatus) {
                'paired' => 'bg-label-success',
                'pending' => 'bg-label-warning',
                default => 'bg-label-secondary',
            };

            return [
                'id' => (int) $ep->id,
                'label' => $ep->label ?: ('Endpoint #' . $ep->id),
                'status' => (string) $ep->status,
                'status_label' => $meta['label'] ?? $ep->status,
                'status_class' => $meta['class'] ?? 'bg-label-secondary',
                'stampanti_count' => (int) $ep->stampanti_count,
                'last_heartbeat' => $ep->last_heartbeat?->diffForHumans(),
                'deeplink_status' => $deeplinkStatus,
                'deeplink_label' => $deeplinkLabel,
                'deeplink_class' => $deeplinkClass,
                'has_product_key' => ! empty($ep->product_key),
            ];
        })->values()->all();

        $puntiVendita = collect($att['punti_vendita'] ?? []);
        $cucine = collect($att['cucine'] ?? []);

        $pvSenzaEndpoint = $puntiVendita
            ->filter(fn (array $pv) => empty($pv['endpoint_id']))
            ->map(fn (array $pv) => ['id' => (int) $pv['id'], 'nome' => (string) $pv['nome']])
            ->values()
            ->all();

        $pvSenzaStampante = $puntiVendita
            ->filter(fn (array $pv) => empty($pv['stampante_cassa']))
            ->map(fn (array $pv) => ['id' => (int) $pv['id'], 'nome' => (string) $pv['nome']])
            ->values()
            ->all();

        $cucineSenzaStampante = $cucine
            ->filter(fn (array $c) => empty($c['stampante']))
            ->map(fn (array $c) => ['id' => (int) $c['id'], 'nome' => (string) $c['nome']])
            ->values()
            ->all();

        $totStampanti = Dispositivo::query()
            ->where('tipo', Dispositivo::STAMPANTE)
            ->where('attivita_id', $attivitaId)
            ->count();

        $hasEndpoint = $endpoints->isNotEmpty();
        $hasEndpointAttivo = $endpoints->contains(fn (Endpoint $e) => $e->status === Endpoint::ATTIVO);
        $hasStampanti = $totStampanti > 0 || $endpoints->sum('stampanti_count') > 0;
        $cucineOk = $cucine->isEmpty() || empty($cucineSenzaStampante);
        $casseOk = $puntiVendita->isEmpty() || (empty($pvSenzaStampante) && empty($pvSenzaEndpoint));

        $problemi = count($pvSenzaEndpoint) + count($pvSenzaStampante) + count($cucineSenzaStampante);
        if (! $hasEndpoint) {
            $problemi += 1;
        } elseif (! $hasEndpointAttivo) {
            $problemi += 1;
        }
        if (! $hasStampanti) {
            $problemi += 1;
        }

        return [
            'ready' => true,
            'endpoints' => $endpointRows,
            'tot_stampanti' => $totStampanti,
            'tot_endpoint' => count($endpointRows),
            'pv_senza_endpoint' => $pvSenzaEndpoint,
            'pv_senza_stampante' => $pvSenzaStampante,
            'cucine_senza_stampante' => $cucineSenzaStampante,
            'tot_cucine' => $cucine->count(),
            'tot_pv' => $puntiVendita->count(),
            'checks' => [
                'endpoint' => $hasEndpoint,
                'endpoint_attivo' => $hasEndpointAttivo,
                'stampanti' => $hasStampanti,
                'cucine' => $cucineOk,
                'casse' => $casseOk,
            ],
            'completato' => $hasEndpointAttivo && $hasStampanti && $cucineOk && $casseOk,
            'problemi' => $problemi,
        ];
    }
}
