<?php
// organization/includes/functions.php
// Core business logic for the Organization module.
// All functions receive $conn as first argument — no globals.

// ─────────────────────────────────────────────
// SECURITY HELPERS
// ─────────────────────────────────────────────

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

function csrf_verify(): bool {
    return !empty($_POST['csrf_token'])
        && !empty($_SESSION['csrf_token'])
        && hash_equals($_SESSION['csrf_token'], $_POST['csrf_token']);
}

function csrf_field(): string {
    return '<input type="hidden" name="csrf_token" value="' . htmlspecialchars(csrf_token()) . '">';
}

function h(string $value): string {
    return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

// ─────────────────────────────────────────────
// REFERENCE GENERATORS
// ─────────────────────────────────────────────

function generate_batch_reference(int $org_id): string {
    return 'BATCH_' . $org_id . '_' . time() . '_' . strtoupper(bin2hex(random_bytes(4)));
}

function generate_tx_reference(int $batch_id, int $staff_id): string {
    return 'MT_' . $batch_id . '_' . $staff_id . '_' . time();
}

// ─────────────────────────────────────────────
// ORGANISATION WALLET HELPERS
// ─────────────────────────────────────────────

/**
 * Ensure the organisation has a wallet row; create one (balance=0) if missing.
 */
function ensure_org_wallet(mysqli $conn, int $org_id, int $user_id): void {
    $chk = $conn->prepare(
        "SELECT id FROM wallets WHERE organization_id=? AND wallet_type='organization' LIMIT 1"
    );
    $chk->bind_param("i", $org_id);
    $chk->execute();
    if ($chk->get_result()->num_rows === 0) {
        $ins = $conn->prepare(
            "INSERT INTO wallets (user_id, wallet_type, organization_id, balance, created_at)
             VALUES (?, 'organization', ?, 0.00, NOW())"
        );
        $ins->bind_param("ii", $user_id, $org_id);
        $ins->execute();
    }
}

/**
 * Return the current org wallet balance (0.00 if wallet not yet created).
 */
function get_org_wallet_balance(mysqli $conn, int $org_id): float {
    $r = $conn->prepare(
        "SELECT COALESCE(balance, 0) FROM wallets
         WHERE organization_id=? AND wallet_type='organization' LIMIT 1"
    );
    $r->bind_param("i", $org_id);
    $r->execute();
    $row = $r->get_result()->fetch_row();
    return $row ? (float)$row[0] : 0.00;
}

/**
 * Credit the org wallet (called after a successful Paystack payment).
 * Records the transaction and updates the balance atomically.
 */
function credit_org_wallet(
    mysqli $conn,
    int $org_id,
    int $user_id,
    float $amount,
    string $reference,
    string $description
): void {
    ensure_org_wallet($conn, $org_id, $user_id);

    // Update balance
    $upd = $conn->prepare(
        "UPDATE wallets SET balance = balance + ?
         WHERE organization_id=? AND wallet_type='organization'"
    );
    $upd->bind_param("di", $amount, $org_id);
    $upd->execute();

    // Record transaction
    $tx = $conn->prepare(
        "INSERT INTO wallet_transactions
           (user_id, wallet_type, organization_id,
            amount, type, reference, description, status, created_at)
         VALUES (?, 'organization', ?, ?, 'credit', ?, ?, 'success', NOW())"
    );
    $tx->bind_param("iidss", $user_id, $org_id, $amount, $reference, $description);
    $tx->execute();
}

/**
 * Debit the org wallet (called when disbursing to staff).
 * Must be called inside an active transaction with the wallet row already locked.
 */
function debit_org_wallet(
    mysqli $conn,
    int $org_id,
    int $user_id,
    float $amount,
    string $reference,
    string $description
): void {
    $upd = $conn->prepare(
        "UPDATE wallets SET balance = balance - ?
         WHERE organization_id=? AND wallet_type='organization'"
    );
    $upd->bind_param("di", $amount, $org_id);
    $upd->execute();

    $tx = $conn->prepare(
        "INSERT INTO wallet_transactions
           (user_id, wallet_type, organization_id,
            amount, type, reference, description, status, created_at)
         VALUES (?, 'organization', ?, ?, 'debit', ?, ?, 'success', NOW())"
    );
    $tx->bind_param("iidss", $user_id, $org_id, $amount, $reference, $description);
    $tx->execute();
}

// ─────────────────────────────────────────────
// WALLET HELPERS
// ─────────────────────────────────────────────

/**
 * Credit a staff member's meal_ticket wallet.
 * Creates the wallet if it somehow doesn't exist yet.
 * Returns true on success, throws Exception on failure.
 */
function credit_meal_ticket(
    mysqli $conn,
    int $staff_id,
    float $amount,
    int $org_id,
    int $batch_id,
    string $tx_reference,
    string $description
): bool {
    // Ensure meal_ticket wallet exists
    $chk = $conn->prepare(
        "SELECT id FROM wallets WHERE user_id=? AND wallet_type='meal_ticket' LIMIT 1"
    );
    $chk->bind_param("i", $staff_id);
    $chk->execute();
    $wallet = $chk->get_result()->fetch_assoc();

    if (!$wallet) {
        $ins = $conn->prepare(
            "INSERT INTO wallets (user_id, wallet_type, organization_id, balance, created_at)
             VALUES (?, 'meal_ticket', ?, 0.00, NOW())"
        );
        $ins->bind_param("ii", $staff_id, $org_id);
        $ins->execute();
    }

    // Credit wallet balance
    $upd = $conn->prepare(
        "UPDATE wallets SET balance = balance + ?
         WHERE user_id=? AND wallet_type='meal_ticket'"
    );
    $upd->bind_param("di", $amount, $staff_id);
    $upd->execute();

    if ($upd->affected_rows < 1) {
        throw new RuntimeException("Failed to update meal ticket wallet for staff #{$staff_id}");
    }

    // Record transaction
    $tx = $conn->prepare(
        "INSERT INTO wallet_transactions
           (user_id, wallet_type, organization_id, funding_batch_id,
            amount, type, reference, description, status, created_at)
         VALUES (?, 'meal_ticket', ?, ?, ?, 'credit', ?, ?, 'success', NOW())"
    );
    $tx->bind_param("iiidss", $staff_id, $org_id, $batch_id, $amount, $tx_reference, $description);
    $tx->execute();

    return true;
}

/**
 * Fund one or more staff members' meal ticket wallets.
 * Creates a batch record, processes each staff member, marks batch complete.
 *
 * @param  int[]  $staff_ids
 * @return array{batch_id:int, funded:int, failed:int}
 */
function fund_staff_batch(
    mysqli $conn,
    int $org_id,
    int $admin_id,
    array $staff_ids,
    float $amount_per_staff,
    string $batch_type,
    string $notes = ''
): array {
    if (empty($staff_ids) || $amount_per_staff <= 0) {
        throw new InvalidArgumentException("Invalid funding parameters.");
    }

    // Validate all staff belong to this org (security check)
    $placeholders = implode(',', array_fill(0, count($staff_ids), '?'));
    $types        = str_repeat('i', count($staff_ids));
    $val_stmt     = $conn->prepare(
        "SELECT id FROM users
         WHERE id IN ({$placeholders})
           AND organization_id = ?
           AND role = 'org_staff'
           AND status = 1"
    );
    $all_params = array_merge($staff_ids, [$org_id]);
    $val_stmt->bind_param($types . 'i', ...$all_params);
    $val_stmt->execute();
    $valid_ids = array_column($val_stmt->get_result()->fetch_all(MYSQLI_ASSOC), 'id');

    if (empty($valid_ids)) {
        throw new RuntimeException("No valid staff members found for this organization.");
    }

    $staff_count  = count($valid_ids);
    $total_amount = round($amount_per_staff * $staff_count, 2);
    $batch_ref    = generate_batch_reference($org_id);

    $conn->begin_transaction();
    try {
        // ── Lock org wallet row and verify sufficient balance ─────────────
        $lock = $conn->prepare(
            "SELECT balance FROM wallets
             WHERE organization_id=? AND wallet_type='organization'
             LIMIT 1 FOR UPDATE"
        );
        $lock->bind_param("i", $org_id);
        $lock->execute();
        $wallet_row = $lock->get_result()->fetch_row();

        $org_balance = $wallet_row ? (float)$wallet_row[0] : 0.00;
        if ($org_balance < $total_amount) {
            $conn->rollback();
            throw new RuntimeException(
                "Insufficient organisation wallet balance. " .
                "Available: ₦" . number_format($org_balance, 2) . ", " .
                "Required: ₦" . number_format($total_amount, 2) . ". " .
                "Please <a href='wallet.php'>top up your wallet</a> first."
            );
        }

        // Create batch record
        $batch = $conn->prepare(
            "INSERT INTO org_funding_batches
               (organization_id, funded_by, batch_type, amount_per_staff,
                total_amount, staff_count, reference, notes, status, created_at)
             VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'processing', NOW())"
        );
        $batch->bind_param(
            "iisddiss",
            $org_id, $admin_id, $batch_type,
            $amount_per_staff, $total_amount, $staff_count,
            $batch_ref, $notes
        );
        $batch->execute();
        $batch_id = $conn->insert_id;

        // Get org name for notification
        $org_row = $conn->prepare("SELECT name FROM organizations WHERE id=? LIMIT 1");
        $org_row->bind_param("i", $org_id);
        $org_row->execute();
        $org_name = $org_row->get_result()->fetch_row()[0] ?? 'Your Organization';

        $funded = 0;
        $failed = 0;

        foreach ($valid_ids as $staff_id) {
            try {
                $tx_ref  = generate_tx_reference($batch_id, $staff_id);
                $note_suffix = ($notes !== '') ? " — {$notes}" : '';
                $desc    = "Meal ticket funded by {$org_name} — ₦" . number_format($amount_per_staff, 2) . $note_suffix;
                credit_meal_ticket(
                    $conn, $staff_id, $amount_per_staff,
                    $org_id, $batch_id, $tx_ref, $desc
                );
                $funded++;
                // Notify staff member — non-fatal if it fails. Respect the
                // staff member's "Wallet & funding" notification preference, and
                // include the organisation's narration/note when one was provided.
                try {
                    $wants_wallet = true;
                    if ($pf = @$conn->query("SELECT notify_wallet FROM users WHERE id=" . (int)$staff_id . " LIMIT 1")) {
                        $pr = $pf->fetch_assoc();
                        if ($pr && $pr['notify_wallet'] !== null) $wants_wallet = (bool)$pr['notify_wallet'];
                    }
                    if ($wants_wallet) {
                        notify_user(
                            $conn, $staff_id, null, $org_id,
                            'wallet_funded',
                            'Meal Ticket Credited',
                            "₦" . number_format($amount_per_staff, 2) . " meal ticket credit from {$org_name}."
                            . (($notes !== '') ? " Note: {$notes}" : '')
                        );
                    }
                } catch (\Throwable $ne) {
                    // Notification failure must not roll back the funding
                }
            } catch (\Throwable $e) {
                $failed++;
            }
        }

        // ── Debit org wallet for the amount actually disbursed ────────────
        $actual_total = round($amount_per_staff * $funded, 2);
        if ($actual_total > 0) {
            debit_org_wallet(
                $conn, $org_id, $admin_id, $actual_total,
                'DISBURSE_' . $batch_ref,
                "Staff meal ticket disbursement — Batch #{$batch_id} ({$funded} staff)"
            );
        }

        // Mark batch complete
        $status = ($failed === 0) ? 'completed' : (($funded > 0) ? 'completed' : 'failed');
        $upd    = $conn->prepare(
            "UPDATE org_funding_batches
             SET status=?, staff_count=?, total_amount=?, completed_at=NOW()
             WHERE id=?"
        );
        $upd->bind_param("sidi", $status, $funded, $actual_total, $batch_id);
        $upd->execute();

        $conn->commit();

        return [
            'batch_id' => $batch_id,
            'funded'   => $funded,
            'failed'   => $failed,
        ];

    } catch (Exception $e) {
        $conn->rollback();
        throw $e;
    }
}

// ─────────────────────────────────────────────
// NOTIFICATIONS
// ─────────────────────────────────────────────

/**
 * Insert a notification for a customer/org_staff user.
 */
function notify_user(
    mysqli $conn,
    int $user_id,
    ?int $restaurant_id,
    ?int $org_id,
    string $type,
    string $title,
    string $message
): void {
    $stmt = $conn->prepare(
        "INSERT INTO customer_notifications
           (user_id, restaurant_id, organization_id, type, title, message, is_read, created_at)
         VALUES (?, ?, ?, ?, ?, ?, 0, NOW())"
    );
    $stmt->bind_param("iiisss", $user_id, $restaurant_id, $org_id, $type, $title, $message);
    $stmt->execute();

    // Also deliver as a web push to the user's devices (non-fatal).
    try {
        $wp = __DIR__ . '/../../customer/includes/webpush.php';
        if (is_file($wp)) {
            require_once $wp;
            dabil_push_to_user($conn, $user_id, $title, $message, 'notifications.php');
        }
    } catch (\Throwable $e) { /* ignore push failures */ }
}

// ─────────────────────────────────────────────
// ORGANISATION STATS
// ─────────────────────────────────────────────

function get_org_stats(mysqli $conn, int $org_id): array {
    $stats = [];

    $r = $conn->prepare(
        "SELECT COUNT(*) FROM users WHERE organization_id=? AND role='org_staff' AND status=1"
    );
    $r->bind_param("i", $org_id); $r->execute();
    $stats['active_staff'] = (int)$r->get_result()->fetch_row()[0];

    $r = $conn->prepare(
        "SELECT COUNT(*) FROM users WHERE organization_id=? AND role='org_staff' AND status=0"
    );
    $r->bind_param("i", $org_id); $r->execute();
    $stats['suspended_staff'] = (int)$r->get_result()->fetch_row()[0];

    $r = $conn->prepare(
        "SELECT COALESCE(SUM(w.balance),0)
         FROM wallets w
         INNER JOIN users u ON w.user_id=u.id
         WHERE u.organization_id=? AND w.wallet_type='meal_ticket'"
    );
    $r->bind_param("i", $org_id); $r->execute();
    $stats['total_meal_ticket_balance'] = (float)$r->get_result()->fetch_row()[0];

    $r = $conn->prepare(
        "SELECT COALESCE(SUM(total_amount),0), COUNT(*)
         FROM org_funding_batches
         WHERE organization_id=? AND status='completed'"
    );
    $r->bind_param("i", $org_id); $r->execute();
    $row = $r->get_result()->fetch_row();
    $stats['total_funded']  = (float)$row[0];
    $stats['total_batches'] = (int)$row[1];

    $r = $conn->prepare(
        "SELECT COALESCE(SUM(total_amount),0)
         FROM org_funding_batches
         WHERE organization_id=? AND status='completed'
           AND MONTH(created_at)=MONTH(NOW()) AND YEAR(created_at)=YEAR(NOW())"
    );
    $r->bind_param("i", $org_id); $r->execute();
    $stats['funded_this_month'] = (float)$r->get_result()->fetch_row()[0];

    return $stats;
}

// ─────────────────────────────────────────────
// FORMATTING HELPERS
// ─────────────────────────────────────────────

function fmt_money(float $amount): string {
    return '₦' . number_format($amount, 2);
}

function fmt_date(string $datetime): string {
    return date('M j, Y', strtotime($datetime));
}

function fmt_datetime(string $datetime): string {
    return date('M j, Y g:ia', strtotime($datetime));
}

function time_ago(string $datetime): string {
    $diff = time() - strtotime($datetime);
    if ($diff < 60)       return 'just now';
    if ($diff < 3600)     return floor($diff / 60) . 'm ago';
    if ($diff < 86400)    return floor($diff / 3600) . 'h ago';
    if ($diff < 604800)   return floor($diff / 86400) . 'd ago';
    return date('M j', strtotime($datetime));
}

function initials(string $name): string {
    $parts = explode(' ', trim($name));
    $i     = strtoupper(substr($parts[0], 0, 1));
    if (isset($parts[1])) $i .= strtoupper(substr($parts[1], 0, 1));
    return $i;
}

function status_badge(int $status): string {
    return $status
        ? '<span class="badge badge-success">Active</span>'
        : '<span class="badge badge-error">Suspended</span>';
}

// ─────────────────────────────────────────────
// SCHEDULE HELPERS
// ─────────────────────────────────────────────

/**
 * Calculate the next run date for a schedule.
 */
function next_run_date(string $frequency, int $day_of_month, int $day_of_week): string {
    $now = new DateTime();

    if ($frequency === 'monthly') {
        $target = new DateTime("first day of this month");
        $target->setDate((int)$target->format('Y'), (int)$target->format('m'), $day_of_month);
        if ($target <= $now) {
            $target->modify('first day of next month');
            $target->setDate((int)$target->format('Y'), (int)$target->format('m'), $day_of_month);
        }
        return $target->format('Y-m-d');
    }

    // weekly / bi_weekly
    $days_map = [1=>'Monday',2=>'Tuesday',3=>'Wednesday',4=>'Thursday',5=>'Friday',6=>'Saturday',7=>'Sunday'];
    $day_name = $days_map[$day_of_week] ?? 'Monday';
    $target   = new DateTime("next {$day_name}");
    if ($frequency === 'bi_weekly') $target->modify('+7 days');

    return $target->format('Y-m-d');
}
