<?php

namespace App\Rules;

use App\Models\Environments\Environment;
use App\Services\Api\HostNormalizer;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;

/**
 * Valida que el dominio del entorno no esté duplicado entre entornos no-local.
 *
 * Para `env=local` se permiten duplicados (ver fixes/PRODSECU-192). Si se omite `$env`
 * se aplica la regla estricta, que es el comportamiento seguro por defecto.
 *
 * **Dos cosas que estaban mal y cortaban de formas distintas:**
 *
 * 1. **Los entornos borrados no se veían.** La consulta era `Environment::where(...)`, y
 *    con SoftDeletes eso **excluye los borrados**; pero el índice único de la base los
 *    cuenta, porque no sabe nada de `deleted_at`. Así que poner el dominio de un entorno
 *    borrado pasaba la validación y **reventaba con un error 500** de la base en vez de
 *    decir «ya existe».
 *
 * 2. **Comparaba distinto que la API.** Solo quitaba la barra final, sin normalizar la
 *    caja ni el esquema. Y el índice único de MySQL **es insensible a mayúsculas**, así
 *    que `Sitio.com` pasaba la validación y lo rechazaba la base; en sqlite —los tests—
 *    es sensible, así que ahí colaba el duplicado. Dos comportamientos para la misma
 *    regla según el motor.
 *
 * Ahora se compara con el mismo normalizador que usa la API para encontrar el entorno
 * (`HostNormalizer`), que es el que decide de verdad si dos dominios son el mismo.
 */
class UniqueEnvironmentDomain implements ValidationRule
{
    public function __construct(
        private ?string $env = null,
        private ?int $ignoreId = null,
    ) {}

    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if ($this->env === 'local') {
            return;
        }

        $normalizado = HostNormalizer::normalize((string) $value);

        // `withTrashed()`: el índice único de la base incluye los borrados.
        $existentes = Environment::withTrashed()
            ->where('env', '!=', 'local')
            ->when($this->ignoreId !== null, fn ($q) => $q->where('id', '!=', $this->ignoreId))
            ->pluck('domain', 'id');

        foreach ($existentes as $id => $dominio) {
            if ($dominio === null) {
                continue;
            }

            if (HostNormalizer::normalize($dominio) !== $normalizado) {
                continue;
            }

            // El mensaje distingue el caso del borrado: «ya existe» sobre algo que no se
            // ve en ningún listado es de las cosas que hacen perder media hora.
            $borrado = Environment::onlyTrashed()->whereKey($id)->exists();

            $fail($borrado
                ? 'Ese dominio lo tiene un entorno BORRADO (id ' . $id . '). El índice de la '
                    . 'base cuenta los borrados, así que hay que restaurarlo o borrarlo del todo.'
                : 'Ya existe un entorno no-local con ese dominio.');

            return;
        }
    }
}
