<?php

declare(strict_types=1);

namespace App\Http\Controllers\Api\Pwa;

use App\Exceptions\PlanLimitExceededException;
use App\Http\Controllers\Api\Pwa\Traits\RoleAwareQueries;
use App\Http\Controllers\Controller;
use App\Http\Requests\Pwa\StoreClientRequest;
use App\Http\Requests\Pwa\UpdateClientRequest;
use App\Models\Client;
use App\Models\Company;
use App\Models\Credit;
use App\Models\DepartmentMunicipality;
use App\Models\Installment;
use App\Services\PlanLimitService;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class ClientController extends Controller
{
    use RoleAwareQueries;

    /**
     * GET /api/pwa/clients
     *
     * Lista clientes segun el rol del usuario.
     * - Collector: solo clientes con creditos activos asignados (por defecto)
     * - Supervisor/Admin: todos los clientes de la empresa
     *
     * Query params:
     * - for_credit=1: Retorna todos los clientes activos (para crear creditos)
     */
    public function index(Request $request): JsonResponse
    {
        $role = $this->getUserPwaRole($request);
        $user = $request->user();
        $search = $request->get('search', '');
        $forCredit = $request->boolean('for_credit', false);

        $query = Client::query()
            ->where('company_id', $user->company_id)
            ->where('is_active', true);

        // Aplicar filtro de rol (excepto cuando es para crear créditos)
        if ($role === 'collector' && ! $forCredit) {
            // Collectors ven:
            // 1. Clientes con creditos activos asignados a ellos
            // 2. Clientes que ellos mismos crearon (para poder asignarles créditos)
            $query->where(function ($q) use ($user) {
                $q->whereHas('credits', function ($creditQuery) use ($user) {
                    $creditQuery->where('collector_user_id', $user->id)
                        ->whereIn('status', Credit::ACTIVE_STATUSES);
                })
                    ->orWhere('created_by_user_id', $user->id);
            });
        }

        // Aplicar busqueda
        if ($search) {
            $query->where(function ($q) use ($search) {
                $q->where('name', 'like', "%{$search}%")
                    ->orWhere('identification', 'like', "%{$search}%")
                    ->orWhere('phone', 'like', "%{$search}%");
            });
        }

        // Contadores optimizados con withCount
        // IMPORTANTE: Excluir créditos padre (con hijos derivados)
        $query->withCount([
            'credits as active_credits_count' => function ($q) use ($user, $role) {
                $q->whereIn('status', Credit::ACTIVE_STATUSES)
                    ->whereDoesntHave('children'); // Excluir créditos padre
                if ($role === 'collector') {
                    $q->where('collector_user_id', $user->id);
                }
            },
            'credits as historical_credits_count' => function ($q) use ($user, $role) {
                $q->whereNotIn('status', Credit::ACTIVE_STATUSES)
                    ->whereDoesntHave('children'); // Excluir créditos padre
                if ($role === 'collector') {
                    $q->where('collector_user_id', $user->id);
                }
            },
        ]);

        // Sin tope: offline-first — la PWA necesita todos los clientes del cobrador
        // sincronizados localmente. El `->limit(100)` fijo truncaba en silencio (mismo
        // defecto que /credits). Acotado por rol/empresa y por el cupo de plan (max_clients).
        $clients = $query->orderBy('name')->get();

        $data = $clients->map(fn (Client $client) => [
            'id' => $client->id,
            'name' => $client->name,
            'identification' => $client->identification,
            'phone' => $client->phone,
            'active_credits' => (int) $client->active_credits_count,
            'historical_credits' => (int) $client->historical_credits_count,
        ]);

        return response()->json([
            'data' => $data->values(),
            'meta' => [
                'total' => $data->count(),
                'search' => $search,
                'role' => $role,
                'can_create' => $this->canCreateClients($request),
            ],
        ]);
    }

    /**
     * POST /api/pwa/clients
     *
     * Crea un nuevo cliente.
     */
    public function store(StoreClientRequest $request): JsonResponse
    {
        // Verificar permiso usando Spatie role
        if (! $this->canCreateClients($request)) {
            return response()->json([
                'message' => 'No tienes permiso para crear clientes.',
            ], 403);
        }

        $user = $request->user();

        // Crea el cliente (+ su dirección). Se ejecuta bajo el guard de cupo para
        // que "contar + crear" sea atómico (#169).
        $createClient = function () use ($request, $user): Client {
            $client = Client::create([
                'company_id' => $user->company_id,
                'name' => $request->name,
                'identification' => $request->identification,
                'phone' => $request->phone,
                'email' => $request->email,
                'created_by_user_id' => $user->id,
                'is_active' => true,
                'preferred_payment_method' => 'physical',
            ]);

            // Crear direccion si se proporciona cualquier campo de ubicación
            if ($request->filled('address') || $request->filled('state') || $request->filled('city')) {
                $client->addresses()->create([
                    'address_line_1' => $request->address,
                    'state' => $request->state,
                    'city' => $request->city,
                    'is_primary' => true,
                ]);
            }

            return $client;
        };

        // Límite de clientes del plan (bloqueo estricto, igual que en el panel).
        // El assert + la creación van dentro de runGuardedCreate (transacción +
        // lock por empresa) para que dos syncs concurrentes no rebasen el cupo (#169).
        $company = Company::find($user->company_id);
        try {
            $service = app(PlanLimitService::class);
            $client = $company
                ? $service->runGuardedCreate($company, function () use ($service, $company, $createClient) {
                    $service->assertCanAddClient($company);

                    return $createClient();
                })
                : $createClient();
        } catch (PlanLimitExceededException $e) {
            return response()->json(['message' => $e->getMessage()], 422);
        }

        return response()->json([
            'message' => 'Cliente creado correctamente.',
            'client' => [
                'id' => $client->id,
                'name' => $client->name,
                'identification' => $client->identification,
            ],
        ], 201);
    }

    /**
     * PUT /api/pwa/clients/{id}
     *
     * Edita un cliente existente (datos de contacto, cedula y direccion primaria).
     * Solo-online desde la PWA. No crea cliente nuevo → sin guard de cupo.
     */
    public function update(UpdateClientRequest $request, int $id): JsonResponse
    {
        if (! $this->canEditClients($request)) {
            return response()->json([
                'message' => 'No tienes permiso para editar clientes.',
            ], 403);
        }

        $client = $this->visibleClientsQuery($request)->find($id);

        if (! $client) {
            return response()->json([
                'message' => 'Cliente no encontrado o no tienes acceso.',
            ], 404);
        }

        DB::transaction(function () use ($client, $request) {
            $client->update([
                'name' => $request->name,
                'identification' => $request->identification,
                'phone' => $request->phone,
                'email' => $request->email,
            ]);

            $hasAddress = $request->filled('address')
                || $request->filled('state')
                || $request->filled('city');

            if ($hasAddress) {
                $payload = [
                    'address_line_1' => $request->address,
                    'state' => $request->state,
                    'city' => $request->city,
                ];

                $address = $client->addresses()->where('is_primary', true)->first();

                if ($address) {
                    $address->update($payload);
                } else {
                    $client->addresses()->create($payload + ['is_primary' => true]);
                }
            }
        });

        $client->refresh()->load(['primaryAddress', 'addresses']);

        return response()->json([
            'message' => 'Cliente actualizado correctamente.',
            'client' => [
                'id' => $client->id,
                'name' => $client->name,
                'identification' => $client->identification,
                'phone' => $client->phone,
                'email' => $client->email,
                // Dirección formateada (misma fuente que el detalle) para refrescar la
                // caché IndexedDB del cliente y no dejarla obsoleta offline tras editar.
                'address' => $client->formatted_address,
                'state' => $client->primaryAddress?->state,
                'city' => $client->primaryAddress?->city,
                'address_line_1' => $client->primaryAddress?->address_line_1,
            ],
        ]);
    }

    /**
     * GET /api/pwa/locations/departments
     *
     * Lista todos los departamentos disponibles.
     */
    public function departments(): JsonResponse
    {
        $departments = DepartmentMunicipality::query()
            ->select('department_name')
            ->distinct()
            ->orderBy('department_name')
            ->pluck('department_name');

        return response()->json([
            'data' => $departments,
        ]);
    }

    /**
     * GET /api/pwa/locations/cities?department=Antioquia
     *
     * Lista ciudades/municipios de un departamento.
     */
    public function cities(Request $request): JsonResponse
    {
        $department = $request->get('department');

        if (! $department) {
            return response()->json([
                'data' => [],
            ]);
        }

        $cities = DepartmentMunicipality::query()
            ->where('department_name', $department)
            ->orderBy('municipality_name')
            ->pluck('municipality_name');

        return response()->json([
            'data' => $cities,
        ]);
    }

    /**
     * Query base de clientes visibles para el usuario segun su rol.
     * - admin/supervisor: todos los de su empresa.
     * - collector: clientes con creditos asignados a el O creados por el.
     * Fuente unica de visibilidad para show() y update().
     *
     * @return Builder<Client>
     */
    private function visibleClientsQuery(Request $request): Builder
    {
        $user = $request->user();
        $role = $this->getUserPwaRole($request);

        $query = Client::query()->where('company_id', $user->company_id);

        if ($role === 'collector') {
            $query->where(function ($q) use ($user) {
                $q->whereHas('credits', function ($creditQuery) use ($user) {
                    $creditQuery->where('collector_user_id', $user->id);
                })->orWhere('created_by_user_id', $user->id);
            });
        }

        return $query;
    }

    /**
     * GET /api/pwa/clients/{id}
     *
     * Detalle de cliente con sus creditos.
     */
    public function show(Request $request, int $id): JsonResponse
    {
        $role = $this->getUserPwaRole($request);
        $user = $request->user();

        $client = $this->visibleClientsQuery($request)
            ->with([
                'primaryAddress',
                'credits' => function ($q) use ($user, $role) {
                    // Filtrar creditos segun rol
                    if ($role === 'collector') {
                        $q->where('collector_user_id', $user->id);
                    }
                    $q->whereIn('status', Credit::ACTIVE_STATUSES)
                        ->with([
                            'collector:id,name',
                            'installments' => function ($q) {
                                $q->whereIn('status', [
                                    Installment::STATUS_PENDING,
                                    Installment::STATUS_PARTIAL_PAID,
                                    Installment::STATUS_OVERDUE,
                                ])->orderBy('due_date')->limit(1);
                            },
                        ])
                      // Contadores optimizados
                        ->withCount([
                            'installments as installments_paid_count' => function ($q) {
                                $q->where('status', Installment::STATUS_PAID);
                            },
                        ]);
                },
            ])
            ->find($id);

        if (! $client) {
            return response()->json([
                'message' => 'Cliente no encontrado o no tienes acceso.',
            ], 404);
        }

        $credits = $client->credits->map(function (Credit $credit) use ($role) {
            $nextInstallment = $credit->installments->first();

            $data = [
                'id' => $credit->id,
                'code' => $credit->code,
                'company_credit_number' => $credit->company_credit_number,
                'status' => $credit->status,
                'status_label' => $credit->getStatusLabel(),
                'amount' => round((float) $credit->amount, 2),
                'remaining_balance' => round((float) $credit->remaining_balance, 2),
                'installments_paid' => (int) $credit->installments_paid_count,
                'total_installments' => $credit->installments_count,
                'next_due_date' => $nextInstallment?->due_date?->toDateString(),
                'next_due_amount' => $nextInstallment
                    ? round((float) $nextInstallment->remaining_amount, 2)
                    : null,
            ];

            // Incluir collector para supervisor/admin
            if (in_array($role, ['supervisor', 'admin'], true) && $credit->collector) {
                $data['collector'] = [
                    'id' => $credit->collector->id,
                    'name' => $credit->collector->name,
                ];
            }

            return $data;
        });

        return response()->json([
            'id' => $client->id,
            'name' => $client->name,
            'identification' => $client->identification,
            'phone' => $client->phone,
            'email' => $client->email,
            'address' => $client->formatted_address,
            'state' => $client->primaryAddress?->state,
            'city' => $client->primaryAddress?->city,
            'address_line_1' => $client->primaryAddress?->address_line_1,
            'credits' => $credits->values(),
            'role' => $role,
            'can_create_credits' => false, // Future feature
        ]);
    }
}
