<?php

namespace App\Domain\POS\Services;

use App\Domain\Financial\Models\CashSession;
use App\Domain\Financial\Services\CashSessionService;
use App\Domain\Financial\Services\InvoiceService;
use App\Domain\Financial\Services\PaymentService;
use App\Domain\Financial\Services\WalletService;
use App\Domain\Inventory\Enums\MovementType;
use App\Domain\Inventory\Models\Kit;
use App\Domain\Inventory\Models\Product;
use App\Domain\Inventory\Models\Warehouse;
use App\Domain\Inventory\Services\InventoryService;
use App\Domain\Shared\Services\PlatformFeeService;
use App\Domain\Shared\Services\SettingsService;
use App\Domain\Participant\Models\Participant;
use App\Domain\POS\Enums\POSItemType;
use App\Domain\POS\Enums\POSPaymentMethod;
use App\Domain\POS\Enums\POSPaymentStatus;
use App\Domain\POS\Events\POSTransactionCompleted;
use App\Domain\POS\Models\POSTransaction;
use App\Domain\Pricing\Services\PriceResult;
use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Training\Models\Enrollment;
use App\Models\User;
use Illuminate\Support\Facades\DB;

class POSService
{
    public function __construct(
        private InvoiceService $invoiceService,
        private PaymentService $paymentService,
        private WalletService $walletService,
        private CashSessionService $cashSessionService,
        private PlatformFeeService $platformFeeService,
        private InventoryService $inventoryService,
        private SettingsService $settings,
    ) {}

    /**
     * Process a full POS transaction (10-step flow, single DB::transaction).
     *
     * @param array $cartItems Each item: ['item_type', 'item_id', 'item_name_ar', 'quantity', 'unit_price', 'discount_amount', 'line_total', 'pricing_snapshot', 'metadata']
     * @param User $cashier The user processing the transaction
     * @param int $branchId The branch where the sale occurs
     * @param string $paymentMethod One of: cash, card, wallet, split
     * @param Participant|null $participant Optional participant (required for wallet/enrollment)
     * @param string|null $couponCode Applied coupon code
     * @param array|null $splitPayments For split: [['method' => 'cash', 'amount' => 5000, 'reference' => null], ...]
     * @param string|null $notes Transaction notes
     * @return POSTransaction
     */
    public function processTransaction(
        array $cartItems,
        User $cashier,
        int $branchId,
        string $paymentMethod,
        ?Participant $participant = null,
        ?string $couponCode = null,
        ?array $splitPayments = null,
        ?string $notes = null,
        ?int $depositAmount = null,
    ): POSTransaction {
        // Guard: POS session must be open
        $cashSession = $this->cashSessionService->getOpenSession($cashier);
        if (!$cashSession) {
            throw new DomainException('يجب فتح وردية أولاً');
        }

        // Guard: cart must not be empty
        if (empty($cartItems)) {
            throw new DomainException('السلة فارغة');
        }

        // Guard: participant suspended or blacklisted
        if ($participant) {
            $status = $participant->status instanceof \BackedEnum
                ? $participant->status->value
                : (string) $participant->status;

            if (in_array($status, ['suspended', 'blacklisted'])) {
                throw new DomainException('المشترك معلق — لا يمكن إجراء عمليات شراء جديدة');
            }
        }

        return DB::transaction(function () use ($cartItems, $cashier, $branchId, $paymentMethod, $participant, $couponCode, $splitPayments, $notes, $cashSession, $depositAmount) {
            // Steps 2-3: Items already priced by caller (pricing engine runs in Livewire/controller layer)
            $subtotal = 0;
            $totalDiscount = 0;

            foreach ($cartItems as $item) {
                $subtotal += (int) $item['line_total'];
                $totalDiscount += (int) ($item['discount_amount'] ?? 0);
            }

            // Step 5: Calculate totals
            $taxAmount = 0; // Tax configuration per academy — to be implemented
            $customerPays = $this->platformFeeService->customerPays();
            $serviceFeeAmount = $customerPays ? $this->platformFeeService->calculate($subtotal) : 0;
            $totalAmount = $subtotal + $taxAmount + $serviceFeeAmount;

            // Step 6-7: Validate payment
            $paymentMethodEnum = POSPaymentMethod::from($paymentMethod);
            $isDeposit = $depositAmount !== null && $depositAmount > 0 && $depositAmount < $totalAmount;
            $amountToPayNow = $isDeposit ? $depositAmount : $totalAmount;
            $this->validatePayment($paymentMethodEnum, $amountToPayNow, $participant, $splitPayments);

            // Step 8: Commit — create POS transaction
            $receiptNumber = $this->generateReceiptNumber($branchId);
            $paymentStatus = $isDeposit ? POSPaymentStatus::PartiallyPaid : POSPaymentStatus::Completed;

            $posTransaction = POSTransaction::create([
                'academy_id' => $cashSession->academy_id,
                'branch_id' => $branchId,
                'cash_session_id' => $cashSession->id,
                'participant_id' => $participant?->id,
                'receipt_number' => $receiptNumber,
                'subtotal' => $subtotal,
                'discount_amount' => $totalDiscount,
                'tax_amount' => $taxAmount,
                'service_fee_amount' => $serviceFeeAmount,
                'total_amount' => $totalAmount,
                'payment_method' => $paymentMethodEnum->value,
                'payment_status' => $paymentStatus->value,
                'coupon_code' => $couponCode,
                'notes' => $isDeposit ? ($notes ? $notes . ' | ' : '') . 'دفع مقدم (عربون)' : $notes,
                'processed_by' => $cashier->id,
                'processed_at' => now(),
                'metadata' => $isDeposit ? ['deposit_amount' => $depositAmount, 'remaining' => $totalAmount - $depositAmount] : [],
            ]);

            // Create POS transaction items
            foreach ($cartItems as $item) {
                $posTransaction->items()->create([
                    'item_type' => $item['item_type'],
                    'item_id' => $item['item_id'] ?? null,
                    'item_name_ar' => $item['item_name_ar'],
                    'quantity' => $item['quantity'] ?? 1,
                    'unit_price' => (int) $item['unit_price'],
                    'discount_amount' => (int) ($item['discount_amount'] ?? 0),
                    'line_total' => (int) $item['line_total'],
                    'pricing_snapshot' => $item['pricing_snapshot'] ?? [],
                    'metadata' => $item['metadata'] ?? [],
                ]);
            }

            // Create split payment records (if split method)
            if ($paymentMethodEnum === POSPaymentMethod::Split && $splitPayments) {
                foreach ($splitPayments as $sp) {
                    $posTransaction->splitPayments()->create([
                        'payment_method' => $sp['method'],
                        'amount' => (int) $sp['amount'],
                        'reference' => $sp['reference'] ?? null,
                    ]);
                }
            }

            // Create invoice + items
            $invoice = $this->invoiceService->create([
                'academy_id' => $cashSession->academy_id,
                'branch_id' => $branchId,
                'billable_type' => $participant ? $participant->getMorphClass() : null,
                'billable_id' => $participant?->id,
                'number' => $this->invoiceService->generateNumber($cashSession->academy_id),
                'total_amount' => $totalAmount,
                'subtotal_amount' => $subtotal,
                'discount_amount' => $totalDiscount,
                'tax_amount' => $taxAmount,
                'service_fee_amount' => $serviceFeeAmount,
                'contact_name' => $participant?->person?->name_ar ?? 'عميل عابر',
            ], $this->buildInvoiceItems($cartItems), $cashier);

            // Link invoice to POS transaction
            $posTransaction->update(['invoice_id' => $invoice->id]);

            // Record payment(s) on the invoice (deposit pays only the deposit amount)
            if ($paymentMethodEnum === POSPaymentMethod::Split && $splitPayments) {
                foreach ($splitPayments as $sp) {
                    $this->recordSinglePayment($invoice, $sp['method'], (int) $sp['amount'], $cashier, $participant, $branchId);
                }
            } else {
                $this->recordSinglePayment($invoice, $paymentMethod, $amountToPayNow, $cashier, $participant, $branchId);
            }

            // Handle enrollment for program items + inventory deduction for tracked products
            foreach ($cartItems as $item) {
                $itemType = $item['item_type'] instanceof POSItemType
                    ? $item['item_type']
                    : POSItemType::from($item['item_type']);

                if ($itemType === POSItemType::Program && $participant && isset($item['metadata']['program_id'])) {
                    $this->createEnrollmentIfNeeded(
                        $participant,
                        (int) $item['metadata']['program_id'],
                        isset($item['metadata']['group_id']) ? (int) $item['metadata']['group_id'] : null
                    );
                }

                // Inventory movement for tracked products
                if ($itemType === POSItemType::Product && isset($item['item_id'])) {
                    $this->deductInventoryIfTracked(
                        (int) $item['item_id'],
                        (int) ($item['quantity'] ?? 1),
                        $branchId,
                        $posTransaction,
                        $cashier
                    );
                }
            }

            // Update cash session totals
            $cashSession->increment('transactions_count');
            if (in_array($paymentMethod, ['cash', 'split'])) {
                $cashAmount = $paymentMethod === 'cash'
                    ? $amountToPayNow
                    : (int) collect($splitPayments)->where('method', 'cash')->sum('amount');

                if ($cashAmount > 0) {
                    $cashSession->increment('total_cash_in', $cashAmount);
                }
            }

            // Step 10: Dispatch events (fires after commit via ShouldDispatchAfterCommit)
            POSTransactionCompleted::dispatch($posTransaction, $cashier);

            return $posTransaction->fresh(['items', 'splitPayments']);
        });
    }

    /**
     * Validate payment method-specific constraints.
     */
    private function validatePayment(POSPaymentMethod $method, int $total, ?Participant $participant, ?array $splitPayments): void
    {
        if ($method === POSPaymentMethod::Wallet) {
            if (!$participant) {
                throw new DomainException('الدفع بالمحفظة يتطلب تحديد المشترك');
            }
            $wallet = $this->walletService->getOrCreateWallet($participant, $participant->academy_id);
            $availableBalance = $wallet->balance;
            if ($availableBalance < $total) {
                throw new DomainException('رصيد المحفظة غير كافٍ');
            }
        }

        if ($method === POSPaymentMethod::Split) {
            if (!$splitPayments || empty($splitPayments)) {
                throw new DomainException('يجب تحديد طرق الدفع المقسمة');
            }
            $splitTotal = (int) collect($splitPayments)->sum('amount');
            if ($splitTotal < $total) {
                throw new DomainException('المبلغ المدفوع أقل من الإجمالي');
            }
        }
    }

    /**
     * Record a single payment against the invoice (handles wallet deduction).
     */
    private function recordSinglePayment($invoice, string $method, int $amount, User $cashier, ?Participant $participant, int $branchId): void
    {
        // Wallet deduction
        if ($method === 'wallet' && $participant) {
            $wallet = $this->walletService->getOrCreateWallet($participant, $participant->academy_id);
            $this->walletService->withdraw($wallet, $amount, 'دفع فاتورة: ' . $invoice->number, $invoice, $cashier);
        }

        $this->paymentService->recordPayment([
            'academy_id' => $invoice->academy_id,
            // The branch of the sale. Invoices carry no branch_id, so reading
            // it off the invoice always produced null and left POS payments
            // unattributed to any branch.
            'branch_id' => $branchId,
            'invoice_id' => $invoice->id,
            'amount' => $amount,
            'method' => $method,
            'direction' => 'inbound',
            'currency' => 'EGP',
            'payment_date' => now()->toDateString(),
        ], $cashier);
    }

    /**
     * Build invoice items array from cart items.
     */
    private function buildInvoiceItems(array $cartItems): array
    {
        return array_map(function (array $item) {
            $line = [
                'description' => $item['item_name_ar'],
                'quantity' => $item['quantity'] ?? 1,
                'unit_price' => (int) $item['unit_price'],
                'discount_amount' => (int) ($item['discount_amount'] ?? 0),
                'tax_amount' => 0,
            ];

            // Point the line at what was actually sold. Without this every POS
            // sale lands with a null itemable_type, which reporting reads as a
            // programme subscription — so product sales inflated subscription
            // revenue, were missing from product revenue, and never counted
            // towards "has this participant bought the required product".
            $type = $item['item_type'] ?? null;
            $type = $type instanceof POSItemType ? $type : POSItemType::tryFrom((string) $type);
            $id = $item['item_id'] ?? null;

            if ($id && $type === POSItemType::Product) {
                $line['itemable_type'] = Product::class;
                $line['itemable_id'] = (int) $id;
            } elseif ($id && $type === POSItemType::Kit) {
                $line['itemable_type'] = Kit::class;
                $line['itemable_id'] = (int) $id;
            }

            return $line;
        }, $cartItems);
    }

    /**
     * Create enrollment if not already enrolled in the program.
     */
    private function createEnrollmentIfNeeded(Participant $participant, int $programId, ?int $groupId): void
    {
        $exists = Enrollment::where('participant_id', $participant->id)
            ->where('training_program_id', $programId)
            ->whereIn('status', ['active', 'pending'])
            ->exists();

        if ($exists) {
            throw new DomainException('مسجل بالفعل في هذا البرنامج');
        }

        Enrollment::create([
            'academy_id' => $participant->academy_id,
            'participant_id' => $participant->id,
            'training_program_id' => $programId,
            'training_group_id' => $groupId,
            'status' => 'active',
            'enrollment_date' => now()->toDateString(),
        ]);
    }

    /**
     * Deduct inventory for a tracked product after POS sale.
     * Finds the branch warehouse and creates a 'sale' movement.
     */
    private function deductInventoryIfTracked(int $productId, int $quantity, int $branchId, POSTransaction $posTransaction, User $cashier): void
    {
        if (!(bool) $this->settings->get('auto_inventory_deduction_on_sale', true)) {
            return;
        }

        $product = Product::find($productId);
        if (!$product || !$product->track_inventory) {
            return;
        }

        // Resolve warehouse for this branch (first active warehouse for the branch)
        $warehouse = Warehouse::where('branch_id', $branchId)
            ->where('is_active', true)
            ->first();

        if (!$warehouse) {
            // No warehouse configured for this branch — skip silently
            // (admin should configure warehouses for inventory-tracked branches)
            return;
        }

        $this->inventoryService->createMovement(
            product: $product,
            warehouse: $warehouse,
            type: MovementType::Sale,
            quantity: $quantity,
            actor: $cashier,
            unitCost: $product->cost_price,
            reference: $posTransaction,
            reason: "بيع نقطة بيع: {$posTransaction->receipt_number}",
        );
    }

    /**
     * Generate unique receipt number: RCP-{BRANCH}-{YYYYMMDD}-{SEQ}
     */
    private function generateReceiptNumber(int $branchId): string
    {
        $branchCode = str_pad($branchId, 3, '0', STR_PAD_LEFT);
        $dateCode = now()->format('Ymd');

        $seq = POSTransaction::withoutGlobalScopes()
            ->where('branch_id', $branchId)
            ->whereDate('processed_at', now()->toDateString())
            ->count() + 1;

        return "RCP-{$branchCode}-{$dateCode}-" . str_pad($seq, 4, '0', STR_PAD_LEFT);
    }
}
