<?php
require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/icons.php';

if (session_status() === PHP_SESSION_NONE) {
    session_start();
}

// Load accounting helper if tables exist (safe to include after DB connect)
if (file_exists(__DIR__ . '/accounting.php')) {
    require_once __DIR__ . '/accounting.php';
}

/* ---------------------------------------------------------
   AUTH HELPERS
--------------------------------------------------------- */
function isLoggedIn() {
    return isset($_SESSION['user_id']);
}

function requireLogin() {
    if (!isLoggedIn()) {
        header('Location: ' . APP_URL . '/login.php');
        exit;
    }
    enforceSingleSession();
}

/**
 * SINGLE-DEVICE LOGIN
 * At login, a random token is generated, saved on the user's row
 * (active_session_token) and mirrored into $_SESSION['session_token'].
 * On every authenticated page load we re-check the DB copy still
 * matches this session's copy. If someone logs into the same account
 * from another device, login.php overwrites the DB token, which makes
 * every other open session mismatch on its next request and get
 * signed out automatically.
 */
function enforceSingleSession() {
    global $pdo;
    if (empty($_SESSION['user_id']) || empty($_SESSION['session_token']) || !$pdo) return;
    $stmt = $pdo->prepare("SELECT active_session_token FROM users WHERE id = ?");
    $stmt->execute([$_SESSION['user_id']]);
    $row = $stmt->fetch();
    if (!$row || $row['active_session_token'] !== $_SESSION['session_token']) {
        $_SESSION = [];
        if (ini_get('session.use_cookies')) {
            $params = session_get_cookie_params();
            setcookie(session_name(), '', time() - 42000, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
        }
        session_destroy();
        header('Location: ' . APP_URL . '/login.php?reason=another_device');
        exit;
    }
}

function currentUserId() {
    return $_SESSION['user_id'] ?? null;
}

function currentUserName() {
    return $_SESSION['user_name'] ?? 'User';
}

function currentUserRole() {
    return $_SESSION['user_role'] ?? 'staff';
}

function requireAdmin() {
    if (currentUserRole() !== 'admin') {
        setFlash('danger', 'Access denied. Admin only.');
        header('Location: ' . APP_URL . '/dashboard.php');
        exit;
    }
}

/**
 * Check if the current user can access a given tab key.
 * Admin always has full access. Staff access depends on tab_permissions JSON stored in session.
 */
function hasTabAccess(string $tabKey): bool {
    if (currentUserRole() === 'admin') return true;
    $perms = $_SESSION['tab_permissions'] ?? [];
    return in_array($tabKey, $perms, true);
}

/* ---------------------------------------------------------
   LICENSE HELPERS
   license_count (in this company's own `settings` table) is
   the max number of ACTIVE user logins this company is allowed.
   It is copied here from the master DB when Super Admin sets/
   changes the license, so normal pages never need to touch the
   master DB.
--------------------------------------------------------- */
function getLicenseCount(): int {
    return (int) getSetting('license_count', 5);
}

function getActiveUserCount(): int {
    global $pdo;
    return (int) $pdo->query("SELECT COUNT(*) FROM users WHERE status = 1")->fetchColumn();
}

function canCreateMoreUsers(): bool {
    return getActiveUserCount() < getLicenseCount();
}

/* ---------------------------------------------------------
   SETTINGS HELPERS
--------------------------------------------------------- */
function getSetting($key, $default = '') {
    global $pdo;
    static $cache = [];
    if (isset($cache[$key])) return $cache[$key];
    $stmt = $pdo->prepare("SELECT setting_value FROM settings WHERE setting_key = ?");
    $stmt->execute([$key]);
    $row = $stmt->fetch();
    $cache[$key] = $row ? $row['setting_value'] : $default;
    return $cache[$key];
}

function updateSetting($key, $value) {
    global $pdo;
    $stmt = $pdo->prepare("INSERT INTO settings (setting_key, setting_value) VALUES (?, ?)
                            ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)");
    return $stmt->execute([$key, $value]);
}

/* ---------------------------------------------------------
   NUMBER GENERATORS (PREFIX-YYYYMM-0001 style)
--------------------------------------------------------- */
function generateDocNumber($table, $numberColumn, $prefix) {
    global $pdo;
    $yearMonth = date('Ym');
    $likePattern = $prefix . '-' . $yearMonth . '-%';
    $stmt = $pdo->prepare("SELECT $numberColumn FROM $table WHERE $numberColumn LIKE ? ORDER BY id DESC LIMIT 1");
    $stmt->execute([$likePattern]);
    $row = $stmt->fetch();
    if ($row) {
        $lastNum = (int) substr($row[$numberColumn], -4);
        $newNum = $lastNum + 1;
    } else {
        $newNum = 1;
    }
    return $prefix . '-' . $yearMonth . '-' . str_pad($newNum, 4, '0', STR_PAD_LEFT);
}

/* ---------------------------------------------------------
   FORMATTERS
--------------------------------------------------------- */
function formatMoney($amount) {
    return '₹' . number_format((float)$amount, 2);
}

function formatDate($date) {
    if (!$date || $date == '0000-00-00') return '-';
    return date('d-M-Y', strtotime($date));
}

function formatDateTime($datetime) {
    if (!$datetime || $datetime == '0000-00-00 00:00:00') return '-';
    return date('d-M-Y h:i A', strtotime($datetime));
}

/* ---------------------------------------------------------
   FLASH MESSAGES
--------------------------------------------------------- */
function setFlash($type, $message) {
    $_SESSION['flash'] = ['type' => $type, 'message' => $message];
}

function getFlash() {
    if (isset($_SESSION['flash'])) {
        $flash = $_SESSION['flash'];
        unset($_SESSION['flash']);
        return $flash;
    }
    return null;
}

/* ---------------------------------------------------------
   SECURITY
--------------------------------------------------------- */
function sanitize($str) {
    return htmlspecialchars(trim($str ?? ''), ENT_QUOTES, 'UTF-8');
}

function generateCSRFToken() {
    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['csrf_token'];
}

function verifyCSRFToken($token) {
    return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
}

/* ---------------------------------------------------------
   STOCK HELPERS
   Stock states:
     available_qty -> can be sold/quoted
     freeze_qty     -> reserved (delivery note created, payment pending)
     sold_qty       -> fully paid & completed
--------------------------------------------------------- */

// Ensure a stock_ledger row exists for an item
function ensureStockLedger($itemId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT id FROM stock_ledger WHERE item_id = ?");
    $stmt->execute([$itemId]);
    if (!$stmt->fetch()) {
        $pdo->prepare("INSERT INTO stock_ledger (item_id, purchased_qty, available_qty, freeze_qty, sold_qty) VALUES (?,0,0,0,0)")
            ->execute([$itemId]);
    }
}

// Add purchased stock (increases purchased_qty + available_qty)
function addPurchaseStock($itemId, $qty, $purchasePrice, $referenceId, $userId) {
    global $pdo;
    ensureStockLedger($itemId);

    // Update average purchase price (weighted average)
    $stmt = $pdo->prepare("SELECT purchased_qty, avg_purchase_price FROM stock_ledger WHERE item_id = ?");
    $stmt->execute([$itemId]);
    $row = $stmt->fetch();

    $oldQty = (int)$row['purchased_qty'];
    $oldAvg = (float)$row['avg_purchase_price'];
    $newQty = $oldQty + $qty;
    $newAvg = $newQty > 0 ? (($oldQty * $oldAvg) + ($qty * $purchasePrice)) / $newQty : $purchasePrice;

    $pdo->prepare("UPDATE stock_ledger SET
                        purchased_qty = purchased_qty + ?,
                        available_qty = available_qty + ?,
                        avg_purchase_price = ?
                    WHERE item_id = ?")
        ->execute([$qty, $qty, $newAvg, $itemId]);

    logStockMovement($itemId, 'PURCHASE', $qty, 'purchase', $referenceId, 'Stock added via purchase', $userId);
}

// Reverse purchase stock (used when editing/deleting a purchase entry)
function reversePurchaseStock($itemId, $qty, $referenceId, $userId) {
    global $pdo;
    ensureStockLedger($itemId);
    $pdo->prepare("UPDATE stock_ledger SET
                        purchased_qty = GREATEST(0, purchased_qty - ?),
                        available_qty = GREATEST(0, available_qty - ?)
                    WHERE item_id = ?")
        ->execute([$qty, $qty, $itemId]);
    logStockMovement($itemId, 'ADJUSTMENT', -$qty, 'purchase', $referenceId, 'Purchase entry reversed/edited', $userId);
}

// Move stock from AVAILABLE -> FREEZE (when delivery note is created)
function freezeStock($itemId, $qty, $referenceId, $userId) {
    global $pdo;
    ensureStockLedger($itemId);
    $pdo->prepare("UPDATE stock_ledger SET
                        available_qty = available_qty - ?,
                        freeze_qty = freeze_qty + ?
                    WHERE item_id = ?")
        ->execute([$qty, $qty, $itemId]);
    logStockMovement($itemId, 'FREEZE', $qty, 'delivery_note', $referenceId, 'Stock reserved for delivery', $userId);
}

// Move stock from FREEZE -> AVAILABLE (if delivery cancelled)
function unfreezeStock($itemId, $qty, $referenceId, $userId) {
    global $pdo;
    ensureStockLedger($itemId);
    $pdo->prepare("UPDATE stock_ledger SET
                        available_qty = available_qty + ?,
                        freeze_qty = freeze_qty - ?
                    WHERE item_id = ?")
        ->execute([$qty, $qty, $itemId]);
    logStockMovement($itemId, 'UNFREEZE', $qty, 'delivery_note', $referenceId, 'Reserved stock released', $userId);
}

// Move stock from FREEZE -> SOLD (when invoice payment fully received)
function markStockSold($itemId, $qty, $referenceId, $userId) {
    global $pdo;
    ensureStockLedger($itemId);
    $pdo->prepare("UPDATE stock_ledger SET
                        freeze_qty = GREATEST(0, freeze_qty - ?),
                        sold_qty = sold_qty + ?
                    WHERE item_id = ?")
        ->execute([$qty, $qty, $itemId]);
    logStockMovement($itemId, 'SOLD', $qty, 'invoice', $referenceId, 'Payment received - stock marked sold', $userId);
}

function logStockMovement($itemId, $type, $qty, $refType, $refId, $remarks, $userId) {
    global $pdo;
    $pdo->prepare("INSERT INTO stock_movements (item_id, movement_type, qty, reference_type, reference_id, remarks, created_by)
                    VALUES (?,?,?,?,?,?,?)")
        ->execute([$itemId, $type, $qty, $refType, $refId, $remarks, $userId]);
}

// Get current stock snapshot for an item
function getStockSnapshot($itemId) {
    global $pdo;
    ensureStockLedger($itemId);
    $stmt = $pdo->prepare("SELECT * FROM stock_ledger WHERE item_id = ?");
    $stmt->execute([$itemId]);
    return $stmt->fetch();
}

/* ---------------------------------------------------------
   CASH FLOW HELPERS
--------------------------------------------------------- */
function recordPayment($type, $refType, $refId, $partyName, $amount, $paymentDate, $mode, $remarks, $userId) {
    global $pdo;
    $pdo->prepare("INSERT INTO payments (payment_type, reference_type, reference_id, party_name, amount, payment_date, payment_mode, remarks, created_by)
                    VALUES (?,?,?,?,?,?,?,?,?)")
        ->execute([$type, $refType, $refId, $partyName, $amount, $paymentDate, $mode, $remarks, $userId]);
}

/* ---------------------------------------------------------
   INVOICE PAYMENT HELPERS (dedicated Payments tab)
   Supports: multiple partial payments per invoice, TDS deduction,
   full payment history, and auto stock freeze->sold conversion
   once the invoice is fully settled (amount + TDS = grand total).
--------------------------------------------------------- */

// Record a single payment entry against an invoice.
// $amount = actual cash/bank amount received (excludes TDS)
// $tdsAmount = TDS deducted for this payment (optional, can be 0)
// Returns ['new_paid_total','new_status','fully_paid' => bool]
function recordInvoicePayment($invoiceId, $amount, $tdsAmount, $paymentDateTime, $mode, $note, $userId, $bankAccountId = 0) {
    global $pdo;

    $stmt = $pdo->prepare("SELECT i.*, c.customer_name FROM invoices i JOIN sale_vendors c ON c.id=i.customer_id WHERE i.id=?");
    $stmt->execute([$invoiceId]);
    $inv = $stmt->fetch();
    if (!$inv) return null;

    // Safety check: marked_by has a FOREIGN KEY to users(id). If the session's
    // user id doesn't actually exist in the users table (stale session, user
    // was deleted/recreated, etc.) insert NULL instead of failing the whole
    // payment with a foreign key violation.
    $validUserId = null;
    if ($userId) {
        $check = $pdo->prepare("SELECT id FROM users WHERE id = ?");
        $check->execute([$userId]);
        if ($check->fetch()) {
            $validUserId = $userId;
        }
    }

    // Insert into payment history
    $pdo->prepare("INSERT INTO invoice_payments (invoice_id, amount, payment_date, payment_mode, tds_amount, reference_note, marked_by)
                    VALUES (?,?,?,?,?,?,?)")
        ->execute([$invoiceId, $amount, $paymentDateTime, $mode, $tdsAmount, $note, $validUserId]);

    // Recalculate totals from full payment history (source of truth)
    $sumStmt = $pdo->prepare("SELECT COALESCE(SUM(amount),0) paid, COALESCE(SUM(tds_amount),0) tds FROM invoice_payments WHERE invoice_id=?");
    $sumStmt->execute([$invoiceId]);
    $sums = $sumStmt->fetch();

    $newPaidTotal = (float)$sums['paid'];
    $newTdsTotal  = (float)$sums['tds'];
    $settledTotal = $newPaidTotal + $newTdsTotal; // amount received + TDS counts as "settled" against grand_total
    $newStatus    = $settledTotal >= $inv['grand_total'] ? 'paid' : 'partial';
    $fullyPaid    = ($newStatus === 'paid');

    $pdo->prepare("UPDATE invoices SET
                        paid_amount = ?,
                        tds_amount = ?,
                        payment_status = ?,
                        payment_date = ?,
                        payment_mode = ?,
                        payment_marked_by = ?
                    WHERE id = ?")
        ->execute([$newPaidTotal, $newTdsTotal, $newStatus, $paymentDateTime, $mode, $validUserId, $invoiceId]);

    // Record cash inflow (actual cash amount only, not TDS - TDS is not cash received)
    if ($amount > 0) {
        recordPayment('inflow', 'invoice', $invoiceId, $inv['customer_name'], $amount, $paymentDateTime, $mode,
            "Payment for invoice {$inv['invoice_no']}" . ($tdsAmount > 0 ? " (TDS deducted: " . formatMoney($tdsAmount) . ")" : ""), $validUserId);

        // Auto-create accounting journal entry if accounting module is installed
        if (function_exists('createJournalEntry')) {
            try {
                // Use the specifically selected bank account if provided,
                // otherwise fall back to guessing from payment mode
                $resolvedBankAccId = $bankAccountId ?: null;
                if (!$resolvedBankAccId) {
                    if (stripos($mode, 'cash') !== false) {
                        $resolvedBankAccId = $pdo->query("SELECT id FROM accounts WHERE account_subtype='cash' AND is_active=1 ORDER BY is_system DESC LIMIT 1")->fetchColumn();
                    } else {
                        $resolvedBankAccId = $pdo->query("SELECT id FROM accounts WHERE account_subtype='bank' AND is_active=1 ORDER BY is_system DESC LIMIT 1")->fetchColumn();
                    }
                }
                $arAccId = $pdo->query("SELECT id FROM accounts WHERE account_code='1100' LIMIT 1")->fetchColumn();

                if ($resolvedBankAccId && $arAccId) {
                    createJournalEntry(
                        date('Y-m-d', strtotime($paymentDateTime)),
                        'receipt',
                        "Payment received from {$inv['customer_name']} for Invoice {$inv['invoice_no']}" . ($tdsAmount > 0 ? " (TDS: ".formatMoney($tdsAmount).")" : ""),
                        [
                            ['account_id' => $resolvedBankAccId, 'debit'  => $amount, 'credit' => 0,       'narration' => $mode],
                            ['account_id' => $arAccId,           'debit'  => 0,       'credit' => $amount, 'narration' => "Invoice {$inv['invoice_no']}"],
                        ],
                        'invoice', $invoiceId, $validUserId
                    );
                }
            } catch (Exception $e) {
                error_log('Journal entry failed for payment: ' . $e->getMessage());
            }
        }
    }

    // On full settlement -> move stock from FREEZE to SOLD (sale invoices only)
    if ($fullyPaid && $inv['invoice_type'] === 'sale') {
        $itemsStmt = $pdo->prepare("SELECT * FROM invoice_items WHERE invoice_id=?");
        $itemsStmt->execute([$invoiceId]);
        foreach ($itemsStmt->fetchAll() as $li) {
            if ($li['item_id']) {
                markStockSold($li['item_id'], (int)$li['qty'], $invoiceId, $validUserId);
            }
        }
    }

    return ['new_paid_total' => $newPaidTotal, 'new_tds_total' => $newTdsTotal, 'new_status' => $newStatus, 'fully_paid' => $fullyPaid];
}

// Get full payment history for one invoice
function getInvoicePaymentHistory($invoiceId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT ip.*, u.name as marked_by_name
                            FROM invoice_payments ip
                            LEFT JOIN users u ON u.id = ip.marked_by
                            WHERE ip.invoice_id = ?
                            ORDER BY ip.payment_date DESC, ip.id DESC");
    $stmt->execute([$invoiceId]);
    return $stmt->fetchAll();
}
