<?php

namespace App\Services\Notifications;

use App\Models\NotificacionVista;
use App\Models\Producto;
use App\Models\ProductoVariante;
use App\Models\SoporteTicket;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

class NotificationCenterService
{
    public function resolveScope(?string $scope = 'auto'): string
    {
        $normalized = strtolower(trim((string) $scope));
        if (in_array($normalized, ['tenant', 'superadmin'], true)) {
            return $normalized;
        }

        return has_permission('superadmin.acceso') ? 'superadmin' : 'tenant';
    }

    public function payload(string $scope = 'auto', int $limit = 10): array
    {
        $resolvedScope = $this->resolveScope($scope);
        $limit = max(1, min(50, $limit));

        if ($resolvedScope === 'superadmin') {
            return $this->superadminPayload($limit);
        }

        return $this->tenantPayload($limit);
    }

    public function markAllAsRead(string $scope = 'auto'): int
    {
        $resolvedScope = $this->resolveScope($scope);
        $userId = (int) (auth_user()?->id ?? 0);
        if ($userId <= 0) {
            return 0;
        }

        $items = $resolvedScope === 'superadmin'
            ? $this->superadminTicketNotifications(null, true)
            : array_merge(
                $this->stockMinimumNotifications(null, true),
                $this->tenantSupportTicketNotifications(null, true),
            );

        if ($items === []) {
            return 0;
        }

        $keys = [];
        foreach ($items as $item) {
            $readKey = trim((string) ($item['read_key'] ?? ''));
            if ($readKey !== '') {
                $keys[] = $readKey;
            }
        }

        if ($keys === []) {
            return 0;
        }

        return $this->markReadKeys($keys, $resolvedScope, $userId);
    }

    /**
     * @return array{count:int,count_stock:int,count_tickets:int,items:array<int,array<string,mixed>>}
     */
    private function tenantPayload(int $limit): array
    {
        if ((int) (auth_user()?->id ?? 0) <= 0 || has_permission('superadmin.acceso')) {
            return [
                'count' => 0,
                'count_stock' => 0,
                'count_tickets' => 0,
                'items' => [],
            ];
        }

        $ticketItems = $this->tenantSupportTicketNotifications(null, false);
        $stockItems = $this->stockMinimumNotifications(null, false);
        $ticketCount = count($ticketItems);
        $stockCount = count($stockItems);

        $items = [];
        $maxIndex = max(count($ticketItems), count($stockItems));
        for ($index = 0; $index < $maxIndex; $index++) {
            if (isset($ticketItems[$index])) {
                $items[] = $ticketItems[$index];
                if (count($items) >= $limit) {
                    break;
                }
            }

            if (isset($stockItems[$index])) {
                $items[] = $stockItems[$index];
                if (count($items) >= $limit) {
                    break;
                }
            }
        }

        return [
            'count' => max(0, $stockCount + $ticketCount),
            'count_stock' => max(0, $stockCount),
            'count_tickets' => max(0, $ticketCount),
            'items' => $this->stripReadKeys($items),
        ];
    }

    /**
     * @return array{count:int,count_stock:int,count_tickets:int,items:array<int,array<string,mixed>>}
     */
    private function superadminPayload(int $limit): array
    {
        if ((int) (auth_user()?->id ?? 0) <= 0 || ! has_permission('superadmin.acceso')) {
            return [
                'count' => 0,
                'count_stock' => 0,
                'count_tickets' => 0,
                'items' => [],
            ];
        }

        $ticketItems = $this->superadminTicketNotifications(null, false);
        $ticketCount = count($ticketItems);

        return [
            'count' => max(0, $ticketCount),
            'count_stock' => 0,
            'count_tickets' => max(0, $ticketCount),
            'items' => $this->stripReadKeys(array_slice($ticketItems, 0, $limit)),
        ];
    }

    /**
     * @return array<int,array<string,mixed>>
     */
    private function stockMinimumNotifications(?int $limit = 10, bool $includeRead = false): array
    {
        if (has_permission('superadmin.acceso')) {
            return [];
        }

        $companyId = (int) (tenant_empresa_id() ?? 0);
        if ($companyId <= 0) {
            return [];
        }

        $limitValue = $limit === null ? null : max(1, min(500, $limit));
        $items = [];

        if (Schema::hasTable('producto_variantes')) {
            $simpleProducts = Producto::query()
                ->where('empresa_id', $companyId)
                ->where('activo', 1)
                ->where('stock_minimo', '>', 0)
                ->whereColumn('stock_actual', '<=', 'stock_minimo')
                ->whereDoesntHave('variantes', static function ($query) use ($companyId): void {
                    $query->where('empresa_id', $companyId)->where('activo', 1);
                })
                ->get([
                    'id',
                    'nombre',
                    'sku',
                    'stock_actual',
                    'stock_minimo',
                ]);

            foreach ($simpleProducts as $product) {
                $stockActual = (float) ($product->stock_actual ?? 0);
                $stockMinimo = (float) ($product->stock_minimo ?? 0);
                $alertId = 'producto-'.(int) $product->id;

                $items[] = [
                    'id' => $alertId,
                    'notification_type' => 'stock',
                    'type' => 'producto',
                    'title' => (string) ($product->nombre ?? 'Producto'),
                    'reference' => trim((string) ($product->sku ?? '')),
                    'stock_actual' => $stockActual,
                    'stock_minimo' => $stockMinimo,
                    'missing' => max(0, $stockMinimo - $stockActual),
                    'severity' => $stockActual <= 0 ? 'critical' : 'warning',
                    'message' => 'Stock actual: '.$this->formatStock($stockActual).' · mínimo: '.$this->formatStock($stockMinimo),
                    'read_key' => 'stock|'.$alertId.'|'.$this->normalizeNumericKey($stockActual).'|'.$this->normalizeNumericKey($stockMinimo),
                ];
            }

            $variantRows = ProductoVariante::query()
                ->where('empresa_id', $companyId)
                ->where('activo', 1)
                ->where('stock_minimo', '>', 0)
                ->whereColumn('cantidad', '<=', 'stock_minimo')
                ->whereHas('producto', static function ($query) use ($companyId): void {
                    $query->where('empresa_id', $companyId)->where('activo', 1);
                })
                ->with(['producto:id,nombre,sku'])
                ->get([
                    'id',
                    'producto_id',
                    'nombre',
                    'sku',
                    'cantidad',
                    'stock_minimo',
                ]);

            foreach ($variantRows as $variant) {
                $stockActual = (float) ($variant->cantidad ?? 0);
                $stockMinimo = (float) ($variant->stock_minimo ?? 0);
                $alertId = 'variante-'.(int) $variant->id;
                $productName = (string) ($variant->producto?->nombre ?? 'Producto');
                $variantName = trim((string) ($variant->nombre ?? ''));

                $items[] = [
                    'id' => $alertId,
                    'notification_type' => 'stock',
                    'type' => 'variante',
                    'title' => $variantName !== '' ? $productName.' · '.$variantName : $productName,
                    'reference' => trim((string) ($variant->sku ?: ($variant->producto?->sku ?? ''))),
                    'stock_actual' => $stockActual,
                    'stock_minimo' => $stockMinimo,
                    'missing' => max(0, $stockMinimo - $stockActual),
                    'severity' => $stockActual <= 0 ? 'critical' : 'warning',
                    'message' => 'Stock actual: '.$this->formatStock($stockActual).' · mínimo: '.$this->formatStock($stockMinimo),
                    'read_key' => 'stock|'.$alertId.'|'.$this->normalizeNumericKey($stockActual).'|'.$this->normalizeNumericKey($stockMinimo),
                ];
            }
        } else {
            $simpleProducts = Producto::query()
                ->where('empresa_id', $companyId)
                ->where('activo', 1)
                ->where('stock_minimo', '>', 0)
                ->whereColumn('stock_actual', '<=', 'stock_minimo')
                ->get([
                    'id',
                    'nombre',
                    'sku',
                    'stock_actual',
                    'stock_minimo',
                ]);

            foreach ($simpleProducts as $product) {
                $stockActual = (float) ($product->stock_actual ?? 0);
                $stockMinimo = (float) ($product->stock_minimo ?? 0);
                $alertId = 'producto-'.(int) $product->id;

                $items[] = [
                    'id' => $alertId,
                    'notification_type' => 'stock',
                    'type' => 'producto',
                    'title' => (string) ($product->nombre ?? 'Producto'),
                    'reference' => trim((string) ($product->sku ?? '')),
                    'stock_actual' => $stockActual,
                    'stock_minimo' => $stockMinimo,
                    'missing' => max(0, $stockMinimo - $stockActual),
                    'severity' => $stockActual <= 0 ? 'critical' : 'warning',
                    'message' => 'Stock actual: '.$this->formatStock($stockActual).' · mínimo: '.$this->formatStock($stockMinimo),
                    'read_key' => 'stock|'.$alertId.'|'.$this->normalizeNumericKey($stockActual).'|'.$this->normalizeNumericKey($stockMinimo),
                ];
            }
        }

        usort($items, static function (array $a, array $b): int {
            $missingA = (float) ($a['missing'] ?? 0);
            $missingB = (float) ($b['missing'] ?? 0);
            if ($missingA !== $missingB) {
                return $missingB <=> $missingA;
            }

            $stockA = (float) ($a['stock_actual'] ?? 0);
            $stockB = (float) ($b['stock_actual'] ?? 0);
            if ($stockA !== $stockB) {
                return $stockA <=> $stockB;
            }

            return strcmp(
                mb_strtolower((string) ($a['title'] ?? '')),
                mb_strtolower((string) ($b['title'] ?? '')),
            );
        });

        $items = $this->filterUnreadItems($items, 'tenant', $includeRead);
        if ($limitValue !== null) {
            $items = array_slice($items, 0, $limitValue);
        }

        return $items;
    }

    /**
     * @return array<int,array<string,mixed>>
     */
    private function tenantSupportTicketNotifications(?int $limit = 10, bool $includeRead = false): array
    {
        if (has_permission('superadmin.acceso')) {
            return [];
        }

        $companyId = (int) (tenant_empresa_id() ?? 0);
        if ($companyId <= 0) {
            return [];
        }

        $limitValue = $limit === null ? null : max(1, min(500, $limit));

        $rows = SoporteTicket::query()
            ->where('empresa_id', $companyId)
            ->whereIn('estado', ['abierto', 'en_proceso', 'pendiente_cliente'])
            ->orderByRaw('CASE estado
                WHEN "pendiente_cliente" THEN 0
                WHEN "abierto" THEN 1
                WHEN "en_proceso" THEN 2
                ELSE 3
            END ASC')
            ->orderByDesc('updated_at')
            ->orderByDesc('id')
            ->get([
                'id',
                'asunto',
                'estado',
                'prioridad',
                'created_at',
                'updated_at',
            ]);

        $items = [];
        foreach ($rows as $ticket) {
            $ticketId = (int) $ticket->id;
            if ($ticketId <= 0) {
                continue;
            }

            $subject = trim((string) ($ticket->asunto ?? ''));
            if ($subject === '') {
                $subject = 'Ticket de soporte';
            }

            $status = strtolower(trim((string) ($ticket->estado ?? 'abierto')));
            $priority = strtolower(trim((string) ($ticket->prioridad ?? 'media')));
            $updatedAt = optional($ticket->updated_at)?->format('Y-m-d H:i:s') ?? '';
            $createdAt = optional($ticket->created_at)?->format('Y-m-d H:i:s') ?? '';
            $dateRef = $updatedAt !== '' ? $updatedAt : $createdAt;
            $dateText = '';
            $timestamp = strtotime($dateRef);
            if ($timestamp !== false) {
                $dateText = date('d/m H:i', $timestamp);
            }

            $message = $this->ticketStatusMessage($status).' Prioridad: '.$this->ticketPriorityLabel($priority);
            if ($dateText !== '') {
                $message .= ' · Act: '.$dateText;
            }

            $items[] = [
                'id' => 'ticket-'.$ticketId,
                'notification_type' => 'ticket',
                'type' => 'ticket',
                'ticket_id' => $ticketId,
                'ticket_status' => $status,
                'ticket_priority' => $priority,
                'title' => 'Ticket #'.$ticketId.' · '.$subject,
                'severity' => ($status === 'pendiente_cliente' || $priority === 'critica') ? 'critical' : 'warning',
                'message' => $message,
                'updated_at' => $updatedAt,
                'created_at' => $createdAt,
                'read_key' => 'ticket|tenant|'.$ticketId.'|'.($updatedAt !== '' ? $updatedAt : $createdAt),
            ];
        }

        $items = $this->filterUnreadItems($items, 'tenant', $includeRead);
        if ($limitValue !== null) {
            $items = array_slice($items, 0, $limitValue);
        }

        return $items;
    }

    /**
     * @return array<int,array<string,mixed>>
     */
    private function superadminTicketNotifications(?int $limit = 10, bool $includeRead = false): array
    {
        if (! has_permission('superadmin.acceso')) {
            return [];
        }

        $limitValue = $limit === null ? null : max(1, min(500, $limit));

        $rows = DB::table('soporte_tickets as t')
            ->join('empresas as e', 'e.id', '=', 't.empresa_id')
            ->whereIn('t.estado', ['abierto', 'en_proceso', 'pendiente_cliente'])
            ->orderByRaw('CASE t.estado
                WHEN "abierto" THEN 0
                WHEN "pendiente_cliente" THEN 1
                WHEN "en_proceso" THEN 2
                ELSE 3
            END ASC')
            ->orderByDesc('t.updated_at')
            ->orderByDesc('t.id')
            ->get([
                't.id',
                't.empresa_id',
                't.asunto',
                't.prioridad',
                't.estado',
                't.created_at',
                't.updated_at',
                'e.nombre as empresa_nombre',
            ]);

        $items = [];
        foreach ($rows as $row) {
            $ticketId = (int) ($row->id ?? 0);
            if ($ticketId <= 0) {
                continue;
            }

            $companyName = trim((string) ($row->empresa_nombre ?? 'Empresa'));
            $subject = trim((string) ($row->asunto ?? 'Ticket de soporte'));
            $status = strtolower(trim((string) ($row->estado ?? 'abierto')));
            $priority = strtolower(trim((string) ($row->prioridad ?? 'media')));
            $updatedAt = trim((string) ($row->updated_at ?? ''));
            $createdAt = trim((string) ($row->created_at ?? ''));
            $timestamp = strtotime($updatedAt !== '' ? $updatedAt : $createdAt);
            $dateText = $timestamp !== false ? date('d/m H:i', $timestamp) : '';

            $message = $this->ticketStatusLabel($status).' · '.$companyName.' · Prioridad: '.$this->ticketPriorityLabel($priority);
            if ($dateText !== '') {
                $message .= ' · Act: '.$dateText;
            }

            $items[] = [
                'id' => 'superadmin-ticket-'.$ticketId,
                'notification_type' => 'ticket',
                'type' => 'ticket',
                'ticket_id' => $ticketId,
                'empresa_id' => (int) ($row->empresa_id ?? 0),
                'ticket_status' => $status,
                'ticket_priority' => $priority,
                'title' => 'Ticket #'.$ticketId.' · '.$subject,
                'message' => $message,
                'severity' => ($status === 'abierto' || $priority === 'critica') ? 'critical' : 'warning',
                'updated_at' => $updatedAt,
                'created_at' => $createdAt,
                'read_key' => 'ticket|superadmin|'.$ticketId.'|'.($updatedAt !== '' ? $updatedAt : $createdAt),
            ];
        }

        $items = $this->filterUnreadItems($items, 'superadmin', $includeRead);
        if ($limitValue !== null) {
            $items = array_slice($items, 0, $limitValue);
        }

        return $items;
    }

    /**
     * @param  array<int,array<string,mixed>>  $items
     * @return array<int,array<string,mixed>>
     */
    private function filterUnreadItems(array $items, string $scope, bool $includeRead): array
    {
        if ($includeRead || $items === []) {
            return $items;
        }

        $userId = (int) (auth_user()?->id ?? 0);
        if ($userId <= 0) {
            return [];
        }

        $keys = [];
        foreach ($items as $item) {
            $readKey = trim((string) ($item['read_key'] ?? ''));
            if ($readKey !== '') {
                $keys[] = $readKey;
            }
        }

        if ($keys === []) {
            return $items;
        }

        $readMap = $this->readMap($keys, $scope, $userId);
        if ($readMap === []) {
            return $items;
        }

        $output = [];
        foreach ($items as $item) {
            $readKey = trim((string) ($item['read_key'] ?? ''));
            if ($readKey !== '' && isset($readMap[$readKey])) {
                continue;
            }

            $output[] = $item;
        }

        return $output;
    }

    /**
     * @param  array<int,string>  $keys
     * @return array<string,bool>
     */
    private function readMap(array $keys, string $scope, int $userId): array
    {
        $normalizedKeys = array_values(array_unique(array_filter(
            array_map(static fn ($value): string => trim((string) $value), $keys),
            static fn (string $value): bool => $value !== '',
        )));
        if ($normalizedKeys === []) {
            return [];
        }

        $companyId = $scope === 'superadmin' ? 0 : max(0, (int) (tenant_empresa_id() ?? 0));

        $rows = NotificacionVista::query()
            ->where('usuario_id', $userId)
            ->where('empresa_id', $companyId)
            ->whereIn('notification_key', $normalizedKeys)
            ->pluck('notification_key')
            ->all();

        $map = [];
        foreach ($rows as $row) {
            $key = trim((string) $row);
            if ($key !== '') {
                $map[$key] = true;
            }
        }

        return $map;
    }

    /**
     * @param  array<int,string>  $keys
     */
    private function markReadKeys(array $keys, string $scope, int $userId): int
    {
        $normalizedKeys = array_values(array_unique(array_filter(
            array_map(static fn ($value): string => trim((string) $value), $keys),
            static fn (string $value): bool => $value !== '',
        )));
        if ($normalizedKeys === []) {
            return 0;
        }

        $companyId = $scope === 'superadmin' ? 0 : max(0, (int) (tenant_empresa_id() ?? 0));
        $now = Carbon::now();
        $rows = [];
        foreach ($normalizedKeys as $key) {
            $rows[] = [
                'usuario_id' => $userId,
                'empresa_id' => $companyId,
                'notification_key' => $key,
                'leido_at' => $now,
                'created_at' => $now,
                'updated_at' => $now,
            ];
        }

        NotificacionVista::query()->upsert(
            $rows,
            ['usuario_id', 'empresa_id', 'notification_key'],
            ['leido_at', 'updated_at'],
        );

        return count($normalizedKeys);
    }

    /**
     * @param  array<int,array<string,mixed>>  $items
     * @return array<int,array<string,mixed>>
     */
    private function stripReadKeys(array $items): array
    {
        $output = [];
        foreach ($items as $item) {
            unset($item['read_key']);
            $output[] = $item;
        }

        return $output;
    }

    private function formatStock(float $value): string
    {
        if (abs($value - round($value)) < 0.0001) {
            return (string) (int) round($value);
        }

        return rtrim(rtrim(number_format($value, 2, '.', ''), '0'), '.');
    }

    private function normalizeNumericKey(mixed $value): string
    {
        $number = (float) $value;
        $formatted = number_format($number, 4, '.', '');

        return rtrim(rtrim($formatted, '0'), '.');
    }

    private function ticketPriorityLabel(string $value): string
    {
        return match (strtolower(trim($value))) {
            'critica' => 'Crítica',
            'alta' => 'Alta',
            'media' => 'Media',
            'baja' => 'Baja',
            default => 'Media',
        };
    }

    private function ticketStatusMessage(string $value): string
    {
        return match (strtolower(trim($value))) {
            'pendiente_cliente' => 'Soporte espera tu respuesta.',
            'en_proceso' => 'Ticket en proceso por soporte.',
            'abierto' => 'Ticket recibido por soporte.',
            default => 'Ticket activo.',
        };
    }

    private function ticketStatusLabel(string $value): string
    {
        return match (strtolower(trim($value))) {
            'abierto' => 'Nuevo ticket',
            'pendiente_cliente' => 'Pendiente cliente',
            'en_proceso' => 'En proceso',
            default => 'Activo',
        };
    }
}

