<?php
/**
 * Cash/Bank Controller — Roznamcha (نقد وصولی / ادائیگی)
 * Records standalone payments/receipts via double-entry journals.
 */

class CashBankController {
    private PDO $db;
    private object $user;

    public function __construct(PDO $db, object $user) {
        $this->db = $db;
        $this->user = $user;
    }

    /** GET /cash-bank?date=2026-04-01 */
    public function index(array $query): void {
        $where = [
            "j.source_type IN ('cash_bank', 'expense')",
            "j.is_reversed = 0",
            "j.narration NOT LIKE 'REVERSAL of Journal #%'" 
        ];
        $params = [];

        if (!empty($query['date'])) {
            $where[] = 'j.journal_date = ?';
            $params[] = $query['date'];
        } else {
            if (!empty($query['from_date'])) {
                $where[] = 'j.journal_date >= ?';
                $params[] = $query['from_date'];
            }
            if (!empty($query['to_date'])) {
                $where[] = 'j.journal_date <= ?';
                $params[] = $query['to_date'];
            }
        }

        if (!empty($query['item_id'])) {
            $where[] = "j.source_id = ? AND j.source_type IN ('expense', 'commission_pay')";
            $params[] = (int)$query['item_id'];
        }

        $limit = isset($query['limit']) ? (int) $query['limit'] : 25;
        $offset = isset($query['offset']) ? (int) $query['offset'] : 0;
        $limit = max(1, min($limit, 100));
        $offset = max(0, $offset);

        $whereStr = implode(' AND ', $where);

        $summaryStmt = $this->db->prepare("
            SELECT 
                COUNT(*) AS total_records,
                COALESCE(SUM(j.total_amount), 0) AS total_amount,
                MIN(j.journal_date) AS first_date,
                MAX(j.journal_date) AS last_date
            FROM journals j
            WHERE {$whereStr}
        ");
        $summaryStmt->execute($params);
        $summary = $summaryStmt->fetch() ?: [
            'total_records' => 0,
            'total_amount' => 0,
            'first_date' => null,
            'last_date' => null
        ];

        $stmt = $this->db->prepare("
            SELECT j.id, j.journal_date, j.narration, j.total_amount, j.is_reversed,
                   u.full_name AS created_by_name
            FROM journals j
            LEFT JOIN users u ON j.created_by = u.id
            WHERE {$whereStr}
            ORDER BY j.journal_date DESC, j.id DESC
            LIMIT ? OFFSET ?
        ");
        $bindIndex = 1;
        foreach ($params as $param) {
            $stmt->bindValue($bindIndex++, $param);
        }
        $stmt->bindValue($bindIndex++, $limit, PDO::PARAM_INT);
        $stmt->bindValue($bindIndex, $offset, PDO::PARAM_INT);
        $stmt->execute();

        $vouchers = $stmt->fetchAll();
        $journalIds = array_map(fn($row) => (int) $row['id'], $vouchers);
        $entriesByJournal = [];

        if (!empty($journalIds)) {
            $placeholders = implode(',', array_fill(0, count($journalIds), '?'));
            $entryStmt = $this->db->prepare("
                SELECT je.*, a.name AS account_name
                FROM journal_entries je
                JOIN accounts a ON je.account_id = a.id
                WHERE je.journal_id IN ({$placeholders})
                ORDER BY je.journal_id ASC, je.id ASC
            ");
            $entryStmt->execute($journalIds);

            foreach ($entryStmt->fetchAll() as $entry) {
                $journalId = (int) $entry['journal_id'];
                if (!isset($entriesByJournal[$journalId])) {
                    $entriesByJournal[$journalId] = [];
                }
                $entriesByJournal[$journalId][] = $entry;
            }
        }

        foreach ($vouchers as &$voucher) {
            $voucherId = (int) $voucher['id'];
            $voucher['entries'] = $entriesByJournal[$voucherId] ?? [];
            $voucher['entry_type'] = $this->detectEntryType((string) ($voucher['narration'] ?? ''));
        }
        unset($voucher);

        $totalRecords = (int) ($summary['total_records'] ?? 0);
        $loadedCount = count($vouchers);

        // Fetch sale total
        $saleQuery = "SELECT COALESCE(SUM(net_receivable), 0) FROM sale_master WHERE is_reversed = 0";
        $saleParams = [];
        if (!empty($query['date'])) {
            $saleQuery .= " AND voucher_date = ?";
            $saleParams[] = $query['date'];
        } else {
            if (!empty($query['from_date'])) {
                $saleQuery .= " AND voucher_date >= ?";
                $saleParams[] = $query['from_date'];
            }
            if (!empty($query['to_date'])) {
                $saleQuery .= " AND voucher_date <= ?";
                $saleParams[] = $query['to_date'];
            }
        }
        if (!empty($query['item_id'])) {
            $saleQuery .= " AND item_id = ?";
            $saleParams[] = (int)$query['item_id'];
        }
        $saleStmt = $this->db->prepare($saleQuery);
        $saleStmt->execute($saleParams);
        $totalSale = (float)$saleStmt->fetchColumn();

        // Fetch purchase total
        $purchaseQuery = "SELECT COALESCE(SUM(total_gross_amount), 0) FROM purchase_master WHERE is_reversed = 0";
        $purchaseParams = [];
        if (!empty($query['date'])) {
            $purchaseQuery .= " AND voucher_date = ?";
            $purchaseParams[] = $query['date'];
        } else {
            if (!empty($query['from_date'])) {
                $purchaseQuery .= " AND voucher_date >= ?";
                $purchaseParams[] = $query['from_date'];
            }
            if (!empty($query['to_date'])) {
                $purchaseQuery .= " AND voucher_date <= ?";
                $purchaseParams[] = $query['to_date'];
            }
        }
        if (!empty($query['item_id'])) {
            $purchaseQuery .= " AND item_id = ?";
            $purchaseParams[] = (int)$query['item_id'];
        }
        $purchaseStmt = $this->db->prepare($purchaseQuery);
        $purchaseStmt->execute($purchaseParams);
        $totalPurchase = (float)$purchaseStmt->fetchColumn();

        // Fetch expense total (source_type = 'expense' journals)
        $expenseQuery = "
            SELECT COALESCE(SUM(j.total_amount), 0)
            FROM journals j
            WHERE j.source_type = 'expense' AND j.is_reversed = 0 AND j.narration NOT LIKE 'REVERSAL%'
        ";
        $expenseParams = [];
        if (!empty($query['date'])) {
            $expenseQuery .= " AND j.journal_date = ?";
            $expenseParams[] = $query['date'];
        } else {
            if (!empty($query['from_date'])) {
                $expenseQuery .= " AND j.journal_date >= ?";
                $expenseParams[] = $query['from_date'];
            }
            if (!empty($query['to_date'])) {
                $expenseQuery .= " AND j.journal_date <= ?";
                $expenseParams[] = $query['to_date'];
            }
        }
        $expenseStmt = $this->db->prepare($expenseQuery);
        $expenseStmt->execute($expenseParams);
        $totalExpense = (float)$expenseStmt->fetchColumn();

        // Fetch all cash/bank accounts and balances
        $bankSql = "
            SELECT 
                a.id, a.name, a.sub_type, a.opening_balance, a.balance_type,
                COALESCE(SUM(CASE WHEN j.id IS NOT NULL AND j.is_reversed = 0 AND j.narration NOT LIKE 'REVERSAL%' THEN je.debit ELSE 0 END), 0) as total_dr,
                COALESCE(SUM(CASE WHEN j.id IS NOT NULL AND j.is_reversed = 0 AND j.narration NOT LIKE 'REVERSAL%' THEN je.credit ELSE 0 END), 0) as total_cr
            FROM accounts a
            LEFT JOIN journal_entries je ON je.account_id = a.id
            LEFT JOIN journals j ON je.journal_id = j.id
            WHERE a.type = 'bank_cash' AND a.is_active = 1 AND a.name NOT LIKE 'capital (سرمایہ)%'
            GROUP BY a.id
            ORDER BY a.name ASC
        ";
        $bankStmt = $this->db->query($bankSql);
        $bankRows = $bankStmt->fetchAll();
        $bankBalances = [];
        $totalCashBank = 0;
        foreach ($bankRows as $row) {
            $opening = (float)$row['opening_balance'];
            if ($row['balance_type'] === 'Cr') $opening = -$opening;
            $balance = $opening + (float)$row['total_dr'] - (float)$row['total_cr'];
            $bankBalances[] = [
                'id' => (int)$row['id'],
                'name' => $row['name'],
                'sub_type' => $row['sub_type'],
                'balance' => $balance
            ];
            $totalCashBank += $balance;
        }

        Response::success([
            'items' => $vouchers,
            'summary' => [
                'total_records' => $totalRecords,
                'total_amount' => (float) ($summary['total_amount'] ?? 0),
                'first_date' => $summary['first_date'] ?? null,
                'last_date' => $summary['last_date'] ?? null
            ],
            'pagination' => [
                'limit' => $limit,
                'offset' => $offset,
                'loaded' => $loadedCount,
                'loaded_total' => min($offset + $loadedCount, $totalRecords),
                'has_more' => ($offset + $loadedCount) < $totalRecords
            ],
            'stats' => [
                'total_sale' => $totalSale,
                'total_purchase' => $totalPurchase,
                'total_expense' => $totalExpense,
                'banks' => $bankBalances,
                'total_cash_bank' => $totalCashBank
            ]
        ]);
    }

    /** POST /cash-bank */
    public function store(array $data): void {
        Validator::check($data, [
            'date'       => 'required|date',
            'amount'     => 'required|numeric|positive',
            'type'       => 'required|in:payment,receipt,transfer,direct_deposit,debit_adj,credit_adj,commission_pay,expense',
            'narration'  => 'nullable|string'
        ]);

        $type = $data['type'];
        $amount = floatval($data['amount']);
        $uuid = $data['uuid'] ?? UUID::generate();

        $existingId = UUID::checkIdempotency($this->db, 'journals', $uuid);
        if ($existingId) {
            Response::success(['id' => $existingId], 'واؤچر پہلے سے موجود ہے (Idempotency).');
            return;
        }

        $entries = [];
        $narration = "";
        $itemId = null;

        if ($type === 'transfer') {
            Validator::check($data, [
                'from_account_id' => 'required|integer',
                'to_account_id'   => 'required|integer'
            ]);

            $fromId = (int)$data['from_account_id'];
            $toId = (int)$data['to_account_id'];

            if ($fromId === $toId) {
                Response::error('پیسے ایک ہی اکاؤنٹ میں منتقل نہیں کیے جا سکتے۔', 400);
            }

            $fromName = $this->db->query("SELECT name FROM accounts WHERE id = {$fromId}")->fetchColumn();
            $toName = $this->db->query("SELECT name FROM accounts WHERE id = {$toId}")->fetchColumn();

            // Transfer: Dr Receiver (to), Cr Giver (from)
            $entries[] = ['account_id' => $toId, 'debit' => $amount, 'credit' => 0, 'narration' => "منتقلی (منجانب: {$fromName})"];
            $entries[] = ['account_id' => $fromId, 'debit' => 0, 'credit' => $amount, 'narration' => "منتقلی (بنام: {$toName})"];
            $narration = "منتقلی: {$fromName} سے {$toName}";

        } elseif ($type === 'direct_deposit') {
            Validator::check($data, [
                'account_id' => 'required|integer'
            ]);

            $accId = (int)$data['account_id'];
            $accountName = $this->db->query("SELECT name FROM accounts WHERE id = {$accId}")->fetchColumn();

            // Find system capital account
            $stmt = $this->db->prepare("SELECT id FROM accounts WHERE type = 'capital' AND is_system = 1 LIMIT 1");
            $stmt->execute();
            $capitalAcc = $stmt->fetch();

            if (!$capitalAcc) {
                Response::error('سرمایہ کاری کا اکاؤنٹ نہیں ملا۔', 500);
            }

            // Direct Deposit: Dr Selected Bank/Cash Account (destination), Cr Capital (source)
            $entries[] = ['account_id' => $accId, 'debit' => $amount, 'credit' => 0, 'narration' => "براہ راست جمع — {$accountName}"];
            $entries[] = ['account_id' => (int)$capitalAcc['id'], 'debit' => 0, 'credit' => $amount, 'narration' => "براہ راست جمع — سرمایہ"];
            $narration = "براہ راست جمع: {$accountName} میں رقم جمع کروائی";

        } elseif ($type === 'debit_adj' || $type === 'credit_adj') {
            Validator::check($data, [
                'party_id'   => 'required|integer'
            ]);

            $partyId = (int)$data['party_id'];
            $partyName = $this->db->query("SELECT name FROM accounts WHERE id = {$partyId}")->fetchColumn();

            // Find system expense account
            $expenseAccStmt = $this->db->prepare("SELECT id FROM accounts WHERE is_system = 1 AND sub_type = 'expense' LIMIT 1");
            $expenseAccStmt->execute();
            $expenseAccId = (int)$expenseAccStmt->fetchColumn();

            if (!$expenseAccId) {
                Response::error('نقصان / خرچہ کا سسٹم اکاؤنٹ نہیں ملا۔', 500);
            }

            if ($type === 'debit_adj') {
                // نام ڈالیں (Debit Party): Dr Party (increases Dr balance), Cr Expense (reduces net expense)
                $entries[] = ['account_id' => $partyId, 'debit' => $amount, 'credit' => 0, 'narration' => "نام ایڈجسٹمنٹ — {$partyName}"];
                $entries[] = ['account_id' => $expenseAccId, 'debit' => 0, 'credit' => $amount, 'narration' => "ایڈجسٹمنٹ آفسیٹ — {$partyName}"];
                $narration = "نام ایڈجسٹمنٹ: {$partyName} کے نام رقم ڈالی";
            } else {
                // جمع ڈالیں (Credit Party): Dr Expense (increases expense), Cr Party (reduces what they owe)
                $entries[] = ['account_id' => $expenseAccId, 'debit' => $amount, 'credit' => 0, 'narration' => "ایڈجسٹمنٹ خرچہ — {$partyName}"];
                $entries[] = ['account_id' => $partyId, 'debit' => 0, 'credit' => $amount, 'narration' => "جمع ایڈجسٹمنٹ — {$partyName}"];
                $narration = "جمع ایڈجسٹمنٹ: {$partyName} کے جمع رقم ڈالی";
            }

        } elseif ($type === 'commission_pay') {
            Validator::check($data, [
                'party_id'   => 'required|integer',
                'account_id' => 'required|integer',
                'item_id'    => 'required|integer'
            ]);

            $partyId = (int)$data['party_id'];
            $accountId = (int)$data['account_id'];
            $itemId = (int)$data['item_id'];

            $partyName = $this->db->query("SELECT name FROM accounts WHERE id = {$partyId}")->fetchColumn();
            $accountName = $this->db->query("SELECT name FROM accounts WHERE id = {$accountId}")->fetchColumn();
            $itemName = $this->db->query("SELECT name FROM items WHERE id = {$itemId}")->fetchColumn();

            // Find system commission account
            $commAccStmt = $this->db->prepare("SELECT id FROM accounts WHERE is_system = 1 AND sub_type = 'commission' LIMIT 1");
            $commAccStmt->execute();
            $commAccId = (int)$commAccStmt->fetchColumn();

            if (!$commAccId) {
                // Fallback to expense
                $expenseAccStmt = $this->db->prepare("SELECT id FROM accounts WHERE is_system = 1 AND sub_type = 'expense' LIMIT 1");
                $expenseAccStmt->execute();
                $commAccId = (int)$expenseAccStmt->fetchColumn();
            }

            if (!$commAccId) {
                Response::error('کمیشن کا سسٹم اکاؤنٹ نہیں ملا۔', 500);
            }

            // Entries:
            // 1. Dr Commission Account (Expense for item)
            $entries[] = ['account_id' => $commAccId, 'debit' => $amount, 'credit' => 0, 'narration' => "کمیشن خرچہ — {$itemName} — {$partyName}"];
            // 2. Cr Party (earnings/liability)
            $entries[] = ['account_id' => $partyId, 'debit' => 0, 'credit' => $amount, 'narration' => "کمیشن آمدنی — {$itemName}"];
            // 3. Dr Party (payment/liability settlement)
            $entries[] = ['account_id' => $partyId, 'debit' => $amount, 'credit' => 0, 'narration' => "کمیشن ادائیگی — بذریعہ {$accountName}"];
            // 4. Cr Bank/Cash Account (asset outflow)
            $entries[] = ['account_id' => $accountId, 'debit' => 0, 'credit' => $amount, 'narration' => "کمیشن ادائیگی — بنام {$partyName}"];

            $narration = "کمیشن ادائیگی: {$partyName} — {$itemName} بذریعہ {$accountName}";

        } elseif ($type === 'expense') {
            Validator::check($data, [
                'account_id' => 'required|integer',
                'item_id'    => 'nullable|integer'
            ]);

            // Get system expense account
            $expenseAccStmt = $this->db->prepare("SELECT id, name FROM accounts WHERE is_system = 1 AND sub_type = 'expense' LIMIT 1");
            $expenseAccStmt->execute();
            $expenseAcc = $expenseAccStmt->fetch();
            $expenseAccId = (int)$expenseAcc['id'];
            $expenseName = $expenseAcc['name'];

            $accountId = (int)$data['account_id'];
            $itemId = !empty($data['item_id']) ? (int)$data['item_id'] : null;
            $accountName = $this->db->query("SELECT name FROM accounts WHERE id = {$accountId}")->fetchColumn();
            $itemName = $itemId ? $this->db->query("SELECT name FROM items WHERE id = {$itemId}")->fetchColumn() : '';

            // Dr Expense Account, Cr Cash/Bank
            $entries[] = ['account_id' => $expenseAccId, 'debit' => $amount, 'credit' => 0, 'narration' => "خرچہ — {$expenseName}" . ($itemName ? " ({$itemName})" : '')];
            $entries[] = ['account_id' => $accountId, 'debit' => 0, 'credit' => $amount, 'narration' => "خرچہ ادائیگی — {$expenseName} بذریعہ {$accountName}"];
            
            $narration = "خرچہ: {$expenseName} بذریعہ {$accountName}" . ($itemName ? " برائے {$itemName}" : '');
        } else {
            Validator::check($data, [
                'party_id'   => 'required|integer',
                'account_id' => 'required|integer'
            ]);

            $partyId = (int)$data['party_id'];
            $accountId = (int)$data['account_id'];

            $partyName = $this->db->query("SELECT name FROM accounts WHERE id = {$partyId}")->fetchColumn();
            $accountName = $this->db->query("SELECT name FROM accounts WHERE id = {$accountId}")->fetchColumn();

            if ($type === 'payment') {
                // ادائیگی: Paying a party (their balance reduces)
                $entries[] = ['account_id' => $partyId, 'debit' => $amount, 'credit' => 0, 'narration' => "ادائیگی — {$partyName}"];
                $entries[] = ['account_id' => $accountId, 'debit' => 0, 'credit' => $amount, 'narration' => "ادائیگی بذریعہ {$accountName}"];
                $narration = "ادائیگی {$partyName} — {$accountName}";
            } else {
                // وصولی: Receiving from a party
                $entries[] = ['account_id' => $accountId, 'debit' => $amount, 'credit' => 0, 'narration' => "وصولی بذریعہ {$accountName}"];
                $entries[] = ['account_id' => $partyId, 'debit' => 0, 'credit' => $amount, 'narration' => "وصولی — {$partyName}"];
                $narration = "وصولی {$partyName} — {$accountName}";
            }
        }

        if (!empty($data['narration'])) {
            $narration .= ' | ' . $data['narration'];
        }

        $this->db->beginTransaction();
        try {
            if ($type === 'transfer') {
                AccountingService::assertSufficientBankCashBalance($this->db, $fromId, $amount);
            } elseif ($type === 'payment' || $type === 'commission_pay' || $type === 'expense') {
                AccountingService::assertSufficientBankCashBalance($this->db, $accountId, $amount);
            }

            $journalId = AccountingService::postJournal($this->db, [
                'uuid'        => $uuid,
                'date'        => $data['date'],
                'source_type' => ($type === 'expense') ? 'expense' : 'cash_bank',
                'source_id'   => $itemId,
                'narration'   => $narration,
                'created_by'  => $this->user->id
            ], $entries);

            AuditLogger::log($this->db, $this->user->id, 'CREATED_CASHBANK', 'journals', $journalId, null, $data);

            $this->db->commit();
            Response::created(['id' => $journalId], 'واؤچر کامیابی سے محفوظ ہو گیا۔');
        } catch (Exception $e) {
            $this->db->rollBack();
            throw $e;
        }
    }

    /** POST /cash-bank/{id}/reverse */
    public function reverse(int $id): void {
        $this->db->beginTransaction();
        try {
            $reversalId = AccountingService::reverseJournal($this->db, $id, $this->user->id);
            AuditLogger::log($this->db, $this->user->id, 'REVERSED_CASHBANK', 'journals', $id);
            $this->db->commit();
            Response::success(['reversal_id' => $reversalId], 'واؤچر ریورس ہو گیا۔');
        } catch (Exception $e) {
            $this->db->rollBack();
            throw $e;
        }
    }

    private function detectEntryType(string $narration): string {
        if (mb_strpos($narration, 'منتقلی') !== false) {
            return 'transfer';
        }
        if (mb_strpos($narration, 'براہ راست جمع') !== false) {
            return 'direct_deposit';
        }
        if (mb_strpos($narration, 'وصولی') !== false) {
            return 'receipt';
        }
        if (mb_strpos($narration, 'نام ایڈجسٹمنٹ') !== false) {
            return 'debit_adj';
        }
        if (mb_strpos($narration, 'جمع ایڈجسٹمنٹ') !== false) {
            return 'credit_adj';
        }
        if (mb_strpos($narration, 'کمیشن ادائیگی') !== false) {
            return 'commission_pay';
        }
        if (mb_strpos($narration, 'خرچہ') !== false) {
            return 'expense';
        }

        return 'payment';
    }
}
