<?php

namespace App\Livewire\Traits;

use App\Models\Environments\Environment;
use App\Models\Monitoring\Log;
use App\Models\Products\LicenseToken;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;

/**
 * Reglas de vinculación entre tokens de licencia y entornos.
 *
 * Un entorno pertenece a un solo token (`environments.license_token_id` es un
 * hasMany), de modo que vincularlo a otro token se lo QUITA al anterior y deja
 * ese sitio sin licencia. Por eso la regla de negocio es estricta: token y
 * entorno tienen que ser del mismo cliente.
 *
 * Antes ninguna de las dos pantallas que hacen esta operación comprobaba la
 * pertenencia, y la lista de candidatos mostraba los entornos de todos los
 * clientes. Ver known-issues MGR-004.
 */
trait AssignsTokenEnvironments
{
    /**
     * Entornos que se pueden ofrecer como candidatos para un token.
     *
     * Son los del cliente dueño del token, más los que ya estén vinculados a
     * este token aunque incumplan la regla: si hubiera datos antiguos cruzados,
     * ocultarlos los volvería invisibles e imposibles de desvincular.
     */
    protected function assignableEnvironmentsQuery(LicenseToken $token): Builder
    {
        return Environment::query()->where(function (Builder $query) use ($token) {
            $query->where('client_id', $token->client_id)
                ->orWhere('license_token_id', $token->id);
        });
    }

    /**
     * IDs de entornos que este token puede tener vinculados, según la regla.
     *
     * @return array<int>
     */
    protected function assignableEnvironmentIds(LicenseToken $token): array
    {
        return $this->assignableEnvironmentsQuery($token)->pluck('id')->all();
    }

    /**
     * Comprueba en el servidor que los IDs recibidos son asignables a este token.
     *
     * No se puede confiar en la lista que llega del navegador: en Livewire las
     * propiedades públicas son escribibles desde el payload de la petición.
     *
     * @param  array<int|string>  $environmentIds
     * @return array<int>|null  IDs válidos, o null si alguno no lo era
     */
    protected function validateAssignableEnvironments(LicenseToken $token, array $environmentIds): ?array
    {
        $requested = array_values(array_unique(array_map('intval', $environmentIds)));
        $rejected = array_diff($requested, $this->assignableEnvironmentIds($token));

        if (!empty($rejected)) {
            Log::db(
                'warning',
                '16001',
                'Intento de vincular al token ' . $token->id . ' entornos que no son de su cliente: '
                    . implode(', ', $rejected),
                'LicenseToken',
                (string) $token->id
            );

            return null;
        }

        return $requested;
    }

    /**
     * Deja el token con exactamente los entornos indicados.
     *
     * @param  array<int>  $environmentIds  Ya validados con validateAssignableEnvironments()
     * @return array{linked: int, unlinked: array<string>}  Vinculados y dominios que se quedan sin licencia
     */
    protected function syncTokenEnvironments(LicenseToken $token, array $environmentIds): array
    {
        return DB::transaction(function () use ($token, $environmentIds) {
            $currentIds = Environment::where('license_token_id', $token->id)->pluck('id')->all();
            $toUnlink = array_values(array_diff($currentIds, $environmentIds));

            // Se recogen los dominios antes de desvincular, para poder avisar de
            // qué sitios se quedan sin licencia (antes esto pasaba en silencio).
            $unlinkedDomains = empty($toUnlink)
                ? []
                : Environment::whereIn('id', $toUnlink)->pluck('domain')->all();

            if (!empty($toUnlink)) {
                Environment::whereIn('id', $toUnlink)->update(['license_token_id' => null]);
            }

            $toLink = array_values(array_diff($environmentIds, $currentIds));

            if (!empty($toLink)) {
                Environment::whereIn('id', $toLink)->update(['license_token_id' => $token->id]);
            }

            return [
                'linked' => count($environmentIds),
                'unlinked' => $unlinkedDomains,
            ];
        });
    }
}
