<?php
namespace App\Services;

use App\Models\{Pay, Pay_details, Pay_type, Sale, User_balance};
use Illuminate\Http\Request;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

class PaysServices
{
    protected ImagesServices $imagesServices;
    protected UserBalanceServices $userBalanceServices;

    public function __construct(ImagesServices $imagesServices, UserBalanceServices $userBalanceServices)
    {
        $this->imagesServices = $imagesServices;
        $this->userBalanceServices = $userBalanceServices;
    }

    public function getPaysData(
        int $page,
        int $length,
        ?string $searchValue = null,
        ?string $status = null,
            ?string $dateFrom = null,
            ?string $dateTo = null
    ): LengthAwarePaginator
    {
        $query = Pay::query()
            ->select([
                'id',
                'pay_type_id',
                'user_id',
                'bank_id',
                'amount',
                'reference',
                'status',   
                'pay_date'
            ])
            ->orderByDesc('id');

        if (! empty($searchValue)) {
            $query->where(function ($subQuery) use ($searchValue): void {
                $subQuery->where('reference', 'like', "%{$searchValue}%")
                    ->orWhere('amount', 'like', "%{$searchValue}%")
                    ->orWhereHas('user', function ($userQuery) use ($searchValue): void {
                        $userQuery->where('name', 'like', "%{$searchValue}%");
                    })
                    ->orWhereHas('payType', function ($payTypeQuery) use ($searchValue): void {
                        $payTypeQuery->where('name', 'like', "%{$searchValue}%");
                    })
                    ->orWhereHas('bank', function ($bankQuery) use ($searchValue): void {
                        $bankQuery->where('name', 'like', "%{$searchValue}%");
                    })
                    ->orWhereHas('user.people', function ($peopleQuery) use ($searchValue): void {
                        $peopleQuery->where('phone', 'like', "%{$searchValue}%")
                            ->orWhere('last_name', 'like', "%{$searchValue}%");
                    });
            });
        }

        if (! empty($status) && Schema::hasColumn('pay', 'status')) {
            $query->where('status', $status);
        }

            if (!empty($dateFrom) && !empty($dateTo)) {
                $query->whereBetween('pay_date', [$dateFrom, $dateTo]);
            } elseif (!empty($dateFrom)) {
                $query->whereDate('pay_date', '>=', $dateFrom);
            } elseif (!empty($dateTo)) {
                $query->whereDate('pay_date', '<=', $dateTo);
        }

        $paginator = $query->paginate($length, ['*'], 'page', $page);

        $paginator->getCollection()->transform(function (Pay $pay) {
            return [
                'id' => $pay->id,
                'pay_type_id' => $pay->pay_type_id,
                'pay_type_name' => $pay->payType?->name,
                'user_name' => $pay->user?->name,
                'user_email' => $pay->user?->email,
                'bank_id' => $pay->bank_id,
                'bank_name' => $pay->bank?->name,
                'first_name' => $pay->user?->name,
                'last_name' => $pay->user?->people?->last_name,
                'amount' => number_format($pay->amount, 2, ',', '.'),
                'reference' => $pay->reference,
                'status' => $pay->status,   
                'pay_date_formatted' => $pay->pay_date ? \Carbon\Carbon::parse($pay->pay_date)->format('d/m/Y') : null,
            ];
        });

        return $paginator;
    }

    /**
     * Exportar pagos filtrados (sin paginación, todos los resultados)
     */
    public function getPaysExportData(
        ?string $searchValue = null,
        ?string $status = null,
            ?string $dateFrom = null,
            ?string $dateTo = null
    ): array {
        $query = Pay::query()
            ->select([
                'id',
                'pay_type_id',
                'user_id',
                'bank_id',
                'amount',
                'reference',
                'status',   
                'pay_date'
            ])
            ->orderByDesc('id');

        if (! empty($searchValue)) {
            $query->where(function ($subQuery) use ($searchValue): void {
                $subQuery->where('reference', 'like', "%{$searchValue}%")
                    ->orWhere('amount', 'like', "%{$searchValue}%")
                    ->orWhereHas('payType', function ($payTypeQuery) use ($searchValue): void {
                        $payTypeQuery->where('name', 'like', "%{$searchValue}%");
                    })
                    ->orWhereHas('bank', function ($bankQuery) use ($searchValue): void {
                        $bankQuery->where('name', 'like', "%{$searchValue}%");
                    })
                    ->orWhereHas('user.people', function ($peopleQuery) use ($searchValue): void {
                        $peopleQuery->where('phone', 'like', "%{$searchValue}%")
                            ->orWhere('last_name', 'like', "%{$searchValue}%");
                    });
            });
        }

        if (! empty($status) && Schema::hasColumn('pay', 'status')) {
            $query->where('status', $status);
        }

            if (!empty($dateFrom) && !empty($dateTo)) {
                $query->whereBetween('pay_date', [$dateFrom, $dateTo]);
            } elseif (!empty($dateFrom)) {
                $query->whereDate('pay_date', '>=', $dateFrom);
            } elseif (!empty($dateTo)) {
                $query->whereDate('pay_date', '<=', $dateTo);
        }

        $rows = $query->get();

        return $rows->map(function (Pay $pay) {
            return [
                'id' => $pay->id,
                'pay_type_id' => $pay->pay_type_id,
                'pay_type_name' => $pay->payType?->name,
                'user_name' => $pay->user?->name,
                'user_email' => $pay->user?->email,
                'bank_id' => $pay->bank_id,
                'bank_name' => $pay->bank?->name,
                'first_name' => $pay->user?->name,
                'last_name' => $pay->user?->people?->last_name,
                'amount' => number_format($pay->amount, 2, ',', '.'),
                'reference' => $pay->reference,
                'status' => $pay->status,   
                'pay_date_formatted' => $pay->pay_date ? \Carbon\Carbon::parse($pay->pay_date)->format('d/m/Y') : null,
            ];
        })->toArray();
    }

    public function createPay(Request $request): Pay
    {
        $validated = $request->validate([
            'pay_type_id' => 'required|integer|exists:pay_type,id',
            'company_id' => 'required|integer|exists:company,id',
            'user_id' => 'required|integer|exists:users,id',
            'pay_date' => 'required|date',
            'amount' => 'required|numeric|min:0.01',
            'id_card_number' => 'nullable|string|max:50',
            'bank_id' => 'nullable|integer|exists:bank,id',
            'reference' => 'nullable|string|max:100',
            'status' => 'required|string|in:Aprobado,Rechazado,Pendiente',
            'url_pay_imagen' => 'nullable|image|max:4096',
        ]);

        if ($request->hasFile('url_pay_imagen')) {
            $urlPayImage = $this->imagesServices->createImage([
                'file' => $request->file('url_pay_imagen'),
                'folder' => 'pay',
            ]);

            if ($urlPayImage) {
                $validated['url_pay_imagen'] = $urlPayImage;
            }
        }

        return Pay::create($validated);
    }

    public function updatePay(Request $request, $pay): Pay
    {
        $payModel = $pay instanceof Pay ? $pay : Pay::findOrFail($pay);
        $previousStatus = (string) ($payModel->status ?? '');

        $validated = $request->validate([
            'pay_type_id' => 'required|integer|exists:pay_type,id',
            'pay_date' => 'required|date',
            'amount' => 'required|numeric|min:0.01',
            'id_card_number' => 'nullable|string|max:50',
            'bank_id' => 'nullable|integer|exists:bank,id',
            'reference' => 'nullable|string|max:100',
            'status' => 'required|string|in:Aprobado,Rechazado,Pendiente',
            'url_pay_imagen' => 'nullable|image|max:4096',
        ]);

        if ($request->hasFile('url_pay_imagen')) {
            $urlPayImage = $this->imagesServices->createImage([
                'file' => $request->file('url_pay_imagen'),
                'folder' => 'pay',
            ]);

            if ($urlPayImage) {
                $validated['url_pay_imagen'] = $urlPayImage;
            }
        }

        DB::transaction(function () use ($payModel, $validated, $previousStatus): void {
            $payModel->update($validated);

            $newStatus = (string) ($validated['status'] ?? $payModel->status ?? '');
            $updatedPay = $payModel->refresh();

            if ($this->isTransitionToApproved($previousStatus, $newStatus)) {
                $this->applyApprovedPayEffects($updatedPay);
                return;
            }

            if ($this->isTransitionFromApprovedToRejected($previousStatus, $newStatus)) {
                $this->revertApprovedPayEffects($updatedPay);
            }
        });

        return $payModel->refresh();
    }

    public function storeSomesPay(Request $request, int $userId, int $companyId, string $status = 'Pendiente'): Pay
    {
        if ($request->has('amount')) {
            $request->merge(['amount' => $this->normalizeDecimalInput($request->input('amount'))]);
        }

        // Recalcular deuda según fecha: hoy usa tasa activa, fecha anterior usa tasa histórica.
        $payDate = $request->input('pay_date');
        $today = date('Y-m-d');

        if ($payDate) {
            if ($payDate === $today) {
                app(\App\Services\SalesService::class)->setNewRateToDebtSales($userId);
            } else {
                app(\App\Services\SalesService::class)->setNewRateToDebtSales($userId, $payDate);
            }
        }

        $selectedSaleIdsFromRequest = [];
        if ($request->has('sale_ids') && is_array($request->input('sale_ids'))) {
            $selectedSaleIdsFromRequest = array_values(array_unique(array_map('intval', $request->input('sale_ids'))));
        }

        if ($request->has('sale_amounts') && is_array($request->input('sale_amounts'))) {
            $normalizedSaleAmounts = [];
            foreach ($request->input('sale_amounts') as $saleId => $rawAmount) {
                $saleIdInt = (int) $saleId;

                // Only keep detail amounts for debts that were actually selected.
                if (!in_array($saleIdInt, $selectedSaleIdsFromRequest, true)) {
                    continue;
                }

                if ($rawAmount === null || $rawAmount === '') {
                    $normalizedSaleAmounts[$saleIdInt] = $rawAmount;
                    continue;
                }

                $normalizedSaleAmounts[$saleIdInt] = $this->normalizeDecimalInput((string) $rawAmount);
            }
            $request->merge(['sale_amounts' => $normalizedSaleAmounts]);
        }

        $request->merge([
            'user_id' => $userId,
            'status' => $status,
            'company_id' => $companyId,
        ]);

        $isCashPayment = $this->isCashPayTypeId((int) $request->input('pay_type_id'));
        $isFullInput = (bool) $request->boolean('is_full_input');

        $validated = $request->validate([
            'pay_type_id' => 'required|integer',
            'user_id' => 'required|integer',
            'pay_date' => 'required|date',
            'amount' => 'required|numeric|min:0.01',
            'bank_id' => $isCashPayment
                ? 'nullable|integer|exists:bank,id'
                : 'required|integer|exists:bank,id',
            'reference' => $isFullInput
                ? 'required|string|max:100'
                : 'nullable|string|max:100',
            'id_card_number' => $isFullInput
                ? 'required|string|max:50'
                : 'nullable|string|max:50',
            'url_pay_imagen' => 'nullable|image|max:4096',
            'sale_ids' => 'nullable|array',
            'sale_ids.*' => 'integer|exists:sale,id',
            'sale_amounts' => 'nullable|array',
            'sale_amounts.*' => 'nullable',
            'status' => 'required|string|in:Aprobado,Rechazado,Pendiente',
            'company_id' => 'required|integer|exists:company,id',
            'is_full_input' => 'nullable|boolean',
        ]);

        $selectedSaleIds = array_values(array_unique($validated['sale_ids'] ?? []));
        $saleAmounts = $validated['sale_amounts'] ?? [];
        unset($validated['sale_ids'], $validated['sale_amounts']);

        $hasPendingDebts = Sale::where('user_id', $userId)
            ->where('status', 'pendiente')
            ->exists();

        if ($hasPendingDebts && empty($selectedSaleIds)) {
            throw ValidationException::withMessages([
                'sale_ids' => 'Debes seleccionar al menos una deuda para asociar al pago.',
            ]);
        }

        $validPendingSaleIds = [];
        $pendingDebtsBySale = [];
        if (!empty($selectedSaleIds)) {
            $pendingSales = Sale::whereIn('id', $selectedSaleIds)
                ->where('user_id', $userId)
                ->where('status', 'pendiente')
                ->get(['id', 'debt']);

            $validPendingSaleIds = $pendingSales->pluck('id')->toArray();
            $pendingDebtsBySale = $pendingSales->pluck('debt', 'id')->toArray();
        }

        if (!empty($selectedSaleIds) && count($validPendingSaleIds) !== count($selectedSaleIds)) {
            throw ValidationException::withMessages([
                'sale_ids' => 'Una o mas deudas seleccionadas no son validas.',
            ]);
        }

        $payDetailsToCreate = [];
        foreach ($validPendingSaleIds as $saleId) {
            $rawAmount = $saleAmounts[$saleId] ?? null;

            if ($rawAmount === null || $rawAmount === '') {
                throw ValidationException::withMessages([
                    'sale_amounts' => 'Debes indicar un monto para cada deuda seleccionada.',
                ]);
            }

            $normalizedDetailAmount = $this->normalizeDecimalInput((string) $rawAmount);
            if (!is_numeric($normalizedDetailAmount) || (float) $normalizedDetailAmount <= 0) {
                throw ValidationException::withMessages([
                    'sale_amounts' => 'El monto de detalle por deuda debe ser mayor a cero.',
                ]);
            }

            $detailAmount = (float) $normalizedDetailAmount;
            $saleDebt = (float) ($pendingDebtsBySale[$saleId] ?? 0);
            if ($saleDebt > 0 && $detailAmount > $saleDebt) {
                throw ValidationException::withMessages([
                    'sale_amounts' => 'El monto de detalle no puede ser mayor a la deuda pendiente de la venta.',
                ]);
            }

            $payDetailsToCreate[] = [
                'sale_id' => (int) $saleId,
                'amount' => $detailAmount,
            ];
        }

        $totalDetailAmount = array_sum(array_column($payDetailsToCreate, 'amount'));
        if ($totalDetailAmount > (float) $validated['amount']) {
            throw ValidationException::withMessages([
                'sale_amounts' => 'La suma de montos por deuda no puede ser mayor al monto del pago.',
            ]);
        }

        if ($request->hasFile('url_pay_imagen')) {
            $urlPayImage = $this->imagesServices->createImage([
                'file' => $request->file('url_pay_imagen'),
                'folder' => 'pay',
            ]);

            if ($urlPayImage) {
                $validated['url_pay_imagen'] = $urlPayImage;
            }
        }

        $pay = DB::transaction(function () use ($validated, $payDetailsToCreate): Pay {
            $pay = Pay::create($validated);

            if (!empty($payDetailsToCreate)) {
                foreach ($payDetailsToCreate as $detail) {
                    Pay_details::create([
                        'pay_id' => $pay->id,
                        'sale_id' => $detail['sale_id'],
                        'amount' => $detail['amount'],
                    ]);
                }
            }

            return $pay;
        });

        $newStatus = mb_strtolower(trim((string) ($validated['status'] ?? $pay->status ?? '')));
        $shouldApplyApprovedEffects = $newStatus === 'aprobado';

        if ($shouldApplyApprovedEffects) {
            $this->applyApprovedPayEffects($pay->refresh());
        }

        return $pay->refresh();
    }

    private function applyApprovedPayEffects(Pay $pay): void
    {
        $this->closePayBill($pay);

        $totalDetailAmount = (float) Pay_details::query()
            ->where('pay_id', $pay->id)
            ->sum('amount');

        $remainingBalance = round(((float) $pay->amount) - $totalDetailAmount, 2);
        if ($remainingBalance <= 0) {
            return;
        }

        $this->userBalanceServices->createBalance(
            (int) $pay->user_id,
            (int) $pay->id,
            $remainingBalance
        );
    }

    public function isCashPayTypeId(?int $payTypeId): bool
    {
        if (empty($payTypeId)) {
            return false;
        }

        $payTypeName = (string) (Pay_type::query()->whereKey($payTypeId)->value('name') ?? '');
        if ($payTypeName === '') {
            return false;
        }

        $normalizedPayTypeName = Str::lower(Str::ascii(trim($payTypeName)));
        return str_contains($normalizedPayTypeName, 'efectivo')
            || str_contains($normalizedPayTypeName, 'efctivo')
            || str_contains($normalizedPayTypeName, 'cash');
    }

    private function isTransitionToApproved(?string $previousStatus, ?string $newStatus): bool
    {
        return mb_strtolower((string) ($previousStatus ?? '')) !== 'aprobado'
            && mb_strtolower((string) ($newStatus ?? '')) === 'aprobado';
    }

    private function isTransitionFromApprovedToRejected(?string $previousStatus, ?string $newStatus): bool
    {
        return mb_strtolower((string) ($previousStatus ?? '')) === 'aprobado'
            && mb_strtolower((string) ($newStatus ?? '')) === 'rechazado';
    }

    private function normalizeDecimalInput($value): ?string
    {
        if ($value === null) {
            return null;
        }

        $normalized = str_replace(' ', '', (string) $value);

        $hasComma = str_contains($normalized, ',');
        $hasDot = str_contains($normalized, '.');

        if ($hasComma && $hasDot) {
            $lastCommaPos = strrpos($normalized, ',');
            $lastDotPos = strrpos($normalized, '.');

            if ($lastCommaPos !== false && $lastDotPos !== false && $lastCommaPos > $lastDotPos) {
                $normalized = str_replace('.', '', $normalized);
                $normalized = str_replace(',', '.', $normalized);
            } else {
                $normalized = str_replace(',', '', $normalized);
            }
        } elseif ($hasComma) {
            $normalized = str_replace('.', '', $normalized);
            $normalized = str_replace(',', '.', $normalized);
        } else {
            $normalized = str_replace(',', '', $normalized);
        }
        return $normalized;
    }

    public function closePayBill(Pay $pay): void
    {
        DB::transaction(function () use ($pay): void {
            $payDetails = Pay_details::query()
                ->where('pay_id', $pay->id)
                ->get(['sale_id', 'amount']);

            if ($payDetails->isEmpty()) {
                return;
            }

            $amountBySaleId = [];
            foreach ($payDetails as $detail) {
                $saleId = (int) $detail->sale_id;
                $detailAmount = (float) $detail->amount;

                if ($saleId <= 0 || $detailAmount <= 0) {
                    continue;
                }

                if (!array_key_exists($saleId, $amountBySaleId)) {
                    $amountBySaleId[$saleId] = 0.0;
                }
                $amountBySaleId[$saleId] += $detailAmount;
            }

            if (empty($amountBySaleId)) {
                return;
            }

            $sales = Sale::query()
                ->whereIn('id', array_keys($amountBySaleId))
                ->lockForUpdate()
                ->get();

            foreach ($sales as $sale) {
                $toDiscount = (float) ($amountBySaleId[$sale->id] ?? 0);
                if ($toDiscount <= 0) {
                    continue;
                }

                $currentDebt = (float) ($sale->debt ?? 0);
                $newDebt = max(0, $currentDebt - $toDiscount);
                $amountRate = (float) ($sale->amount_rate ?? 0);
                $newAuxDebt = $amountRate > 0
                    ? round($newDebt / $amountRate, 2)
                    : $newDebt;

                $sale->debt = $newDebt;
                $sale->aux_debt = $newAuxDebt;
                if ($newDebt <= 0) {
                    $sale->status = 'completada';
                }

                $sale->save();
            }
        });
    }

    private function revertApprovedPayEffects(Pay $pay): void
    {
        DB::transaction(function () use ($pay): void {
            $payDetails = Pay_details::query()
                ->where('pay_id', $pay->id)
                ->get(['sale_id', 'amount']);

            $amountBySaleId = [];
            foreach ($payDetails as $detail) {
                $saleId = (int) $detail->sale_id;
                $detailAmount = (float) $detail->amount;

                if ($saleId <= 0 || $detailAmount <= 0) {
                    continue;
                }

                if (! array_key_exists($saleId, $amountBySaleId)) {
                    $amountBySaleId[$saleId] = 0.0;
                }

                $amountBySaleId[$saleId] += $detailAmount;
            }

            if (! empty($amountBySaleId)) {
                $sales = Sale::query()
                    ->whereIn('id', array_keys($amountBySaleId))
                    ->lockForUpdate()
                    ->get();

                foreach ($sales as $sale) {
                    $toRestore = (float) ($amountBySaleId[$sale->id] ?? 0);
                    if ($toRestore <= 0) {
                        continue;
                    }

                    $currentDebt = (float) ($sale->debt ?? 0);
                    $maxDebt = max(0, (float) ($sale->total ?? 0));
                    $restoredDebt = $currentDebt + $toRestore;
                    $newDebt = $maxDebt > 0
                        ? min($maxDebt, $restoredDebt)
                        : $restoredDebt;

                    $amountRate = (float) ($sale->amount_rate ?? 0);
                    $newAuxDebt = $amountRate > 0
                        ? round($newDebt / $amountRate, 2)
                        : $newDebt;

                    $sale->debt = $newDebt;
                    $sale->aux_debt = $newAuxDebt;

                    if ($newDebt > 0 && mb_strtolower((string) $sale->status) === 'completada') {
                        $sale->status = 'pendiente';
                    }

                    $sale->save();
                }
            }

            $totalDetailAmount = (float) Pay_details::query()
                ->where('pay_id', $pay->id)
                ->sum('amount');

            $remainingBalance = round(((float) $pay->amount) - $totalDetailAmount, 2);
            if ($remainingBalance <= 0) {
                return;
            }

            $userBalance = User_balance::query()
                ->where('user_id', (int) $pay->user_id)
                ->lockForUpdate()
                ->first();

            if (! $userBalance) {
                return;
            }

            $newUserBalance = round(max(0, ((float) $userBalance->balance) - $remainingBalance), 2);
            $userBalance->balance = $newUserBalance;
            $userBalance->is_active = $newUserBalance > 0 ? 1 : 0;
            $userBalance->save();
        });
    }
    
}