<?php

namespace App\Services;

use App\Models\Sale;
use App\Models\rate;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;
use Throwable;

class SalesService
{
    public function getSalesData(
        int $page,
        int $length,
        ?string $searchValue = null,
        ?string $status = null
    ): LengthAwarePaginator {
        $query = Sale::query()
            ->with(['user:id,name', 'people:id,last_name'])
            ->select([
                'id',
                'sale_identifier',
                'user_id',
                'people_id',
                'status',
                'total',
                'aux_total',
                'debt',
                'aux_debt',
                'created_at',
            ])
            ->orderByDesc('id');

        if (! empty($searchValue)) {
            $query->where(function ($subQuery) use ($searchValue): void {
                $subQuery->where('id', 'like', "%{$searchValue}%")
                    ->orWhere('sale_identifier', 'like', "%{$searchValue}%")
                    ->orWhere('status', 'like', "%{$searchValue}%")
                    ->orWhereHas('user', function ($userQuery) use ($searchValue): void {
                        $userQuery->where('name', 'like', "%{$searchValue}%");
                    });
            });
        }

        if ($status !== null && $status !== '') {
            $query->where('status', $status);
        }

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

        $paginator->getCollection()->transform(function (Sale $sale): array {
            $saleIdentifier = $sale->sale_identifier ?: $sale->id;
            $lastName = $sale->people?->last_name;

            return [
                'id' => (int) $sale->id,
                'name' => $sale->user?->name ?? 'N/A',
                'last_name' => $lastName ?: 'N/A',
                'sale' => str_pad((string) $saleIdentifier, 6, '0', STR_PAD_LEFT),
                'total' => number_format((float) ($sale->aux_total ?? 0), 2, ',', '.'),
                'debt' => number_format((float) ($sale->aux_debt ?? 0), 2, ',', '.'),
                'created_at_formatted' => $sale->created_at?->format('d/m/Y H:i'),
            ];
        });

        return $paginator;
    }

    public function getSaleEditData(int $saleId): array
    {
        $sale = Sale::query()
            ->with([
                'user:id,name',
                'people:id,last_name',
                'saleDetails:id,sale_id,product_id,membership_user_id,quantity,unit_price,subtotal,aux_unit_price,aux_subtotal',
                'saleDetails.product:id,product_name',
                'saleDetails.membershipUser:id,membership_id',
                'saleDetails.membershipUser.membership:id,name',
            ])
            ->findOrFail($saleId);

        $saleIdentifier = $sale->sale_identifier ?: $sale->id;

        return [
            'id' => (int) $sale->id,
            'sale' => str_pad((string) $saleIdentifier, 6, '0', STR_PAD_LEFT),
            'name' => $sale->user?->name ?? 'N/A',
            'last_name' => $sale->people?->last_name ?? 'N/A',
            'status' => (string) ($sale->status ?? ''),
            'amount_rate' => (float) ($sale->amount_rate ?? 1),
            'aux_total' => (float) ($sale->aux_total ?? 0),
            'total' => (float) ($sale->total ?? 0),
            'debt' => (float) ($sale->debt ?? 0),
            'created_at_formatted' => $sale->created_at?->format('d/m/Y H:i'),
            'details' => $sale->saleDetails->map(function ($detail): array {
                $itemName = $detail->product?->product_name
                    ?? $detail->membershipUser?->membership?->name
                    ?? 'Sin nombre';

                return [
                    'id' => (int) $detail->id,
                    'item_name' => $itemName,
                    'quantity' => (float) ($detail->quantity ?? 0),
                    'unit_price' => (float) ($detail->unit_price ?? 0),
                    'subtotal' => (float) ($detail->subtotal ?? 0),
                    'aux_unit_price' => (float) ($detail->aux_unit_price ?? 0),
                    'aux_subtotal' => (float) ($detail->aux_subtotal ?? 0),
                ];
            })->values()->all(),
        ];
    }

    public function updateSaleWithDetails(int $saleId, array $payload): array
    {
        return DB::transaction(function () use ($saleId, $payload): array {
            $sale = Sale::query()
                ->with('saleDetails:id,sale_id,quantity,unit_price,subtotal,aux_unit_price,aux_subtotal')
                ->findOrFail($saleId);

            $detailIds = collect($payload['details'] ?? [])->pluck('id')->map(fn ($id) => (int) $id)->all();
            $saleDetailIds = $sale->saleDetails->pluck('id')->map(fn ($id) => (int) $id)->all();

            foreach ($detailIds as $detailId) {
                if (! in_array($detailId, $saleDetailIds, true)) {
                    throw ValidationException::withMessages([
                        'details' => ['Uno de los detalles no pertenece a la venta.'],
                    ]);
                }
            }

            $sale->saleDetails()
                ->whereNotIn('id', $detailIds)
                ->delete();

            $amountRate = isset($payload['amount_rate'])
                ? (float) $payload['amount_rate']
                : (float) ($sale->amount_rate ?? 1);

            if ($amountRate <= 0) {
                $amountRate = 1;
            }

            foreach ($payload['details'] as $detailData) {
                $detail = $sale->saleDetails->firstWhere('id', (int) $detailData['id']);
                if (! $detail) {
                    continue;
                }

                $quantity = max(1, (int) $detailData['quantity']);
                $auxUnitPrice = (float) $detailData['aux_unit_price'];
                $auxSubtotal = round($quantity * $auxUnitPrice, 2);
                $unitPrice = round($auxUnitPrice * $amountRate, 2);
                $subtotal = round($auxSubtotal * $amountRate, 2);

                $detail->quantity = $quantity;
                $detail->aux_unit_price = $auxUnitPrice;
                $detail->aux_subtotal = $auxSubtotal;
                $detail->unit_price = $unitPrice;
                $detail->subtotal = $subtotal;
                $detail->save();
            }

            $sale->refresh();

            $auxTotal = $sale->saleDetails->sum(function ($detail) {
                return (float) ($detail->aux_subtotal ?? 0);
            });

            $sale->status = $payload['status'] ?? $sale->status;
            $sale->amount_rate = $amountRate;
            $sale->aux_total = round($auxTotal, 2);
            $sale->aux_debt = round($auxTotal, 2);
            $sale->total = round($auxTotal * $amountRate, 2);
            $sale->debt = round($auxTotal * $amountRate, 2);
            $sale->save();

            return $this->getSaleEditData((int) $sale->id);
        });
    }

    public function getRateData(
        int $page,
        int $length,
        ?string $searchValue = null,
        ?string $status = null
    ): LengthAwarePaginator {
        $query = rate::query()
            ->select([
                'id',
                'usd_rate',
                'is_active',
                'created_at',
            ])
            ->orderByDesc('id');

        if (! empty($searchValue)) {
            $normalizedSearch = strtolower(trim($searchValue));

            $query->where(function ($subQuery) use ($searchValue): void {
                $subQuery->where('usd_rate', 'like', "%{$searchValue}%");
            });

            if (in_array($normalizedSearch, ['activa', 'activo', 'activas', 'activos'], true)) {
                $query->where('is_active', 1);
            }

            if (in_array($normalizedSearch, ['inactiva', 'inactivo', 'inactivas', 'inactivos'], true)) {
                $query->where('is_active', 0);
            }
        }

        if ($status !== null && $status !== '') {
            $query->where('is_active', (int) ((string) $status === '1'));
        }

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

        $paginator->getCollection()->transform(function (rate $rateItem): array {
            return [
                'id' => $rateItem->id,
                'usd_rate' => number_format((float) $rateItem->usd_rate, 4, ',', '.'),
                'is_active' => (int) $rateItem->is_active,
                'created_at_formatted' => $rateItem->created_at?->format('d/m/Y H:i'),
            ];
        });

        return $paginator;
    }

    /**
     * Recalcula las deudas de ventas para un usuario y una tasa específica (por fecha).
     * Si no se pasa fecha, usa la tasa activa. Si no se pasa user_id, recalcula para todos.
     */
    public function setNewRateToDebtSales($user_id = null, $pay_date = null): void
    {
        try {
            DB::transaction(function () use ($user_id, $pay_date): void {
                // Buscar la tasa por fecha si se pasa pay_date
                $rateQuery = rate::query();
                if ($pay_date) {
                    $rateQuery->whereDate('created_at', '<=', $pay_date);
                } else {
                    $rateQuery->where('is_active', 1);
                }
                $activeRate = $rateQuery->orderByDesc('created_at')->lockForUpdate()->first();

                if (! $activeRate) {
                    return;
                }

                $activeRateValue = (float) $activeRate->usd_rate;
                if ($activeRateValue <= 0) {
                    return;
                }

                $salesQuery = Sale::query()
                    ->with(['saleDetails:id,sale_id,aux_unit_price,aux_subtotal,unit_price,subtotal'])
                    ->where('debt', '>', 0);
                if ($user_id) {
                    $salesQuery->where('user_id', $user_id);
                }
                $salesWithDebt = $salesQuery->lockForUpdate()->get();

                foreach ($salesWithDebt as $sale) {
                    $newTotal = is_numeric($sale->aux_total)
                        ? round((float) $sale->aux_total * $activeRateValue, 2)
                        : (float) $sale->total;

                    $newDebt = is_numeric($sale->aux_debt)
                        ? round((float) $sale->aux_debt * $activeRateValue, 2)
                        : (float) $sale->debt;

                    $sale->total = $newTotal;
                    $sale->debt = $newDebt;
                    $sale->amount_rate = $activeRateValue;
                    $sale->save();

                    foreach ($sale->saleDetails as $saleDetail) {
                        $newUnitPrice = is_numeric($saleDetail->aux_unit_price)
                            ? round((float) $saleDetail->aux_unit_price * $activeRateValue, 2)
                            : (float) $saleDetail->unit_price;

                        $newSubtotal = is_numeric($saleDetail->aux_subtotal)
                            ? round((float) $saleDetail->aux_subtotal * $activeRateValue, 2)
                            : (float) $saleDetail->subtotal;

                        $saleDetail->unit_price = $newUnitPrice;
                        $saleDetail->subtotal = $newSubtotal;
                        $saleDetail->save();
                    }
                }
            });
        } catch (Throwable $exception) {
            Log::error('Error al actualizar la tasa en ventas con deuda', [
                'message' => $exception->getMessage(),
            ]);
            throw $exception;
        }
    }

    public function getNewSaleIdentifier(int $companyId): int
    {
        if ($companyId <= 0) {
            return 1;
        }

        $lastSale = Sale::query()
            ->where('company_id', $companyId)
            ->whereNotNull('sale_identifier')
            ->latest('sale_identifier')
            ->lockForUpdate()
            ->first(['sale_identifier']);

        return $lastSale ? ((int) $lastSale->sale_identifier + 1) : 1;
    }
}
