<?php

namespace App\Services\Lubricantes;

use App\Models\Lubricante;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

/**
 * Resuelve los listados de lubricantes con Query Builder y columnas explícitas
 * para mantener las respuestas pequeñas y evitar N+1 al calcular imágenes.
 */
class LubricanteCatalogService
{
    /**
     * @var array<int, string>
     */
    private const ADMIN_COLUMNS = [
        'id',
        'titulo',
        'slug',
        'estado',
        'created_at',
    ];

    /**
     * @var array<int, string>
     */
    private const PUBLIC_COLUMNS = [
        'id',
        'titulo',
        'slug',
        'estado',
    ];

    /**
     * @var array<int, string>
     */
    public const SORTABLE_COLUMNS = [
        'id',
        'titulo',
        'estado',
        'created_at',
    ];

    /**
     * @var array<int, string>
     */
    public const FILTERABLE_COLUMNS = [
        'id',
        'titulo',
        'estado',
    ];

    /**
     * Pagina la grilla administrativa con filtros y orden restringidos a una
     * whitelist para impedir consultas arbitrarias sobre otras columnas.
     *
     * @param array<string, mixed> $filters
     */
    public function paginateForAdmin(int $size, int $page, ?string $sort, array $filters): LengthAwarePaginator
    {
        $query = Lubricante::query()
            ->select(self::ADMIN_COLUMNS);

        $this->applyAdminFilters($query, $filters);
        $this->applySorts($query, $sort);

        /** @var LengthAwarePaginator $paginator */
        $paginator = $query->paginate($size, self::ADMIN_COLUMNS, 'page[number]', $page);

        return $paginator->setCollection(
            $this->appendImages(collect($paginator->items()))
        );
    }

    /**
     * Devuelve sólo los campos públicos necesarios para la landing de Petronas
     * y resuelve las URLs de imagen en lote para no disparar un query por item.
     */
    public function listPublicCards(): Collection
    {
        $rows = DB::query()
            ->from(Lubricante::TABLE)
            ->select(self::PUBLIC_COLUMNS)
            ->where('estado', '=', 1)
            ->orderByDesc('id')
            ->get();

        return $this->appendImages($rows);
    }

    /**
     * Adjunta la URL de imagen usando una sola lectura a `image_settings` y una
     * sola lectura a `images`, priorizando la imagen más reciente por recurso.
     *
     * @param Collection<int, object> $rows
     * @return Collection<int, object>
     */
    private function appendImages(Collection $rows): Collection
    {
        if ($rows->isEmpty()) {
            return $rows;
        }

        $imageUrls = $this->resolveImageUrls(
            $rows->pluck('id')->map(fn ($id) => (int) $id)->all()
        );

        return $rows->map(function ($row) use ($imageUrls) {
            $row->imagen = $imageUrls[(int) $row->id] ?? null;

            return $row;
        });
    }

    /**
     * Restringe los filtros a columnas aprobadas y usa comparación exacta para
     * `id` para evitar búsquedas ambiguas y costosas.
     *
     * @param array<string, mixed> $filters
     */
    private function applyAdminFilters(Builder $query, array $filters): void
    {
        foreach ($filters as $field => $value) {
            if ($value === null || $value === '') {
                continue;
            }

            if ($field === 'id' && is_numeric($value)) {
                $query->where('id', '=', (int) $value);
                continue;
            }

            if ($field === 'titulo') {
                $query->where('titulo', 'like', '%' . trim((string) $value) . '%');
            }

            if ($field === 'estado' && in_array($value, [0, 1, '0', '1'], true)) {
                $query->where('estado', '=', (int) $value);
            }
        }
    }

    /**
     * Interpreta el formato de sort del proyecto y descarta columnas no
     * permitidas para reducir superficie de abuso y mantener planes previsibles.
     */
    private function applySorts(Builder $query, ?string $sort): void
    {
        if (!$sort) {
            $query->orderByDesc('id');
            return;
        }

        $sortFields = collect(explode(',', $sort))
            ->map(fn (string $field) => trim($field))
            ->filter();

        if ($sortFields->isEmpty()) {
            $query->orderByDesc('id');
            return;
        }

        foreach ($sortFields as $sortField) {
            $direction = str_starts_with($sortField, '-') ? 'desc' : 'asc';
            $column = ltrim($sortField, '-');

            if (!in_array($column, self::SORTABLE_COLUMNS, true)) {
                continue;
            }

            $query->orderBy($column, $direction);
        }

        $query->orderByDesc('id');
    }

    /**
     * Construye un mapa `lubricante_id => url` sin depender de accessors de
     * Eloquent, lo que evita dos consultas extra por cada fila del catálogo.
     *
     * @param array<int, int> $ids
     * @return array<int, string>
     */
    private function resolveImageUrls(array $ids): array
    {
        if ($ids === []) {
            return [];
        }

        $settings = DB::query()
            ->from('image_settings')
            ->select(['id', 'upload_to'])
            ->where('type', '=', Lubricante::class)
            ->whereNull('key')
            ->orderByDesc('id')
            ->first();

        if (!$settings) {
            return [];
        }

        $images = DB::query()
            ->from('images')
            ->select(['id', 'imageable_id', 'name', 'ext'])
            ->where('imageable_type', '=', Lubricante::class)
            ->whereNull('key')
            ->whereIn('imageable_id', $ids)
            ->orderByDesc('id')
            ->get();

        $urls = [];

        foreach ($images as $image) {
            $resourceId = (int) $image->imageable_id;

            if (isset($urls[$resourceId])) {
                continue;
            }

            $urls[$resourceId] = asset(
                'storage/' . $settings->upload_to . '/' . $image->name . '@' . Lubricante::IMAGE_SIZE_LARGE . '.' . $image->ext
            );
        }

        return $urls;
    }
}
