<?php
declare(strict_types=1);

namespace App\Modules\Sales\Services;

use App\Core\App;
use App\Core\EventBus;
use App\Core\Logger;
use App\Modules\Inventory\Services\StockService;
use App\Modules\Inventory\Services\BatchService;
use App\Modules\Inventory\Services\BomService;
use App\Modules\Inventory\Services\CreditLimitService;
use App\Modules\Accounting\Services\MultiCurrencyService;
use App\Modules\Payments\Services\PaymentService;

final class SaleService
{
    /**
     * Create a complete sale.
     *
     * @param array $header  Keys: warehouse_id, customer_type, member_id, player_id, guest_name, guest_phone,
     *                              discount_amount, discount_reason, payment_method, notes
     * @param array $lines   Each: ['item_id' => ?int, 'package_id' => ?int, 'quantity' => string, 'unit_price' => string,
     *                               'item_name_ar' => string, 'cost_price' => ?string, 'tax_amount' => ?string]
     * @return array ['success' => bool, 'sale_id' => int, 'invoice_number' => string, ...]
     */
    public static function createSale(array $header, array $lines): array
    {
        $db = App::getInstance()->db();
        $employee = App::getInstance()->currentEmployee();

        if (empty($lines)) {
            return ['success' => false, 'error' => 'يجب إضافة صنف واحد على الأقل'];
        }

        $warehouseId  = (int) ($header['warehouse_id'] ?? 0);
        $customerType = (string) ($header['customer_type'] ?? 'guest');
        $memberId     = ($header['member_id'] ?? null) ? (int) $header['member_id'] : null;
        $playerId     = ($header['player_id'] ?? null) ? (int) $header['player_id'] : null;

        if ($warehouseId <= 0) {
            return ['success' => false, 'error' => 'يجب تحديد المخزن'];
        }

        $invoiceNumber = InvoiceNumberGenerator::nextInvoiceNumber();
        $discountAmount = (string) ($header['discount_amount'] ?? '0.00');
        $paymentMethod = $header['payment_method'] ?? 'cash';

        // Calculate totals
        $subtotal = '0.00';
        $taxTotal = '0.00';
        foreach ($lines as $line) {
            $lineTotal = bcmul((string) $line['quantity'], (string) $line['unit_price'], 2);
            $subtotal = bcadd($subtotal, $lineTotal, 2);
            $taxTotal = bcadd($taxTotal, (string) ($line['tax_amount'] ?? '0.00'), 2);
        }

        $totalAmount = bcsub(bcadd($subtotal, $taxTotal, 2), $discountAmount, 2);
        if (bccomp($totalAmount, '0.00', 2) <= 0) {
            return ['success' => false, 'error' => 'إجمالي المبيعة يجب أن يكون أكبر من صفر'];
        }

        // Credit limit check for members
        if ($memberId) {
            $creditCheck = CreditLimitService::checkCustomerCanTransact($memberId, $totalAmount);
            if (!$creditCheck['allowed']) {
                return ['success' => false, 'error' => $creditCheck['message']];
            }
        }

        // Multi-currency: resolve exchange rate if non-base currency
        $currencyCode = $header['currency_code'] ?? null;
        $exchangeRate = '1.000000';
        if ($currencyCode && $currencyCode !== 'EGP') {
            $rateInfo = MultiCurrencyService::getExchangeRate($currencyCode);
            $exchangeRate = $rateInfo['mid_rate'];
        }

        $db->beginTransaction();
        try {
            $saleData = [
                'invoice_number'  => $invoiceNumber,
                'warehouse_id'    => $warehouseId,
                'customer_type'   => $customerType,
                'member_id'       => $memberId,
                'player_id'       => $playerId,
                'guest_name'      => $header['guest_name'] ?? null,
                'guest_phone'     => $header['guest_phone'] ?? null,
                'subtotal'        => $subtotal,
                'discount_amount' => $discountAmount,
                'discount_reason' => $header['discount_reason'] ?? null,
                'tax_amount'      => $taxTotal,
                'total_amount'    => $totalAmount,
                'amount_paid'     => $totalAmount,
                'amount_refunded' => '0.00',
                'payment_method'  => $paymentMethod,
                'status'          => 'completed',
                'sold_by'         => $employee ? (int) $employee->id : null,
                'sale_date'       => date('Y-m-d'),
                'notes'           => $header['notes'] ?? null,
                'created_by'      => $employee ? (int) $employee->id : null,
                'created_at'      => date('Y-m-d H:i:s'),
                'updated_at'      => date('Y-m-d H:i:s'),
            ];

            if ($currencyCode) {
                $saleData['currency_code'] = $currencyCode;
                $saleData['exchange_rate'] = $exchangeRate;
            }
            if (!empty($header['representative_id'])) {
                $saleData['representative_id'] = (int) $header['representative_id'];
            }

            $saleId = $db->insert('sales', $saleData);

            // Process each line item
            foreach ($lines as $line) {
                $lineQty   = (string) $line['quantity'];
                $linePrice = (string) $line['unit_price'];
                $lineTotal = bcmul($lineQty, $linePrice, 2);
                $lineTax   = (string) ($line['tax_amount'] ?? '0.00');

                $batchId = null;
                $itemId = isset($line['item_id']) ? (int) $line['item_id'] : null;

                // Deduct stock for items (not packages — packages are expanded below)
                if ($itemId) {
                    $bom = BomService::getBomForItem($itemId);

                    if ($bom) {
                        // Item has active BOM — deduct components instead
                        $bomResult = BomService::deductComponents((int) $bom['id'], $lineQty, $warehouseId);
                        if (!$bomResult['success']) {
                            throw new \RuntimeException($bomResult['error']);
                        }
                    } else {
                        $item = $db->selectOne("SELECT `tracking_type` FROM `inventory_items` WHERE `id` = ?", [$itemId]);

                        // FEFO for expiry items
                        if ($item && $item['tracking_type'] === 'expiry') {
                            $allocations = BatchService::allocateBatch($itemId, $warehouseId, $lineQty);
                            foreach ($allocations as $alloc) {
                                StockService::moveStock([
                                    'item_id'        => $itemId,
                                    'warehouse_id'   => $warehouseId,
                                    'movement_type'  => 'sale_out',
                                    'direction'      => 'out',
                                    'quantity'       => $alloc['quantity'],
                                    'batch_id'       => $alloc['batch_id'],
                                    'reference_type' => 'sales',
                                    'reference_id'   => $saleId,
                                ]);
                                $batchId = $alloc['batch_id'];
                            }
                        } else {
                            StockService::moveStock([
                                'item_id'        => $itemId,
                                'warehouse_id'   => $warehouseId,
                                'movement_type'  => 'sale_out',
                                'direction'      => 'out',
                                'quantity'       => $lineQty,
                                'reference_type' => 'sales',
                                'reference_id'   => $saleId,
                            ]);
                        }
                    }
                }

                // If it's a package, expand and deduct component items
                $packageId = isset($line['package_id']) ? (int) $line['package_id'] : null;
                if ($packageId) {
                    $components = $db->select(
                        "SELECT pi.`item_id`, pi.`quantity`, i.`tracking_type`
                         FROM `package_items` pi
                         JOIN `inventory_items` i ON i.`id` = pi.`item_id`
                         WHERE pi.`package_id` = ?",
                        [$packageId]
                    );
                    foreach ($components as $comp) {
                        $compQty = bcmul((string) $comp['quantity'], $lineQty, 3);
                        if ($comp['tracking_type'] === 'expiry') {
                            $allocs = BatchService::allocateBatch((int) $comp['item_id'], $warehouseId, $compQty);
                            foreach ($allocs as $a) {
                                StockService::moveStock([
                                    'item_id'        => (int) $comp['item_id'],
                                    'warehouse_id'   => $warehouseId,
                                    'movement_type'  => 'sale_out',
                                    'direction'      => 'out',
                                    'quantity'       => $a['quantity'],
                                    'batch_id'       => $a['batch_id'],
                                    'reference_type' => 'sales',
                                    'reference_id'   => $saleId,
                                ]);
                            }
                        } else {
                            StockService::moveStock([
                                'item_id'        => (int) $comp['item_id'],
                                'warehouse_id'   => $warehouseId,
                                'movement_type'  => 'sale_out',
                                'direction'      => 'out',
                                'quantity'       => $compQty,
                                'reference_type' => 'sales',
                                'reference_id'   => $saleId,
                            ]);
                        }
                    }
                }

                $db->insert('sale_items', [
                    'sale_id'         => $saleId,
                    'item_id'         => $itemId,
                    'package_id'      => $packageId,
                    'item_name_ar'    => $line['item_name_ar'],
                    'quantity'        => $lineQty,
                    'unit_price'      => $linePrice,
                    'discount_amount' => '0.00',
                    'tax_amount'      => $lineTax,
                    'line_total'      => bcadd($lineTotal, $lineTax, 2),
                    'cost_price'      => $line['cost_price'] ?? null,
                    'batch_id'        => $batchId,
                ]);
            }

            // Process payment
            $paymentResult = self::processPayment($saleId, $customerType, $memberId, $playerId, $totalAmount, $paymentMethod, $invoiceNumber, $header);

            if ($paymentResult && isset($paymentResult['payment_id'])) {
                $db->update('sales', [
                    'payment_id' => (int) $paymentResult['payment_id'],
                    'updated_at' => date('Y-m-d H:i:s'),
                ], '`id` = ?', [$saleId]);
            }

            $db->commit();
        } catch (\Throwable $e) {
            $db->rollBack();
            Logger::error("Sale failed: " . $e->getMessage());
            return ['success' => false, 'error' => 'فشل إنشاء المبيعة: ' . $e->getMessage()];
        }

        EventBus::dispatch('sale.completed', [
            'sale_id'        => $saleId,
            'sale_number'    => $invoiceNumber,
            'invoice_number' => $invoiceNumber,
            'member_id'      => $memberId,
            'customer_type'  => $customerType,
            'total_amount'   => $totalAmount,
        ]);

        Logger::info("Sale #{$saleId} ({$invoiceNumber}) completed — total: {$totalAmount}");

        return [
            'success'        => true,
            'sale_id'        => $saleId,
            'invoice_number' => $invoiceNumber,
            'total_amount'   => $totalAmount,
        ];
    }

    /**
     * Void a sale — reverses stock but does not refund payment.
     */
    public static function voidSale(int $saleId, string $reason): array
    {
        $db = App::getInstance()->db();
        $employee = App::getInstance()->currentEmployee();

        $sale = $db->selectOne("SELECT * FROM `sales` WHERE `id` = ?", [$saleId]);
        if (!$sale) {
            return ['success' => false, 'error' => 'المبيعة غير موجودة'];
        }
        if ($sale['status'] === 'voided') {
            return ['success' => false, 'error' => 'المبيعة ملغاة بالفعل'];
        }

        $db->beginTransaction();
        try {
            // Return stock for each line item
            $items = $db->select("SELECT * FROM `sale_items` WHERE `sale_id` = ?", [$saleId]);
            foreach ($items as $item) {
                if ($item['item_id']) {
                    StockService::moveStock([
                        'item_id'        => (int) $item['item_id'],
                        'warehouse_id'   => (int) $sale['warehouse_id'],
                        'movement_type'  => 'return_in',
                        'direction'      => 'in',
                        'quantity'       => (string) $item['quantity'],
                        'reference_type' => 'sales',
                        'reference_id'   => $saleId,
                        'notes'          => 'إلغاء مبيعة — ' . $reason,
                    ]);
                }
            }

            $db->update('sales', [
                'status'     => 'voided',
                'notes'      => ($sale['notes'] ? $sale['notes'] . "\n" : '') . 'إلغاء: ' . $reason,
                'updated_at' => date('Y-m-d H:i:s'),
                'updated_by' => $employee ? (int) $employee->id : null,
            ], '`id` = ?', [$saleId]);

            // Void the payment if exists
            if ($sale['payment_id']) {
                PaymentService::voidPayment((int) $sale['payment_id'], 'إلغاء مبيعة ' . $sale['invoice_number']);
            }

            $db->commit();
        } catch (\Throwable $e) {
            $db->rollBack();
            return ['success' => false, 'error' => 'فشل الإلغاء: ' . $e->getMessage()];
        }

        EventBus::dispatch('sale.voided', [
            'sale_id'        => $saleId,
            'member_id'      => $sale['member_id'] ? (int) $sale['member_id'] : null,
            'total_amount'   => (string) $sale['total_amount'],
            'invoice_number' => $sale['invoice_number'],
            'reason'         => $reason,
        ]);

        return ['success' => true];
    }

    private static function processPayment(int $saleId, string $customerType, ?int $memberId, ?int $playerId, string $amount, string $method, string $invoiceNumber, array $header): ?array
    {
        if ($customerType === 'member' && $memberId) {
            $result = PaymentService::processPayment([
                'member_id'           => $memberId,
                'amount'              => $amount,
                'payment_type'        => 'inventory_sale',
                'payment_method'      => $method,
                'related_entity_type' => 'sales',
                'related_entity_id'   => $saleId,
                'description'         => 'مبيعات مخزون — فاتورة ' . $invoiceNumber,
            ]);
            return $result['success'] ? $result : null;
        }

        if ($customerType === 'player' && $playerId) {
            // Players don't have member_id — use guest payment flow with player reference
            $result = InventoryPaymentService::processGuestPayment([
                'amount'              => $amount,
                'payment_method'      => $method,
                'guest_name'          => 'لاعب #' . $playerId,
                'related_entity_type' => 'sales',
                'related_entity_id'   => $saleId,
                'description'         => 'مبيعات مخزون (لاعب) — فاتورة ' . $invoiceNumber,
            ]);
            return $result['success'] ? $result : null;
        }

        // Guest — use InventoryPaymentService
        $result = InventoryPaymentService::processGuestPayment([
            'amount'              => $amount,
            'payment_method'      => $method,
            'guest_name'          => $header['guest_name'] ?? 'زائر',
            'related_entity_type' => 'sales',
            'related_entity_id'   => $saleId,
            'description'         => 'مبيعات مخزون (زائر) — فاتورة ' . $invoiceNumber,
        ]);
        return $result['success'] ? $result : null;
    }
}
