<?php

declare(strict_types=1);

namespace App\Http\Controllers\Api\Pwa;

use App\Http\Controllers\Api\Pwa\Traits\RoleAwareQueries;
use App\Http\Controllers\Controller;
use App\Http\Requests\Pwa\StoreCollectionVisitRequest;
use App\Models\CollectionVisit;
use App\Models\Credit;
use App\Services\CollectorVisibilityResolver;
use Carbon\Carbon;
use Illuminate\Database\UniqueConstraintViolationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;

class CollectionVisitController extends Controller
{
    use RoleAwareQueries;

    /**
     * POST /api/pwa/visits
     *
     * Registra una nueva visita de cobranza (no pago).
     * Soporta idempotency_key para prevenir duplicados en sync offline.
     */
    public function store(StoreCollectionVisitRequest $request): JsonResponse
    {
        $user = $request->user();
        $idempotencyKey = $request->idempotency_key;

        // Verificar si ya existe una visita con esta idempotency_key
        if ($idempotencyKey) {
            $existingVisit = CollectionVisit::query()
                ->where('company_id', $user->company_id)
                ->where('idempotency_key', $idempotencyKey)
                ->first();

            if ($existingVisit) {
                Log::info('PWA Visit: Duplicate prevented', [
                    'idempotency_key' => $idempotencyKey,
                    'existing_visit_id' => $existingVisit->id,
                    'user_id' => $user->id,
                ]);

                return response()->json([
                    'message' => 'Esta visita ya fue registrada.',
                    'duplicate' => true,
                    'visit' => $this->formatVisitResponse($existingVisit),
                ], 200);
            }
        }

        // Verificar si el usuario puede registrar visitas
        if (! $this->canRegisterVisits($request)) {
            return response()->json([
                'message' => 'No tienes permiso para registrar visitas.',
            ], 403);
        }

        // Obtener crédito y validar acceso según rol
        $query = Credit::query()
            ->where('company_id', $user->company_id)
            ->whereIn('status', Credit::ACTIVE_STATUSES);

        // Collectors solo pueden registrar visitas en sus créditos asignados
        $role = $this->getUserPwaRole($request);
        if ($role === 'collector') {
            $query->where('collector_user_id', $user->id);
        }

        $credit = $query->find($request->credit_id);

        if (! $credit) {
            return response()->json([
                'message' => 'Crédito no encontrado o no tienes acceso.',
                'errors' => [
                    'credit_id' => ['El crédito no existe o no está asignado a ti.'],
                ],
            ], 422);
        }

        // Si se indica una cuota, debe pertenecer al crédito objetivo.
        // Evita asociar una visita a una cuota de otro crédito vía payload.
        if ($request->installment_id !== null
            && ! $credit->installments()->whereKey($request->installment_id)->exists()) {
            return response()->json([
                'message' => 'La cuota indicada no pertenece a este crédito.',
                'errors' => [
                    'installment_id' => ['La cuota no pertenece al crédito indicado.'],
                ],
            ], 422);
        }

        try {
            $visit = CollectionVisit::create([
                'company_id' => $user->company_id,
                'credit_id' => $request->credit_id,
                'installment_id' => $request->installment_id,
                'collector_user_id' => $user->id,
                'visit_type' => $request->visit_type,
                'reason' => $request->reason,
                'notes' => $request->notes,
                'promised_date' => $request->promised_date,
                'promised_amount' => $request->promised_amount,
                'visit_date' => $request->visit_date,
                'visit_time' => $request->visit_time,
                'latitude' => $request->latitude,
                'longitude' => $request->longitude,
                'device_id' => $request->device_id,
                'idempotency_key' => $idempotencyKey,
            ]);

            Log::info('PWA Visit: Success', [
                'visit_id' => $visit->id,
                'credit_id' => $credit->id,
                'visit_type' => $request->visit_type,
                'reason' => $request->reason,
                'user_id' => $user->id,
                'idempotency_key' => $idempotencyKey,
            ]);

            return response()->json([
                'message' => 'Visita registrada correctamente.',
                'visit' => $this->formatVisitResponse($visit),
            ], 201);

        } catch (UniqueConstraintViolationException $e) {
            // Carrera: otro envío con la misma clave (el lote, o un reintento) guardó
            // la visita entre la búsqueda y este insert, y el índice único frenó a
            // este. Es la misma visita: se devuelve como duplicado, igual que el lote.
            $existingVisit = $idempotencyKey
                ? CollectionVisit::query()
                    ->where('company_id', $user->company_id)
                    ->where('idempotency_key', $idempotencyKey)
                    ->first()
                : null;

            if (! $existingVisit) {
                // El índice único es global: la clave la tiene otra empresa. No es
                // un duplicado propio; el mismo 500 que cualquier otro fallo, sin
                // el mensaje (trae el SQL con las notas y el GPS).
                Log::error('PWA Visit: Error', [
                    'credit_id' => $request->credit_id,
                    'user_id' => $user->id,
                    'exception' => get_class($e),
                    'code' => $e->getCode(),
                ]);

                return response()->json([
                    'message' => 'Error al registrar la visita. Intenta de nuevo.',
                ], 500);
            }

            Log::info('PWA Visit: Duplicate prevented', [
                'idempotency_key' => $idempotencyKey,
                'existing_visit_id' => $existingVisit->id,
                'user_id' => $user->id,
                'via' => 'carrera',
            ]);

            return response()->json([
                'message' => 'Esta visita ya fue registrada.',
                'duplicate' => true,
                'visit' => $this->formatVisitResponse($existingVisit),
            ], 200);
        } catch (\Exception $e) {
            Log::error('PWA Visit: Error', [
                'credit_id' => $request->credit_id,
                'visit_type' => $request->visit_type,
                'user_id' => $user->id,
                'error' => $e->getMessage(),
            ]);

            return response()->json([
                'message' => 'Error al registrar la visita. Intenta de nuevo.',
            ], 500);
        }
    }

    /**
     * GET /api/pwa/visits/today
     *
     * Lista visitas registradas hoy.
     * - Collector: solo las propias
     * - Supervisor: solo las de los cobradores de su equipo asignado
     *   (acepta ?collector_id para filtrar a uno de su equipo)
     * - Admin: todas las de la empresa (acepta ?collector_id para filtrar)
     */
    public function today(Request $request): JsonResponse
    {
        $user = $request->user();
        $today = Carbon::today()->toDateString();
        $role = $this->getUserPwaRole($request);

        $query = CollectionVisit::query()
            ->where('company_id', $user->company_id)
            ->whereDate('visit_date', $today)
            ->with(['credit.client:id,name'])
            ->orderBy('created_at', 'desc');

        if ($role === 'collector') {
            $query->where('collector_user_id', $user->id);
        } elseif ($role === 'supervisor') {
            $visibility = app(CollectorVisibilityResolver::class)->resolve($user);
            if ($visibility['mode'] === CollectorVisibilityResolver::MODE_ALL) {
                // supervisor con sees_all_collectors — ve toda la empresa; acepta filtro opcional
                if ($request->filled('collector_id')) {
                    $query->where('collector_user_id', (int) $request->get('collector_id'));
                }
            } elseif ($visibility['mode'] === CollectorVisibilityResolver::MODE_ASSIGNED) {
                $visibleIds = $visibility['collector_ids'];
                if ($request->filled('collector_id') && in_array((int) $request->get('collector_id'), $visibleIds)) {
                    $query->where('collector_user_id', (int) $request->get('collector_id'));
                } else {
                    $query->whereIn('collector_user_id', $visibleIds);
                }
            } else {
                // MODE_UNASSIGNED: sin cobradores asignados → resultado vacío
                $query->whereRaw('1 = 0');
            }
        } elseif ($request->filled('collector_id')) {
            // admin con ?collector_id explícito
            $query->where('collector_user_id', (int) $request->get('collector_id'));
        }

        $data = $query->get()->map(fn ($visit) => $this->formatVisitResponse($visit));

        return response()->json([
            'data' => $data->values(),
            'total_count' => $data->count(),
            'date' => $today,
        ]);
    }

    /**
     * Formatea la respuesta de una visita.
     */
    private function formatVisitResponse(CollectionVisit $visit): array
    {
        $visit->loadMissing(['credit.client:id,name']);

        return [
            'id' => $visit->id,
            'credit_id' => $visit->credit_id,
            'installment_id' => $visit->installment_id,
            'client_name' => $visit->credit?->client?->name ?? 'N/A',
            'visit_type' => $visit->visit_type,
            'visit_type_label' => $visit->getVisitTypeLabel(),
            'reason' => $visit->reason,
            'reason_label' => $visit->getReasonLabel(),
            'notes' => $visit->notes,
            'promised_date' => $visit->promised_date?->toDateString(),
            'promised_amount' => $visit->promised_amount ? round((float) $visit->promised_amount, 2) : null,
            'visit_date' => $visit->visit_date->toDateString(),
            'visit_time' => $visit->visit_time,
            'has_location' => $visit->latitude !== null && $visit->longitude !== null,
            'registered_at' => $visit->created_at->toIso8601String(),
            'idempotency_key' => $visit->idempotency_key,
        ];
    }
}
