<?php

declare(strict_types=1);

namespace App\Services;

use App\Repositories\SalesReturnRepository;
use Exception;
use PDO;
use RuntimeException;

final class SalesReturnService
{
    public function __construct(private PDO $pdo, private SalesReturnRepository $returns)
    {
    }

    public function loadForm(int $returnId, int $sourceInvoiceId): array
    {
        $isEdit = $returnId > 0;
        $return = null;
        if ($isEdit) {
            $return = $this->returns->findById($returnId);
            if (!$return) {
                throw new RuntimeException('Not found');
            }
            if (strtoupper((string)$return['status']) !== 'DRAFT') {
                throw new RuntimeException('لا يمكن تعديل مرتجع بيع مرحّل أو ملغى.');
            }
            $sourceInvoiceId = (int)$return['sales_invoice_id'];
        }
        if ($sourceInvoiceId <= 0) {
            throw new RuntimeException('invoice_id required');
        }

        $invoice = $this->returns->fetchSourceInvoice($sourceInvoiceId);
        if (!$invoice) {
            throw new RuntimeException('Invoice not found');
        }
        if (strtoupper((string)$invoice['status']) !== 'POSTED') {
            throw new RuntimeException('يجب أن تكون فاتورة البيع مُرحّلة لإنشاء مرتجع.');
        }

        $customerName = (string)($invoice['customer_name'] ?? '');
        $customerCode = (string)($invoice['customer_code'] ?? '');
        $isCashCustomer = ($customerCode === 'POS-CASH') || ((function_exists('mb_strpos') ? mb_strpos($customerName, 'نقدي') : strpos($customerName, 'نقدي')) !== false);

        $draftMap = [];
        if ($isEdit) {
            foreach ($this->returns->fetchDraftItems($returnId) as $dl) {
                $iid = (int)($dl['item_id'] ?? 0);
                if ($iid <= 0) continue;
                $draftMap[$iid] = [
                    'qty' => (float)($dl['qty'] ?? 0),
                    'unit_price' => (float)($dl['unit_price'] ?? 0),
                ];
            }
        }

        $lines = [];
        $returnedMap = $this->returns->fetchPostedReturnedQtyMap($sourceInvoiceId);
        foreach ($this->returns->fetchInvoiceLines($sourceInvoiceId) as $ln) {
            $lineId = (int)$ln['id'];
            $soldQty = (float)$ln['qty'];
            $alreadyReturned = (float)($returnedMap[$lineId] ?? 0.0);
            $maxQty = max(0.0, $soldQty - $alreadyReturned);
            $curQty = isset($draftMap[(int)$ln['item_id']]) ? (float)$draftMap[(int)$ln['item_id']]['qty'] : 0.0;
            if ($curQty > $maxQty) $maxQty = $curQty;
            $unitPrice = isset($draftMap[(int)$ln['item_id']]) && $draftMap[(int)$ln['item_id']]['unit_price'] > 0 ? (float)$draftMap[(int)$ln['item_id']]['unit_price'] : (float)$ln['price'];
            $lines[] = [
                'sales_invoice_item_id' => $lineId,
                'item_id' => (int)$ln['item_id'],
                'item_name' => (string)$ln['item_name'],
                'sold_qty' => $soldQty,
                'returned_qty' => $alreadyReturned,
                'max_qty' => $maxQty,
                'qty' => $curQty,
                'unit_price' => $unitPrice,
            ];
        }

        if (!$isEdit) {
            $return = [
                'return_date' => date('Y-m-d'),
                'settlement_type' => $isCashCustomer ? 'cash_refund' : 'credit_note',
                'cashbox_id' => null,
                'reason' => '',
                'notes' => '',
                'subtotal' => 0,
                'total_amount' => 0,
            ];
        }

        return [
            'return' => $return,
            'invoice' => $invoice,
            'lines' => $lines,
            'cashboxes' => $this->returns->fetchCashboxes(),
            'isEdit' => $isEdit,
            'errors' => [],
            'isCashCustomer' => $isCashCustomer,
        ];
    }

    public function applyPostedInput(array $form, array $input): array
    {
        $return = $form['return'];
        $isCashCustomer = (bool)$form['isCashCustomer'];
        $lines = $form['lines'];
        $errors = [];

        $return['return_date'] = trim((string)($input['return_date'] ?? '')) ?: date('Y-m-d');
        $return['settlement_type'] = trim((string)($input['settlement_type'] ?? ($isCashCustomer ? 'cash_refund' : 'credit_note')));
        $return['cashbox_id'] = (int)($input['cashbox_id'] ?? 0) ?: null;
        $return['reason'] = trim((string)($input['reason'] ?? ''));
        $return['notes'] = trim((string)($input['notes'] ?? ''));
        if ($isCashCustomer) $return['settlement_type'] = 'cash_refund';
        if (!in_array($return['settlement_type'], ['cash_refund', 'credit_note'], true)) {
            $return['settlement_type'] = $isCashCustomer ? 'cash_refund' : 'credit_note';
        }
        if ($isCashCustomer && !$return['cashbox_id']) {
            $errors[] = 'للعميل النقدي يجب اختيار خزنة لرد المبلغ.';
        }
        if ($return['settlement_type'] === 'cash_refund' && !$return['cashbox_id']) {
            $errors[] = 'يجب اختيار خزنة عند رد نقدي.';
        }

        $postedLines = [];
        $subtotal = 0.0;
        $qtys = $input['qty'] ?? [];
        $prices = $input['unit_price'] ?? [];
        foreach ($lines as $idx => &$ln) {
            $qty = (float)($qtys[$idx] ?? 0);
            $price = (float)($prices[$idx] ?? $ln['unit_price']);
            $ln['qty'] = $qty;
            $ln['unit_price'] = $price;
            if ($qty <= 0) continue;
            if ($qty > ((float)$ln['max_qty'] + 0.0001)) {
                $errors[] = 'كمية المرتجع أكبر من المتاح للصنف: ' . $ln['item_name'];
                continue;
            }
            if ($price < 0) {
                $errors[] = 'السعر غير صحيح للصنف: ' . $ln['item_name'];
                continue;
            }
            $lineTotal = round($qty * $price, 2);
            $subtotal += $lineTotal;
            $postedLines[] = [
                'sales_invoice_item_id' => (int)$ln['sales_invoice_item_id'],
                'item_id' => (int)$ln['item_id'],
                'qty' => $qty,
                'unit_price' => $price,
                'line_total' => $lineTotal,
            ];
        }
        unset($ln);

        $return['subtotal'] = round($subtotal, 2);
        $return['total_amount'] = round($subtotal, 2);
        if (!$postedLines) {
            $errors[] = 'أدخل كمية مرتجع على الأقل لصنف واحد.';
        }

        $form['return'] = $return;
        $form['lines'] = $lines;
        $form['errors'] = $errors;
        $form['postedLines'] = $postedLines;
        return $form;
    }

    public function saveDraft(int $returnId, array $form, int $currentUserId): int
    {
        if ($form['errors']) {
            throw new RuntimeException('validation');
        }

        $return = $form['return'];
        $invoice = $form['invoice'];
        $postedLines = $form['postedLines'] ?? [];
        $isEdit = (bool)$form['isEdit'];

        $this->pdo->beginTransaction();
        try {
            $data = [
                'return_no' => $this->returns->generateReturnNo(),
                'return_date' => $return['return_date'] . ' 00:00:00',
                'sales_invoice_id' => (int)$invoice['id'],
                'party_id' => (int)($invoice['party_id'] ?: $invoice['customer_id']),
                'warehouse_id' => (int)$invoice['warehouse_id'],
                'settlement_type' => $return['settlement_type'],
                'cashbox_id' => $return['cashbox_id'],
                'subtotal' => $return['subtotal'],
                'total_amount' => $return['total_amount'],
                'reason' => $return['reason'] !== '' ? $return['reason'] : null,
                'notes' => $return['notes'] !== '' ? $return['notes'] : null,
            ];

            if (!$isEdit) {
                $returnId = $this->returns->createDraft($data, $currentUserId);
                audit_log('CREATE_SALES_RET', 'sales_returns', $returnId, ['return_no' => $data['return_no'], 'invoice_id' => (int)$invoice['id']]);
            } else {
                $this->returns->updateDraft($returnId, $data);
                audit_log('UPDATE_SALES_RET', 'sales_returns', $returnId);
            }
            $this->returns->replaceItems($returnId, $postedLines);
            $this->pdo->commit();
            return $returnId;
        } catch (\Throwable $e) {
            if ($this->pdo->inTransaction()) $this->pdo->rollBack();
            throw $e;
        }
    }

    public function post(int $id, int $currentUserId): void
    {
        $this->pdo->beginTransaction();
        try {
            $ret = $this->returns->lockReturn($id);
            if (!$ret) throw new Exception('المستند غير موجود.');
            if (strtoupper((string)$ret['status']) !== 'DRAFT') throw new Exception('لا يمكن ترحيل مرتجع غير مسودة.');

            $inv = $this->returns->fetchSourceInvoice((int)$ret['sales_invoice_id']);
            if (!$inv) throw new Exception('الفاتورة الأصلية غير موجودة.');
            if (strtoupper((string)$inv['status']) !== 'POSTED') throw new Exception('لا يمكن إنشاء مرتجع من فاتورة غير مرحلة.');

            $customerName = (string)($inv['customer_name'] ?? '');
            $customerCode = (string)($inv['customer_code'] ?? '');
            $isCashCustomer = ($customerCode === 'POS-CASH') || ((function_exists('mb_strpos') ? mb_strpos($customerName, 'نقدي') : strpos($customerName, 'نقدي')) !== false);

            $lines = $this->returns->fetchPostingLines($id);
            if (!$lines) throw new Exception('لا توجد أصناف في المرتجع.');

            if ($isCashCustomer && (string)$ret['settlement_type'] !== 'cash_refund') {
                throw new Exception('العميل النقدي يجب أن يكون مرتجعه رد نقدي فقط.');
            }
            if ((string)$ret['settlement_type'] === 'cash_refund' && (int)($ret['cashbox_id'] ?? 0) <= 0) {
                throw new Exception('يجب اختيار خزنة عند الرد النقدي.');
            }

            $returned = $this->returns->fetchPostedReturnedQtyMap((int)$ret['sales_invoice_id'], $id);
            foreach ($lines as $ln) {
                $sourceLineId = (int)$ln['sales_invoice_item_id'];
                $soldQty = $this->returns->sourceLineQty($sourceLineId);
                if ($soldQty <= 0) throw new Exception('تعذر قراءة سطر الفاتورة الأصلي لأحد الأصناف.');
                $alreadyReturned = (float)($returned[$sourceLineId] ?? 0.0);
                $available = max(0.0, $soldQty - $alreadyReturned);
                $qty = (float)($ln['qty'] ?? 0);
                if ($qty <= 0) throw new Exception('توجد كمية غير صحيحة في أحد سطور المرتجع.');
                if ($qty > $available + 0.0001) {
                    $nm = (string)($ln['item_name'] ?? ('#'.(int)$ln['item_id']));
                    throw new Exception('كمية المرتجع أكبر من المتاح للصنف: ' . $nm);
                }
            }

            $returnDate = (string)($ret['return_date'] ?: date('Y-m-d H:i:s'));
            $returnDateOnly = substr($returnDate, 0, 10);
            $totalAmount = round((float)($ret['total_amount'] ?? 0), 2);
            if ($totalAmount <= 0) throw new Exception('إجمالي المرتجع غير صحيح.');

            if ((string)$ret['settlement_type'] === 'cash_refund') {
                $cashboxId = (int)$ret['cashbox_id'];
                $avail = cashbox_balance($this->pdo, $cashboxId, $returnDateOnly);
                if ($avail + 0.0001 < $totalAmount) {
                    throw new Exception('رصيد الخزنة غير كافٍ لرد المبلغ. المتاح: ' . format_money($avail));
                }
            }

            $this->returns->deletePostingEffects($id);
            foreach ($lines as $ln) {
                $itemId = (int)$ln['item_id'];
                $qty = (float)$ln['qty'];
                $cost = (float)($ln['item_cost'] ?? 0);

                $this->pdo->prepare("INSERT INTO stock_moves (ref_type, ref_id, warehouse_id, item_id, qty_in, qty_out, cost, user_id)
                       VALUES ('SALES_RET', ?, ?, ?, ?, 0, ?, ?)")
                    ->execute([$id, (int)$ret['warehouse_id'], $itemId, $qty, $cost, $currentUserId]);
                stock_balance_apply($this->pdo, (int)$ret['warehouse_id'], $itemId, $qty);
            }

            $docNo = (string)($ret['return_no'] ?: ('SR#'.$id));
            $notes = trim((string)($ret['notes'] ?? ''));
            $settlementType = (string)($ret['settlement_type'] ?? 'credit_note');
            $ledgerNote = $notes !== '' ? $notes : ($settlementType === 'cash_refund' ? 'مرتجع بيع مع رد نقدي' : 'مرتجع بيع كرصيد للعميل');
            party_add_ledger($this->pdo, (int)$ret['party_id'], 'SALES_RET', $id, $docNo, $returnDate, 0.0, $totalAmount, $ledgerNote);

            if ($settlementType === 'cash_refund') {
                $cashboxNote = $notes !== '' ? $notes : ('رد نقدي على مرتجع البيع ' . $docNo);
                cashbox_add_txn($this->pdo, (int)$ret['cashbox_id'], date('Y-m-d H:i:s'), 'out', $totalAmount, 'sales_return', $id, $cashboxNote, $currentUserId);
            }

            $this->returns->markPosted($id, $currentUserId);
            audit_log('POST_SALES_RET', 'sales_returns', $id, ['return_no'=>$docNo,'total_amount'=>$totalAmount,'settlement_type'=>$settlementType]);
            $this->pdo->commit();
        } catch (\Throwable $e) {
            if ($this->pdo->inTransaction()) $this->pdo->rollBack();
            throw $e;
        }
    }

    public function cancelDraft(int $id): void
    {
        $this->returns->markCanceledDraft($id);
        audit_log('CANCEL_SALES_RET', 'sales_returns', $id);
    }
}
