<?php

declare(strict_types=1);

namespace App\Http\Controllers\Api\Pwa\Traits;

use App\Models\Credit;
use App\Models\Installment;
use App\Models\User;
use App\Services\CollectorVisibilityResolver;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;

/**
 * Trait para queries sensibles al rol del usuario.
 *
 * Usa Spatie Permissions como unica fuente de verdad para roles.
 * Todos los queries estan limitados a company_id del usuario.
 */
trait RoleAwareQueries
{
    /**
     * Obtiene el rol PWA del usuario usando Spatie getRoleNames().
     *
     * Orden de prioridad: admin > supervisor > collector
     * Retorna el rol de mayor privilegio si el usuario tiene multiples.
     */
    protected function getUserPwaRole(Request $request): string
    {
        return $this->pwaRoleFor($request->user());
    }

    /**
     * Rol PWA de cualquier usuario (no solo del autenticado). La cola offline lo
     * necesita para el capturador de un ítem, que puede no ser quien sube.
     *
     * Orden de prioridad: admin > supervisor > collector.
     */
    protected function pwaRoleFor(User $user): string
    {
        $roles = $user->getRoleNames();

        if ($roles->contains('admin')) {
            return 'admin';
        }
        if ($roles->contains('supervisor')) {
            return 'supervisor';
        }
        if ($roles->contains('collector')) {
            return 'collector';
        }

        return 'unknown';
    }

    /**
     * Aplica filtro de creditos segun rol.
     *
     * - Todos los roles: filtro por company_id
     * - Collector: solo creditos asignados a el
     * - Supervisor/Admin: todos los creditos de la empresa
     */
    protected function applyCreditRoleFilter(Builder $query, Request $request): Builder
    {
        $user = $request->user();
        $role = $this->getUserPwaRole($request);

        // Siempre filtrar por empresa (company-scoped)
        $query->where('credits.company_id', $user->company_id);

        // Collectors solo ven sus creditos asignados
        if ($role === 'collector') {
            $query->where('credits.collector_user_id', $user->id);
        }

        return $query;
    }

    /**
     * Aplica filtro de clientes segun rol.
     *
     * - Collector: solo clientes con creditos activos asignados a el
     * - Supervisor/Admin: todos los clientes de la empresa
     */
    protected function applyClientRoleFilter(Builder $query, Request $request): Builder
    {
        $user = $request->user();
        $role = $this->getUserPwaRole($request);

        // Siempre filtrar por empresa
        $query->where('clients.company_id', $user->company_id);

        // Collectors solo ven clientes con creditos asignados a ellos
        if ($role === 'collector') {
            $query->whereHas('credits', function (Builder $q) use ($user) {
                $q->where('collector_user_id', $user->id)
                    ->whereIn('status', Credit::ACTIVE_STATUSES);
            });
        }

        return $query;
    }

    /**
     * Obtiene contadores de cuotas de manera optimizada.
     *
     * Usa withCount para evitar N+1 queries.
     */
    protected function withInstallmentCounts(Builder $query): Builder
    {
        return $query->withCount([
            'installments as installments_paid_count' => function (Builder $q) {
                $q->where('status', Installment::STATUS_PAID);
            },
            'installments as installments_pending_count' => function (Builder $q) {
                $q->where('status', '!=', Installment::STATUS_PAID);
            },
            'installments as total_installments_count',
        ]);
    }

    /**
     * Obtiene sumas de cuotas de manera optimizada.
     *
     * Usa withSum para evitar N+1 queries en balances.
     */
    protected function withInstallmentSums(Builder $query): Builder
    {
        return $query
            ->withSum('installments as total_receivable', 'total_amount')
            ->withSum('installments as total_paid', 'amount_paid');
    }

    /**
     * Verifica si el usuario puede registrar pagos.
     */
    protected function canRegisterPayments(Request $request): bool
    {
        $role = $this->getUserPwaRole($request);

        return in_array($role, ['collector', 'admin'], true);
    }

    /**
     * Verifica si el usuario puede registrar visitas de cobranza.
     *
     * A diferencia de canRegisterPayments(), los supervisores también
     * pueden registrar visitas (informacional, sin implicación financiera).
     */
    protected function canRegisterVisits(Request $request): bool
    {
        $role = $this->getUserPwaRole($request);

        return in_array($role, ['collector', 'supervisor', 'admin'], true);
    }

    /**
     * Verifica si el usuario puede crear clientes.
     * Ahora permitido para: admin, supervisor, collector
     */
    protected function canCreateClients(Request $request): bool
    {
        $role = $this->getUserPwaRole($request);

        return in_array($role, ['admin', 'supervisor', 'collector'], true);
    }

    /**
     * Verifica si el usuario puede editar clientes.
     * Permitido para: admin, supervisor, collector (igual que crear).
     */
    protected function canEditClients(Request $request): bool
    {
        $role = $this->getUserPwaRole($request);

        return in_array($role, ['admin', 'supervisor', 'collector'], true);
    }

    /**
     * Verifica si el usuario puede ANULAR pagos desde la PWA.
     * Solo admin (operacion financiera sensible: revierte un cobro).
     */
    protected function canVoidPayments(Request $request): bool
    {
        return $this->getUserPwaRole($request) === 'admin';
    }

    /**
     * Aplica un filtro opcional por collector_id validando que el usuario
     * indicado pertenezca a la empresa del solicitante.
     *
     * Defensa en profundidad: aunque las queries ya están limitadas por
     * company_id, esta validación hace explícita la intención y evita que un
     * collector_id de otra empresa devuelva un conjunto sin filtrar si en el
     * futuro se relajara el scope base. Un collector_id inválido o de otra
     * empresa fuerza un resultado vacío en lugar de ignorarse silenciosamente.
     *
     * @param  string  $column  Columna a filtrar (p. ej. 'credits.collector_user_id'
     *                          o 'registered_by_user_id').
     */
    protected function applyCollectorIdFilter(Builder $query, Request $request, string $column): Builder
    {
        if (! $request->filled('collector_id')) {
            return $query;
        }

        $collectorId = (int) $request->get('collector_id');

        $belongsToCompany = User::query()
            ->where('id', $collectorId)
            ->where('company_id', $request->user()->company_id)
            ->exists();

        if ($belongsToCompany) {
            $query->where($column, $collectorId);
        } else {
            $query->whereRaw('1 = 0');
        }

        return $query;
    }

    /**
     * Verifica si el usuario puede ver todos los creditos de la empresa.
     */
    protected function canViewAllCredits(Request $request): bool
    {
        $role = $this->getUserPwaRole($request);

        return in_array($role, ['supervisor', 'admin'], true);
    }

    /**
     * Scope de visibilidad supervisor→cobrador (#71) para queries por
     * `collector_user_id`, aplicando el filtro puntual `?collector_id` validado
     * contra lo que el usuario realmente puede ver. Centraliza la regla para
     * todos los endpoints PWA. Devuelve:
     *   null  → sin filtro (collector_user_id de toda la empresa: admin / supervisor
     *           con sees_all_collectors y sin filtro puntual)
     *   int[] → filtrar `collector_user_id IN (...)`; `[]` ⇒ no ve a nadie
     *           (supervisor sin asignaciones, o `collector_id` fuera de su alcance).
     *
     * @return int[]|null
     */
    protected function visibleCollectorScope(Request $request): ?array
    {
        $user = $request->user();

        // Collector: siempre y solo sus propios créditos.
        if ($this->getUserPwaRole($request) === 'collector') {
            return [$user->id];
        }

        // Supervisor/Admin: visibilidad resuelta por la fuente única de verdad.
        $visible = app(CollectorVisibilityResolver::class)->visibleCollectorIds($user); // null|[]|int[]

        if (! $request->filled('collector_id')) {
            return $visible;
        }

        // Filtro puntual: el cobrador solicitado debe estar DENTRO de lo visible.
        $requested = (int) $request->get('collector_id');
        $allowed = $visible === null
            ? $this->collectorBelongsToCompany($request, $requested)
            : in_array($requested, $visible, true);

        return $allowed ? [$requested] : [];
    }

    /**
     * ¿El id corresponde a un cobrador de la empresa del solicitante?
     */
    private function collectorBelongsToCompany(Request $request, int $collectorId): bool
    {
        return User::query()
            ->where('id', $collectorId)
            ->where('company_id', $request->user()->company_id)
            ->role('collector')
            ->exists();
    }

    /**
     * Verifica si el usuario puede crear créditos.
     * Ahora permitido para: admin, supervisor, collector
     */
    protected function canCreateCredits(Request $request): bool
    {
        $role = $this->getUserPwaRole($request);

        return in_array($role, ['admin', 'supervisor', 'collector'], true);
    }

    /**
     * Genera display de frecuencia legible.
     *
     * Semántica de due_day_1 (definida en DueDateService):
     *   weekly   → ISO weekday 1-7 (Lun-Dom)
     *   biweekly → día del mes 1-31 (cobro 2 veces/mes)
     *   monthly  → día del mes 1-31
     */
    protected function getFrequencyDisplay(Credit $credit): string
    {
        // ISO weekday 1 (Lun) … 7 (Dom). Índice 0 reservado como centinela vacío.
        $dayNames = ['', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado', 'Domingo'];

        return match ($credit->periodicity) {
            'weekly' => 'Cada '.(
                isset($credit->due_day_1) && $credit->due_day_1 >= 1 && $credit->due_day_1 <= 7
                    ? $dayNames[(int) $credit->due_day_1]
                    : 'semana'
            ),
            'biweekly' => 'Días '.($credit->due_day_1 ?? '?').' y '.($credit->due_day_2 ?? '?'),
            'monthly' => 'Día '.($credit->due_day_1 ?? '?'),
            default => ucfirst($credit->periodicity ?? 'N/A'),
        };
    }
}
