<?php

namespace App\Services;

use App\Exceptions\InsufficientStockException;
use App\Exceptions\StockException;
use App\Models\Order;
use App\Models\Product;
use App\Models\Setting;
use App\Models\StockReservation;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;

class InventoryService
{
    /**
     * Default reservation TTL in seconds (15 minutes).
     * Configurable via Settings: stock_reservation_ttl_seconds
     */
    protected int $defaultReservationTtl = 900;

    public function checkAvailability(int $productId, int $quantity): bool
    {
        $product = Product::find($productId);

        if (!$product) {
            return false;
        }

        // Get available stock (total stock minus pending reservations)
        $availableStock = $this->getAvailableStock($productId);

        return $availableStock >= $quantity;
    }

    /**
     * Get available stock (total stock minus pending reservations)
     */
    public function getAvailableStock(int $productId): int
    {
        $product = Product::find($productId);

        if (!$product) {
            return 0;
        }

        // Get total reserved quantity for pending reservations
        $reservedQuantity = StockReservation::where('product_id', $productId)
            ->pending()
            ->sum('quantity');

        // Use current_stock (calculated from movements) instead of legacy stock field
        $currentStock = $product->current_stock;
        return max(0, $currentStock - $reservedQuantity);
    }

    /**
     * Reserve stock with database lock to prevent race conditions
     *
     * @param int $productId
     * @param int $quantity
     * @param int|null $ttl Reservation TTL in seconds (default: 5 minutes)
     * @param int|null $orderId Optional order ID to link reservation
     * @return string Reservation token
     * @throws InsufficientStockException
     */
    public function reserve(int $productId, int $quantity, ?int $ttl = null, ?int $orderId = null): string
    {
        if ($ttl === null) {
            $configured = Setting::get('stock_reservation_ttl_seconds');
            $ttl = $configured !== '' && $configured !== null ? (int) $configured : $this->defaultReservationTtl;
            $ttl = max(60, min(3600, $ttl)); // Clamp between 1 min and 60 min
        }

        return DB::transaction(function () use ($productId, $quantity, $ttl, $orderId) {
            // Lock the product row for update to prevent race conditions
            $product = Product::lockForUpdate()->find($productId);

            if (!$product) {
                throw new StockException("Product not found: {$productId}");
            }

            // Calculate available stock (total - pending reservations)
            $reservedQuantity = StockReservation::where('product_id', $productId)
                ->pending()
                ->sum('quantity');

            // Use current_stock (calculated from movements) instead of legacy stock field
            $currentStock = $product->current_stock;
            $availableStock = max(0, $currentStock - $reservedQuantity);

            if ($availableStock < $quantity) {
                throw new InsufficientStockException(
                    $product->name,
                    $quantity,
                    $availableStock
                );
            }

            // Create reservation
            $reservation = StockReservation::create([
                'product_id' => $productId,
                'order_id' => $orderId,
                'quantity' => $quantity,
                'reservation_token' => Str::random(64),
                'expires_at' => now()->addSeconds($ttl),
                'status' => 'pending',
            ]);

            Log::info('Stock reserved', [
                'product_id' => $productId,
                'quantity' => $quantity,
                'reservation_token' => $reservation->reservation_token,
                'expires_at' => $reservation->expires_at,
            ]);

            return $reservation->reservation_token;
        });
    }

    /**
     * Confirm reservation and decrement stock
     *
     * @param string $reservationToken
     * @return bool
     * @throws StockException
     */
    public function confirmReservation(string $reservationToken): bool
    {
        return DB::transaction(function () use ($reservationToken) {
            $reservation = StockReservation::where('reservation_token', $reservationToken)
                ->where('status', 'pending')
                ->lockForUpdate()
                ->first();

            if (!$reservation) {
                throw new StockException("Reservation not found or already processed: {$reservationToken}");
            }

            if ($reservation->isExpired()) {
                $reservation->update(['status' => 'expired']);
                throw new StockException("Reservation expired: {$reservationToken}");
            }

            // Lock product and decrement stock
            $product = Product::lockForUpdate()->find($reservation->product_id);

            // Use current_stock (calculated from movements) instead of legacy stock field
            $currentStock = $product->current_stock;
            
            if (!$product || $currentStock < $reservation->quantity) {
                $reservation->update(['status' => 'released']);
                throw new InsufficientStockException(
                    $product->name ?? 'Unknown',
                    $reservation->quantity,
                    $currentStock ?? 0
                );
            }

            // Create stock movement for sale (this will update current_stock via recalculateStock)
            $notes = $reservation->order_id !== null
                ? "Order #{$reservation->order_id}"
                : "Reservation confirmed: {$reservationToken}";
            if ($product->isVariant()) {
                \App\Models\VariantStockMovement::create([
                    'product_id' => $product->id,
                    'type' => \App\Models\VariantStockMovement::TYPE_SALE,
                    'quantity' => -$reservation->quantity,
                    'notes' => $notes,
                    'created_by' => 1, // System user
                ]);
            } else {
                \App\Models\StockMovement::create([
                    'product_id' => $product->id,
                    'type' => \App\Models\StockMovement::TYPE_SALE,
                    'quantity' => -$reservation->quantity,
                    'notes' => $notes,
                    'created_by' => 1, // System user
                ]);
            }
            
            // Recalculate stock
            $product->recalculateStock();

            // Mark reservation as confirmed
            $reservation->update(['status' => 'confirmed']);

            Log::info('Stock reservation confirmed and decremented', [
                'reservation_token' => $reservationToken,
                'product_id' => $reservation->product_id,
                'quantity' => $reservation->quantity,
            ]);

            return true;
        });
    }

    /**
     * Release reservation (cancel it)
     *
     * @param string $reservationToken
     * @return bool
     */
    public function release(string $reservationToken): bool
    {
        $reservation = StockReservation::where('reservation_token', $reservationToken)
            ->where('status', 'pending')
            ->first();

        if (!$reservation) {
            return false;
        }

        $reservation->update(['status' => 'released']);

        Log::info('Stock reservation released', [
            'reservation_token' => $reservationToken,
            'product_id' => $reservation->product_id,
            'quantity' => $reservation->quantity,
        ]);

        return true;
    }

    /**
     * Release reservation by order ID
     *
     * @param int $orderId
     * @return int Number of reservations released
     */
    public function releaseByOrderId(int $orderId, bool $cancelAwaitingPayment = false): int
    {
        $count = StockReservation::where('order_id', $orderId)
            ->where('status', 'pending')
            ->update(['status' => 'released']);

        if ($count > 0) {
            Log::info('Stock reservations released by order ID', [
                'order_id' => $orderId,
                'count' => $count,
            ]);
        }

        if ($cancelAwaitingPayment) {
            $this->cancelAwaitingPaymentOrder($orderId, 'reservation_released');
        }

        return $count;
    }

    /**
     * Decrement stock directly (with lock for race condition prevention)
     * Use this for direct stock decrements without reservations
     *
     * @param int $productId
     * @param int $quantity
     * @return bool
     * @throws InsufficientStockException
     */
    public function decrement(int $productId, int $quantity): bool
    {
        return DB::transaction(function () use ($productId, $quantity) {
            // Lock product row
            $product = Product::lockForUpdate()->find($productId);

            if (!$product) {
                throw new StockException("Product not found: {$productId}");
            }

            // Check available stock (considering pending reservations)
            $availableStock = $this->getAvailableStock($productId);

            if ($availableStock < $quantity) {
                throw new InsufficientStockException(
                    $product->name,
                    $quantity,
                    $availableStock
                );
            }

            // Use stock movements instead of direct decrement
            $currentStock = $product->current_stock;
            
            if ($currentStock < $quantity) {
                throw new InsufficientStockException(
                    $product->name,
                    $quantity,
                    $currentStock
                );
            }
            
            // Create stock movement
            \App\Models\StockMovement::create([
                'product_id' => $productId,
                'type' => \App\Models\StockMovement::TYPE_SALE,
                'quantity' => -$quantity,
                'notes' => 'Direct stock decrement',
                'created_by' => 1, // System user
            ]);
            
            // Recalculate stock
            $product->recalculateStock();
            $affected = 1;

            if ($affected > 0) {
                Log::info('Inventory decremented', [
                    'product_id' => $productId,
                    'quantity' => $quantity,
                ]);

                return true;
            }

            throw new InsufficientStockException(
                $product->name,
                $quantity,
                $product->current_stock
            );
        });
    }

    /**
     * Increment stock
     *
     * @param int $productId
     * @param int $quantity
     * @return void
     */
    public function increment(int $productId, int $quantity): void
    {
        // Use stock movements instead of direct increment
        $product = Product::find($productId);
        
        if ($product) {
            \App\Models\StockMovement::create([
                'product_id' => $productId,
                'type' => \App\Models\StockMovement::TYPE_RESTOCK,
                'quantity' => $quantity,
                'notes' => 'Direct stock increment',
                'created_by' => 1, // System user
            ]);
            
            // Recalculate stock
            $product->recalculateStock();
        }

        Log::info('Inventory incremented', [
            'product_id' => $productId,
            'quantity' => $quantity,
        ]);
    }

    /**
     * Clean up expired reservations
     *
     * @return int Number of expired reservations cleaned up
     */
    public function cleanupExpiredReservations(): int
    {
        $expiredOrderIds = StockReservation::expired()
            ->whereNotNull('order_id')
            ->pluck('order_id')
            ->unique()
            ->filter()
            ->values()
            ->all();

        $count = StockReservation::expired()
            ->update(['status' => 'expired']);

        if ($count > 0) {
            Log::info('Expired stock reservations cleaned up', [
                'count' => $count,
            ]);
        }

        foreach ($expiredOrderIds as $orderId) {
            $this->cancelAwaitingPaymentOrder((int) $orderId, 'reservation_expired');
        }

        return $count;
    }

    /**
     * Cancel an awaiting payment order and mark payment as failed.
     */
    public function cancelAwaitingPaymentOrder(int $orderId, string $reason = 'system'): bool
    {
        $updated = DB::transaction(function () use ($orderId, $reason) {
            $order = Order::where('id', $orderId)
                ->where('status', Order::STATUS_AWAITING_PAYMENT)
                ->where('payment_method', 'online')
                ->whereIn('payment_status', [Order::PAYMENT_PENDING, Order::PAYMENT_FAILED])
                ->lockForUpdate()
                ->first();

            if (!$order) {
                return 0;
            }

            // Guard: do not cancel if gateway already captured or is in authorization pipeline.
            if (!empty($order->razorpay_payment_id)) {
                $gatewayState = app(RazorpayService::class)->resolvePaymentState($order->razorpay_payment_id);
                $status = $gatewayState['status'] ?? RazorpayService::PAYMENT_STATUS_UNKNOWN;

                if ($status === RazorpayService::PAYMENT_STATUS_CAPTURED) {
                    $order->update([
                        'payment_status' => Order::PAYMENT_PAID,
                        'status' => Order::STATUS_PLACED,
                        'placed_at' => $order->placed_at ?? now(),
                    ]);

                    Log::warning('Awaiting payment auto-cancel skipped: gateway already captured', [
                        'order_id' => $orderId,
                        'reason' => $reason,
                    ]);

                    return 0;
                }

                if ($status === RazorpayService::PAYMENT_STATUS_AUTHORIZED) {
                    Log::info('Awaiting payment auto-cancel skipped: gateway authorization in progress', [
                        'order_id' => $orderId,
                        'reason' => $reason,
                    ]);

                    return 0;
                }
            }

            return Order::whereKey($order->id)->update([
                'status' => Order::STATUS_CANCELLED,
                'payment_status' => Order::PAYMENT_FAILED,
            ]);
        });

        if ($updated > 0) {
            Log::info('Awaiting payment order auto-cancelled', [
                'order_id' => $orderId,
                'reason' => $reason,
            ]);

            return true;
        }

        return false;
    }
}
