<?php

namespace App\Services\Courier;

use App\Models\Courier;
use App\Models\CourierAuditLog;
use App\Models\CourierLedgerEntry;
use App\Models\CourierSettlement;
use App\Models\CourierSettlementItem;
use App\Models\OrderCourierShipment;
use Illuminate\Support\Facades\DB;

class CourierSettlementService
{
    public function __construct(
        private readonly CourierVoucherService $voucherService,
        private readonly CourierLedgerService $ledgerService
    ) {
    }

    public function create(array $data, ?int $adminId = null): CourierSettlement
    {
        return DB::transaction(function () use ($data, $adminId) {
            $courier = Courier::query()->lockForUpdate()->findOrFail($data['courier_id']);
            $shipmentIds = array_values(array_filter((array) ($data['shipment_ids'] ?? [])));
            $shipments = OrderCourierShipment::query()
                ->with('order')
                ->where('courier_id', $courier->id)
                ->whereIn('id', $shipmentIds)
                ->where(function ($query) {
                    $query->whereNull('settlement_status')
                        ->orWhereNotIn('settlement_status', ['settled', 'paid'])
                        ->orWhere('balance_amount', '>', 0);
                })
                ->lockForUpdate()
                ->get();

            if ($shipments->isEmpty()) {
                throw new \InvalidArgumentException('Please select at least one courier parcel for settlement.');
            }

            $totals = $this->shipmentTotals($shipments);
            $netReceived = isset($data['net_received_amount']) && $data['net_received_amount'] !== ''
                ? (float) $data['net_received_amount']
                : $totals['net_receivable'];

            $settlement = CourierSettlement::create([
                'courier_id' => $courier->id,
                'voucher_no' => $this->voucherService->nextSettlementVoucher(),
                'settlement_date' => $data['settlement_date'] ?? now()->toDateString(),
                'reference_no' => $data['reference_no'] ?? null,
                'payment_method' => $data['payment_method'] ?? null,
                'settlement_account' => $data['settlement_account'] ?? $courier->default_settlement_account_name,
                'total_cod_amount' => $totals['cod_amount'],
                'delivery_charge_deduction' => $totals['delivery_charge'],
                'cod_charge_deduction' => $totals['cod_charge'],
                'return_charge_deduction' => $totals['return_charge'],
                'adjustment_amount' => (float) ($data['adjustment_amount'] ?? 0),
                'net_received_amount' => $netReceived,
                'status' => 'posted',
                'note' => $data['note'] ?? null,
                'payload' => [
                    'shipment_ids' => $shipmentIds,
                    'manual_entry' => true,
                ],
                'created_by_admin_id' => $adminId,
            ]);

            $remaining = $netReceived;

            foreach ($shipments as $shipment) {
                $order = $shipment->order;
                $cod = (float) ($shipment->cod_collected_amount ?: $shipment->collectable_amount ?: 0);
                $deliveryCharge = (float) ($shipment->actual_delivery_cost ?: 0);
                $codCharge = (float) ($shipment->actual_cod_charge ?: 0);
                $returnCharge = (float) ($shipment->actual_return_cost ?: 0);
                $netReceivable = max($cod - $deliveryCharge - $codCharge - $returnCharge, 0);
                $itemReceived = min(max($remaining, 0), $netReceivable);
                $remaining -= $itemReceived;
                $previousSettled = (float) ($shipment->settled_amount ?: 0);
                $totalSettled = $previousSettled + $itemReceived;
                $status = $totalSettled + 0.0001 >= $netReceivable ? 'settled' : ($totalSettled > 0 ? 'partially_settled' : 'unsettled');

                CourierSettlementItem::create([
                    'courier_settlement_id' => $settlement->id,
                    'courier_id' => $courier->id,
                    'order_courier_shipment_id' => $shipment->id,
                    'order_id' => $order?->id,
                    'parcel_id' => $shipment->courier_consignment_id ?: $shipment->courier_parcel_id ?: $shipment->courier_tracking_id,
                    'invoice_no' => $order?->id,
                    'cod_amount' => $cod,
                    'delivery_charge' => $deliveryCharge,
                    'cod_charge' => $codCharge,
                    'return_charge' => $returnCharge,
                    'adjustment_amount' => 0,
                    'net_receivable' => $netReceivable,
                    'net_received' => $itemReceived,
                    'previous_settlement_status' => $shipment->settlement_status,
                    'settlement_status' => $status,
                ]);

                if ($order) {
                    $this->ledgerService->recordShipmentAccounting(
                        order: $order,
                        shipment: $shipment,
                        payload: ['settlement_id' => $settlement->id, 'voucher_no' => $settlement->voucher_no],
                        adminId: $adminId,
                        source: 'manual_settlement'
                    );
                }

                $shipment->forceFill([
                    'cod_collection_status' => $shipment->cod_collection_status ?: 'collected',
                    'cod_collected_amount' => $shipment->cod_collected_amount ?: $cod,
                    'cod_collected_at' => $shipment->cod_collected_at ?: $settlement->settlement_date,
                    'settlement_status' => $status,
                    'settled_amount' => $totalSettled,
                    'settlement_paid_at' => $status === 'settled' ? $settlement->settlement_date : ($shipment->settlement_paid_at ?: $settlement->settlement_date),
                    'settlement_reference' => $settlement->reference_no ?: $settlement->voucher_no,
                    'settlement_payload' => array_filter([
                        'manual_settlement_id' => $settlement->id,
                        'voucher_no' => $settlement->voucher_no,
                        'reference_no' => $settlement->reference_no,
                    ]),
                    'courier_accounting_synced_at' => $shipment->courier_accounting_synced_at ?: now(),
                    'courier_accounting_note' => 'Courier settlement posted by voucher ' . $settlement->voucher_no,
                ])->save();

                $this->ledgerService->refreshShipmentMoneyStatus($shipment);
            }

            $this->ledgerService->postSettlementReceipt($settlement, $adminId);

            CourierAuditLog::create([
                'courier_id' => $courier->id,
                'courier_settlement_id' => $settlement->id,
                'action' => 'settlement_created',
                'auditable_type' => CourierSettlement::class,
                'auditable_id' => $settlement->id,
                'new_values' => $settlement->load('items')->toArray(),
                'source' => 'admin',
                'created_by_admin_id' => $adminId,
            ]);

            return $settlement->fresh(['courier', 'items.shipment.order', 'ledgerEntries']);
        });
    }

    public function reverse(CourierSettlement $settlement, string $reason, ?int $adminId = null): CourierSettlement
    {
        return DB::transaction(function () use ($settlement, $reason, $adminId) {
            $settlement = CourierSettlement::query()
                ->with(['items.shipment', 'ledgerEntries'])
                ->lockForUpdate()
                ->findOrFail($settlement->id);

            if ($settlement->reversed_at) {
                return $settlement;
            }

            foreach ($settlement->ledgerEntries as $entry) {
                $this->ledgerService->reverseEntry($entry, $reason, $adminId);
            }

            foreach ($settlement->items as $item) {
                $shipment = $item->shipment;

                if (! $shipment) {
                    continue;
                }

                $settledAmount = max((float) ($shipment->settled_amount ?: 0) - (float) $item->net_received, 0);
                $shipment->forceFill([
                    'settled_amount' => $settledAmount,
                    'settlement_status' => $item->previous_settlement_status ?: ($settledAmount > 0 ? 'partially_settled' : 'unsettled'),
                    'settlement_paid_at' => $settledAmount > 0 ? $shipment->settlement_paid_at : null,
                    'courier_accounting_note' => 'Courier settlement reversed: ' . $settlement->voucher_no,
                ])->save();
                $this->ledgerService->refreshShipmentMoneyStatus($shipment);
            }

            $settlement->forceFill([
                'status' => 'reversed',
                'reversed_at' => now(),
                'reverse_reason' => $reason,
                'reversed_by_admin_id' => $adminId,
            ])->save();

            CourierAuditLog::create([
                'courier_id' => $settlement->courier_id,
                'courier_settlement_id' => $settlement->id,
                'action' => 'settlement_reversed',
                'auditable_type' => CourierSettlement::class,
                'auditable_id' => $settlement->id,
                'new_values' => $settlement->fresh()->toArray(),
                'source' => 'admin',
                'created_by_admin_id' => $adminId,
            ]);

            return $settlement->fresh(['courier', 'items.shipment.order', 'ledgerEntries']);
        });
    }

    private function shipmentTotals($shipments): array
    {
        $totals = [
            'cod_amount' => 0,
            'delivery_charge' => 0,
            'cod_charge' => 0,
            'return_charge' => 0,
            'net_receivable' => 0,
        ];

        foreach ($shipments as $shipment) {
            $cod = (float) ($shipment->cod_collected_amount ?: $shipment->collectable_amount ?: 0);
            $deliveryCharge = (float) ($shipment->actual_delivery_cost ?: 0);
            $codCharge = (float) ($shipment->actual_cod_charge ?: 0);
            $returnCharge = (float) ($shipment->actual_return_cost ?: 0);

            $totals['cod_amount'] += $cod;
            $totals['delivery_charge'] += $deliveryCharge;
            $totals['cod_charge'] += $codCharge;
            $totals['return_charge'] += $returnCharge;
            $totals['net_receivable'] += max($cod - $deliveryCharge - $codCharge - $returnCharge, 0);
        }

        return $totals;
    }
}
