<?php

namespace App\Services;

use App\Models\StockTransfer;
use App\Models\VanClosing;
use App\Models\VanLoading;
use App\Models\VanSession;
use App\Models\Warehouse;
use App\Models\WarehouseStock;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use RuntimeException;

class VanSalesService
{
    public function __construct(
        private DocumentSequenceService $sequences,
        private InventoryService $inventory,
        private AuditLogService $audit,
        private BranchAccessService $branches,
    ) {}

    public function openSession(array $data): VanSession
    {
        return DB::transaction(function () use ($data) {
            $this->branches->assertWarehouse((int) $data['warehouse_id'], $data['branch_id'] ?? null, 'van-sales');
            $warehouse = Warehouse::whereKey($data['warehouse_id'])->where('type', 'van')->firstOrFail();
            if (VanSession::where('warehouse_id', $warehouse->id)->where('session_date', $data['session_date'])->where('status', 'open')->exists()) {
                throw new RuntimeException('This van already has an open session for the selected date.');
            }
            $session = VanSession::create([
                'session_no' => $this->sequences->getNextNumber('van_session') ?? throw new RuntimeException('Unable to generate van session number.'),
                'warehouse_id' => $warehouse->id, 'branch_id' => $data['branch_id'] ?? $warehouse->branch_id,
                'salesman_id' => $data['salesman_id'], 'driver_name' => $data['driver_name'] ?? null,
                'route' => $data['route'] ?? null, 'session_date' => $data['session_date'],
                'opening_odometer' => $data['opening_odometer'] ?? null, 'starting_cash' => $data['starting_cash'] ?? 0,
                'status' => 'open', 'started_at' => now(),
            ]);
            $this->audit->log('VAN_OPENED', 'van_sessions', $session->id, null, ['session_no' => $session->session_no]);
            return $session;
        });
    }

    public function requestLoading(VanSession $session, array $data, array $items): VanLoading
    {
        return DB::transaction(function () use ($session, $data, $items) {
            $this->branches->assertWarehouse((int) $session->warehouse_id, $session->branch_id, 'van-sales');
            $this->branches->assertWarehouse((int) $data['source_warehouse_id'], null, 'van-sales');
            if ($session->status !== 'open') throw new RuntimeException('Van session is not open.');
            if (!$items) throw new RuntimeException('Add at least one product to the loading request.');
            $loading = VanLoading::create([
                'loading_no' => $this->sequences->getNextNumber('van_loading') ?? throw new RuntimeException('Unable to generate loading number.'),
                'van_session_id' => $session->id, 'source_warehouse_id' => $data['source_warehouse_id'],
                'loading_date' => $data['loading_date'], 'status' => 'requested', 'notes' => $data['notes'] ?? null,
                'requested_by' => Auth::id(),
            ]);
            foreach ($items as $row) {
                $product = \App\Models\Product::findOrFail($row['product_id']);
                $unitId = (int) ($row['unit_id'] ?? $product->sales_unit_id ?? $product->base_unit_id);
                $base = bcmul((string) $row['quantity'], (string) $product->getConversionFactor($unitId), 4);
                $loading->items()->create(['product_id' => $product->id, 'unit_id' => $unitId, 'quantity' => $row['quantity'], 'base_quantity' => $base, 'unit_cost' => 0]);
            }
            $this->audit->log('VAN_LOADING_REQUESTED', 'van_loadings', $loading->id, null, ['loading_no' => $loading->loading_no]);
            return $loading->load('items');
        });
    }

    public function approveLoading(VanLoading $loading): VanLoading
    {
        return DB::transaction(function () use ($loading) {
            $loading = VanLoading::with(['items', 'session'])->lockForUpdate()->findOrFail($loading->id);
            $this->branches->assertWarehouse((int) $loading->session->warehouse_id, $loading->session->branch_id, 'van-sales');
            $this->branches->assertWarehouse((int) $loading->source_warehouse_id, null, 'van-sales');
            if ($loading->status !== 'requested' || $loading->session->status !== 'open') throw new RuntimeException('Only requested loadings for open sessions can be approved.');
            $transfer = StockTransfer::create([
                'transfer_no' => $this->sequences->getNextNumber('stock_transfer') ?? throw new RuntimeException('Unable to generate transfer number.'),
                'transfer_date' => $loading->loading_date, 'from_warehouse_id' => $loading->source_warehouse_id,
                'to_warehouse_id' => $loading->session->warehouse_id, 'notes' => 'Van loading '.$loading->loading_no,
                'status' => 'received', 'prepared_by' => $loading->requested_by, 'approved_by' => Auth::id(), 'received_by' => Auth::id(),
                'approved_at' => now(), 'received_at' => now(), 'total_cost' => 0,
            ]);
            $totalCost = '0.00';
            foreach ($loading->items as $item) {
                $cost = $this->inventory->inventoryCostService()->getAvgCost($item->product_id, $loading->source_warehouse_id);
                $lineCost = bcmul((string) $item->base_quantity, $cost, 4); $totalCost = bcadd($totalCost, $lineCost, 2);
                $item->update(['unit_cost' => $cost]);
                $transfer->items()->create(['product_id' => $item->product_id, 'unit_id' => $item->unit_id, 'qty' => $item->quantity, 'base_qty' => $item->base_quantity, 'unit_cost' => $cost, 'total_cost' => $lineCost]);
                $common = ['transaction_date' => $loading->loading_date, 'transaction_type' => 'VAN_LOAD', 'reference_type' => 'van_loadings', 'reference_id' => $loading->id, 'reference_number' => $loading->loading_no, 'product_id' => $item->product_id, 'unit_id' => $item->unit_id, 'unit_cost' => $cost];
                $this->inventory->stockOut($common + ['warehouse_id' => $loading->source_warehouse_id, 'qty_out' => $item->quantity, 'base_qty_out' => $item->base_quantity]);
                $this->inventory->stockIn($common + ['warehouse_id' => $loading->session->warehouse_id, 'qty_in' => $item->quantity, 'base_qty_in' => $item->base_quantity]);
            }
            $transfer->update(['total_cost' => $totalCost]);
            $loading->update(['stock_transfer_id' => $transfer->id, 'status' => 'approved', 'approved_by' => Auth::id(), 'approved_at' => now()]);
            $this->audit->log('VAN_LOADED', 'van_loadings', $loading->id, ['status' => 'requested'], ['status' => 'approved']);
            return $loading->fresh(['items.product', 'session.warehouse', 'transfer']);
        });
    }

    public function closeSession(VanSession $session, array $data): VanClosing
    {
        $this->branches->assertWarehouse((int) $session->warehouse_id, $session->branch_id, 'van-sales');
        return DB::transaction(function () use ($session, $data) {
            $session = VanSession::with('sales')->lockForUpdate()->findOrFail($session->id);
            if ($session->status !== 'open') throw new RuntimeException('Van session is already closed.');
            $cashSales = $session->sales->where('status', 'finalized')->where('payment_type', '!=', 'credit')->sum('paid_total');
            $creditSales = $session->sales->where('status', 'finalized')->sum('balance_total');
            $collections = (string) ($data['collections'] ?? 0); $expenses = (string) ($data['expenses'] ?? 0);
            $expected = bcsub(bcadd(bcadd((string) $session->starting_cash, (string) $cashSales, 2), $collections, 2), $expenses, 2);
            $actual = bcadd((string) $data['actual_cash'], '0', 2); $difference = bcsub($actual, $expected, 2);
            $closing = VanClosing::create([
                'van_session_id' => $session->id, 'cash_sales' => $cashSales, 'credit_sales' => $creditSales,
                'collections' => $collections, 'expenses' => $expenses, 'expected_cash' => $expected,
                'actual_cash' => $actual, 'cash_difference' => $difference,
                'stock_variances' => $data['stock_variances'] ?? null, 'notes' => $data['notes'] ?? null, 'created_by' => Auth::id(),
            ]);
            $session->update(['closing_odometer' => $data['closing_odometer'] ?? null, 'expected_cash' => $expected, 'actual_cash' => $actual, 'cash_difference' => $difference, 'status' => 'closed', 'closed_at' => now(), 'closed_by' => Auth::id()]);
            $this->audit->log('VAN_CLOSED', 'van_sessions', $session->id, ['status' => 'open'], ['status' => 'closed']);
            return $closing;
        });
    }
}
