<?php

namespace App\Services;

use App\Models\CashDrawerTransaction;
use App\Models\Order;
use App\Models\OrderItem;
use App\Models\Product;
use App\Models\SaleReturn;
use App\Models\Shift;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class ReturnService
{
    public function __construct(
        private readonly InventoryService $inventoryService,
        private readonly AuditLogService $auditLogService,
    ) {
    }

    public function create(User $user, Order $originalOrder, array $data): SaleReturn
    {
        return DB::transaction(function () use ($user, $originalOrder, $data) {
            $shift = $this->resolveShift($user, $originalOrder->branch_id);
            $restocked = (bool) ($data['restocked'] ?? true);
            $totalRefundAmount = 0.0;

            $saleReturn = SaleReturn::create([
                'company_id' => $originalOrder->company_id,
                'branch_id' => $originalOrder->branch_id,
                'original_order_id' => $originalOrder->id,
                'user_id' => $user->id,
                'customer_id' => $data['customer_id'] ?? $originalOrder->customer_id,
                'return_number' => $data['return_number'] ?? $this->generateReturnNumber(),
                'reason' => $data['reason'],
                'reason_notes' => $data['reason_notes'] ?? null,
                'status' => $data['status'] ?? 'completed',
                'refund_method' => $data['refund_method'] ?? 'cash',
                'total_refund_amount' => 0,
                'restocked' => $restocked,
                'device_id' => $data['device_id'] ?? null,
                'synced_at' => $data['device_id'] ?? null ? now() : null,
            ]);

            foreach ($data['items'] as $payload) {
                $orderItem = $originalOrder->items()->with('product')->findOrFail($payload['order_item_id']);
                $quantity = round((float) $payload['quantity'], 4);
                $alreadyReturned = (float) $orderItem->returned_quantity;
                $maxReturnable = round((float) $orderItem->quantity - $alreadyReturned, 4);

                if ($maxReturnable <= 0 || $quantity > $maxReturnable) {
                    throw ValidationException::withMessages([
                        'items' => ["Invalid return quantity for order item {$orderItem->id}."],
                    ]);
                }

                $refundAmount = $payload['refund_amount'] ?? round(
                    ((float) $orderItem->total_amount / max((float) $orderItem->quantity, 0.0001)) * $quantity,
                    4
                );

                $saleReturn->items()->create([
                    'order_item_id' => $orderItem->id,
                    'product_id' => $orderItem->product_id,
                    'variant_id' => $orderItem->variant_id,
                    'quantity' => $quantity,
                    'unit_price' => $orderItem->unit_price,
                    'refund_amount' => $refundAmount,
                    'condition' => $payload['condition'] ?? 'resellable',
                ]);

                $orderItem->forceFill([
                    'returned_quantity' => round($alreadyReturned + $quantity, 4),
                    'is_returned' => round($alreadyReturned + $quantity, 4) >= (float) $orderItem->quantity,
                ])->save();

                $totalRefundAmount += (float) $refundAmount;

                if ($restocked && (($payload['condition'] ?? 'resellable') === 'resellable')) {
                    /** @var Product $product */
                    $product = $orderItem->product()->firstOrFail();

                    $this->inventoryService->adjust(
                        user: $user,
                        branchId: $originalOrder->branch_id,
                        product: $product,
                        variantId: $orderItem->variant_id,
                        quantityDelta: $quantity,
                        type: 'return',
                        unitCost: (float) $orderItem->cost_price,
                        batchId: $orderItem->batch_id,
                        referenceType: SaleReturn::class,
                        referenceId: $saleReturn->id,
                        notes: "Return item {$orderItem->id}",
                        deviceId: $saleReturn->device_id,
                    );
                }
            }

            $saleReturn->forceFill([
                'total_refund_amount' => round($totalRefundAmount, 4),
            ])->save();

            $this->updateOriginalOrderStatus($originalOrder);
            $this->recordRefund($shift, $user, $originalOrder, $saleReturn);

            $shift->activities()->create([
                'user_id' => $user->id,
                'activity_type' => 'refund',
                'description' => "Return {$saleReturn->return_number} processed",
                'amount' => -1 * $saleReturn->total_refund_amount,
                'reference_type' => SaleReturn::class,
                'reference_id' => $saleReturn->id,
            ]);

            $this->auditLogService->log($user, 'return.created', $saleReturn, [], $saleReturn->toArray(), $saleReturn->device_id);

            return $saleReturn->load(['items', 'originalOrder', 'customer']);
        });
    }

    private function updateOriginalOrderStatus(Order $order): void
    {
        $order->refresh()->load('items');

        $allReturned = $order->items->every(
            fn (OrderItem $item) => (float) $item->returned_quantity >= (float) $item->quantity
        );

        $anyReturned = $order->items->contains(
            fn (OrderItem $item) => (float) $item->returned_quantity > 0
        );

        if ($allReturned) {
            $order->status = 'refunded';
        } elseif ($anyReturned) {
            $order->status = 'partially_refunded';
        }

        $order->save();
    }

    private function recordRefund(Shift $shift, User $user, Order $order, SaleReturn $saleReturn): void
    {
        if ($saleReturn->refund_method !== 'cash' || (float) $saleReturn->total_refund_amount <= 0) {
            return;
        }

        $runningBalance = (float) CashDrawerTransaction::query()
            ->where('shift_id', $shift->id)
            ->sum('amount');

        CashDrawerTransaction::create([
            'shift_id' => $shift->id,
            'cash_register_id' => $shift->cash_register_id,
            'user_id' => $user->id,
            'type' => 'refund',
            'amount' => -1 * (float) $saleReturn->total_refund_amount,
            'running_balance' => round($runningBalance - (float) $saleReturn->total_refund_amount, 2),
            'notes' => "Cash refund for order {$order->order_number}",
            'reference_type' => SaleReturn::class,
            'reference_id' => $saleReturn->id,
        ]);
    }

    private function resolveShift(User $user, int $branchId): Shift
    {
        $shift = Shift::query()
            ->where('branch_id', $branchId)
            ->where('user_id', $user->id)
            ->where('status', 'open')
            ->latest('opened_at')
            ->first();

        if ($shift === null) {
            throw ValidationException::withMessages([
                'shift_id' => ['An open shift is required before processing a return.'],
            ]);
        }

        return $shift;
    }

    private function generateReturnNumber(): string
    {
        return 'RTN-'.now()->format('YmdHis').'-'.str_pad((string) random_int(1, 9999), 4, '0', STR_PAD_LEFT);
    }
}
