<?php
// functions.php - common functions used across the project
// Include AFTER db.php: require 'db.php'; require 'functions.php';

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

// ---------------------------------------------------------------
// Debug / error logging
// ---------------------------------------------------------------
function logDebug($message) {
    if (defined('DEBUGLOG') && DEBUGLOG) {
        $line = '[' . date('Y-m-d H:i:s') . '] ' . (is_string($message) ? $message : print_r($message, true)) . PHP_EOL;
        @file_put_contents(DEBUGLOGFILE, $line, FILE_APPEND);
    }
}

// ---------------------------------------------------------------
// Auth / role helpers
// ---------------------------------------------------------------
function isLoggedIn() {
    return !empty($_SESSION['userid']) && !empty($_SESSION['role']);
}

function currentRole() {
    return $_SESSION['role'] ?? null;
}

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

function currentPanchayatId() {
    return $_SESSION['panchayatid'] ?? null;
}

// Call at the top of every protected page. $allowedroles = array of roles allowed on this page.
function requireRole($allowedroles) {
    if (!isLoggedIn() || !in_array(currentRole(), $allowedroles, true)) {
        $redirecturl = basename($_SERVER['PHP_SELF']);
        $loginpage = 'login.php';
        if (strpos($redirecturl, 'admin') === 0) $loginpage = 'admin_login.php';
        elseif (strpos($redirecturl, 'panchayat') === 0) $loginpage = 'panchayat_login.php';
        elseif (strpos($redirecturl, 'rnd') === 0) $loginpage = 'rnd_login.php';
        header('Location: ' . $loginpage . '?redirect=' . urlencode($redirecturl));
        exit;
    }
}

function loginRedirectAfter($defaultpage) {
    $redirect = $_GET['redirect'] ?? $_POST['redirect'] ?? '';
    $redirect = basename($redirect); // defensive: prevent open redirect
    return $redirect !== '' ? $redirect : $defaultpage;
}

function hashPassword($plain) {
    return hash('sha256', $plain);
}

function verifyPassword($plain, $hash) {
    return hash('sha256', $plain) === $hash;
}

// ---------------------------------------------------------------
// Input sanitization (keeps ' " & etc, just protects against XSS on output)
// ---------------------------------------------------------------
function cleanInput($value) {
    if (is_array($value)) {
        return array_map('cleanInput', $value);
    }
    return trim($value);
}

function safeOut($value) {
    return htmlspecialchars(html_entity_decode((string)$value, ENT_QUOTES, 'UTF-8'), ENT_QUOTES, 'UTF-8');
}

// ---------------------------------------------------------------
// Money helpers (BCMath rule - see README section 9)
// ---------------------------------------------------------------
function moneyStr($val) {
    return number_format((float)$val, 2, '.', '');
}

function formatINR($val) {
    $num = moneyStr($val);
    $parts = explode('.', $num);
    $intpart = $parts[0];
    $decpart = $parts[1];
    $negative = false;
    if (substr($intpart, 0, 1) === '-') {
        $negative = true;
        $intpart = substr($intpart, 1);
    }
    $lastthree = substr($intpart, -3);
    $rest = substr($intpart, 0, -3);
    if ($rest !== '') {
        $rest = preg_replace('/\B(?=(\d{2})+(?!\d))/', ',', $rest);
        $formatted = $rest . ',' . $lastthree;
    } else {
        $formatted = $lastthree;
    }
    return ($negative ? '-' : '') . '₹' . $formatted . '.' . $decpart;
}

// ---------------------------------------------------------------
// JSON response helper (for ajax_*.php and api_*.php)
// ---------------------------------------------------------------
function jsonOut($success, $message = '', $data = []) {
    header('Content-Type: application/json');
    echo json_encode([
        'success' => $success,
        'message' => $message,
        'data' => $data,
    ]);
    exit;
}

// ---------------------------------------------------------------
// Settings table helpers
// ---------------------------------------------------------------
function getSetting($pdo, $key, $default = null) {
    $stmt = $pdo->prepare("SELECT settingvalue FROM setting WHERE settingkey = ? AND isdeleted = 0");
    $stmt->execute([$key]);
    $row = $stmt->fetch();
    return $row ? $row['settingvalue'] : $default;
}

function setSetting($pdo, $key, $value) {
    $stmt = $pdo->prepare("INSERT INTO setting (settingkey, settingvalue) VALUES (?, ?)
        ON DUPLICATE KEY UPDATE settingvalue = VALUES(settingvalue)");
    return $stmt->execute([$key, $value]);
}

// ---------------------------------------------------------------
// Activity log
// ---------------------------------------------------------------
function logActivity($pdo, $entitytype, $entityid, $action, $actionbyrole, $actionbyid = null, $remarks = '') {
    $stmt = $pdo->prepare("INSERT INTO activitylog (entitytype, entityid, action, actionbyrole, actionbyid, remarks)
        VALUES (?, ?, ?, ?, ?, ?)");
    $stmt->execute([$entitytype, $entityid, $action, $actionbyrole, $actionbyid, $remarks]);
}

// ---------------------------------------------------------------
// OTP helpers
// ---------------------------------------------------------------
function generateOtp($pdo, $mobileoremail, $purpose, $validminutes = 10) {
    $otp = (string)random_int(100000, 999999);
    $expiresat = date('Y-m-d H:i:s', strtotime("+{$validminutes} minutes"));
    $stmt = $pdo->prepare("INSERT INTO otp (mobileoremail, otp, purpose, expiresat) VALUES (?, ?, ?, ?)");
    $stmt->execute([$mobileoremail, $otp, $purpose, $expiresat]);
    return $otp;
}

function verifyOtp($pdo, $mobileoremail, $purpose, $otp) {
    $stmt = $pdo->prepare("SELECT id FROM otp WHERE mobileoremail = ? AND purpose = ? AND otp = ?
        AND isused = 0 AND isdeleted = 0 AND expiresat >= NOW() ORDER BY id DESC LIMIT 1");
    $stmt->execute([$mobileoremail, $purpose, $otp]);
    $row = $stmt->fetch();
    if (!$row) {
        return false;
    }
    $upd = $pdo->prepare("UPDATE otp SET isused = 1 WHERE id = ?");
    $upd->execute([$row['id']]);
    return true;
}

// ---------------------------------------------------------------
// Real OTP SMS gateway (Fabcoders) - used ONLY for sending OTP codes.
// For all other notification messages (approvals, rejections, renewals, payment
// links, new-registration alerts to panchayat) sendSms()/sendWhatsapp() below remain
// stub functions until a general notification gateway is provided.
// ---------------------------------------------------------------
function sendOtpSms($pdo, $mobile, $otp) {
    $message = "Your OTP is $otp . Thank You . From Fabcoders.";
    $urlmessage = str_replace(' ', '%20', $message);

    $smsurl = "http://144.76.184.187/http-tokenkeyapi.php?authentic-key=" . SMSAPIKEY .
        "&senderid=" . SMSSENDERID . "&route=1&number=" . urlencode($mobile) .
        "&message=" . $urlmessage . "&templateid=" . SMSTEMPLATEID;

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $smsurl);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 10);
    $response = curl_exec($ch);
    $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    $logdir = dirname(OTPLOGFILE);
    if (!file_exists($logdir)) {
        mkdir($logdir, 0755, true);
    }
    $logmessage = date('Y-m-d H:i:s') . " - OTP sent to {$mobile} | HTTP Code: {$httpcode} | Response: {$response}" . PHP_EOL;
    @file_put_contents(OTPLOGFILE, $logmessage, FILE_APPEND);

    $success = $httpcode == 200;
    $stmt = $pdo->prepare("INSERT INTO smslog (mobile, message, purpose, status) VALUES (?, ?, 'otp', ?)");
    $stmt->execute([$mobile, $message, $success ? 'sent' : 'failed']);

    return $success;
}

// ---------------------------------------------------------------
// SMS / WhatsApp stub functions (to be replaced with real gateway later)
// ---------------------------------------------------------------
function sendSms($pdo, $mobile, $message, $purpose = '') {
    // TODO: integrate real SMS gateway here.
    logDebug("SMS STUB to {$mobile}: {$message}");
    $stmt = $pdo->prepare("INSERT INTO smslog (mobile, message, purpose, status) VALUES (?, ?, ?, 'stubbed')");
    $stmt->execute([$mobile, $message, $purpose]);
    return true;
}

function sendWhatsapp($pdo, $mobile, $message, $purpose = '') {
    // TODO: integrate real WhatsApp Business API here.
    logDebug("WHATSAPP STUB to {$mobile}: {$message}");
    $stmt = $pdo->prepare("INSERT INTO whatsapplog (mobile, message, purpose, status) VALUES (?, ?, ?, 'stubbed')");
    $stmt->execute([$mobile, $message, $purpose]);
    return true;
}

// ---------------------------------------------------------------
// File upload helper - renames to unique filename
// ---------------------------------------------------------------
function uploadFile($fileinputarray, $destfolder, $allowedext = ['jpg', 'jpeg', 'png', 'pdf']) {
    if (empty($fileinputarray) || $fileinputarray['error'] !== UPLOAD_ERR_OK) {
        return ['success' => false, 'message' => 'No file uploaded or upload error'];
    }
    $ext = strtolower(pathinfo($fileinputarray['name'], PATHINFO_EXTENSION));
    if (!in_array($ext, $allowedext, true)) {
        return ['success' => false, 'message' => 'File type not allowed'];
    }
    if (!is_dir($destfolder)) {
        mkdir($destfolder, 0755, true);
    }
    $uniquename = uniqid('dfp_', true) . '_' . bin2hex(random_bytes(4)) . '.' . $ext;
    $destpath = rtrim($destfolder, '/') . '/' . $uniquename;
    if (!move_uploaded_file($fileinputarray['tmp_name'], $destpath)) {
        return ['success' => false, 'message' => 'Failed to move uploaded file'];
    }
    return ['success' => true, 'filename' => $uniquename, 'path' => $destpath];
}

// ---------------------------------------------------------------
// Barcode / token generators
// ---------------------------------------------------------------
function generateBarcodeText($villagerid) {
    return 'DFP' . str_pad($villagerid, 7, '0', STR_PAD_LEFT);
}

function generateApiToken() {
    return bin2hex(random_bytes(32));
}

// ---------------------------------------------------------------
// Pass validity / age helpers
// ---------------------------------------------------------------
function calcPassEndDate($startdate, $years) {
    return date('Y-m-d', strtotime($startdate . " +{$years} years"));
}

function calcAge($dob) {
    $birth = new DateTime($dob);
    $today = new DateTime('today');
    return $birth->diff($today)->y;
}

function isAjaxRequest() {
    return !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest';
}

// ---------------------------------------------------------------
// Mobile API bearer-token auth (villager app / ticket checker app)
// ---------------------------------------------------------------
function getBearerToken() {
    $header = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
    if ($header === '' && function_exists('apache_request_headers')) {
        $headers = apache_request_headers();
        $header = $headers['Authorization'] ?? '';
    }
    if (preg_match('/Bearer\s+(\S+)/i', $header, $m)) {
        return $m[1];
    }
    return '';
}

// Returns the villager row for a valid token, or sends a 401 JSON response and exits.
function requireVillagerToken($pdo) {
    $token = getBearerToken();
    if ($token === '') {
        http_response_code(401);
        jsonOut(false, 'Missing authorization token');
    }
    $stmt = $pdo->prepare("SELECT * FROM villager WHERE apitoken = ? AND isdeleted = 0");
    $stmt->execute([$token]);
    $villager = $stmt->fetch();
    if (!$villager) {
        http_response_code(401);
        jsonOut(false, 'Invalid or expired session. Please login again.');
    }
    return $villager;
}

// Returns the ticket checker row for a valid token, or sends a 401 JSON response and exits.
function requireCheckerToken($pdo) {
    $token = getBearerToken();
    if ($token === '') {
        http_response_code(401);
        jsonOut(false, 'Missing authorization token');
    }
    $stmt = $pdo->prepare("SELECT * FROM ticketchecker WHERE apitoken = ? AND isactive = 1 AND isdeleted = 0");
    $stmt->execute([$token]);
    $checker = $stmt->fetch();
    if (!$checker) {
        http_response_code(401);
        jsonOut(false, 'Invalid or expired session. Please login again.');
    }
    return $checker;
}

function villagerIssueToken($pdo, $villagerid) {
    $token = generateApiToken();
    $stmt = $pdo->prepare("UPDATE villager SET apitoken = ? WHERE id = ?");
    $stmt->execute([$token, $villagerid]);
    return $token;
}

function checkerIssueToken($pdo, $checkerid) {
    $token = generateApiToken();
    $stmt = $pdo->prepare("UPDATE ticketchecker SET apitoken = ? WHERE id = ?");
    $stmt->execute([$token, $checkerid]);
    return $token;
}

// Registration progress flags used by the mobile app to resume the multi-step flow
function villagerProgress($villager) {
    return [
        'hasname' => $villager['fullname'] !== '' && $villager['fullname'] !== null,
        'hasresidency' => !empty($villager['panchayatid']),
        'status' => $villager['status'],
        'paymentstatus' => $villager['paymentstatus'],
        'passactive' => (int)$villager['passactive'],
    ];
}

// ---------------------------------------------------------------
// Razorpay helpers
// ---------------------------------------------------------------
function razorpayCreateOrder($amount, $receipt, $notes = []) {
    // $amount is a BCMath-safe decimal string, e.g. '100.00'. Razorpay needs integer paise.
    $paise = (int)bcmul(moneyStr($amount), '100', 0);

    $payload = json_encode([
        'amount' => $paise,
        'currency' => 'INR',
        'receipt' => $receipt,
        'notes' => $notes,
    ]);

    $ch = curl_init('https://api.razorpay.com/v1/orders');
    curl_setopt($ch, CURLOPT_USERPWD, RAZORPAYKEYID . ':' . RAZORPAYKEYSECRET);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
    curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 20);
    $response = curl_exec($ch);
    $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $curlerror = curl_error($ch);
    curl_close($ch);

    if ($response === false) {
        logDebug('[razorpayCreateOrder] cURL error: ' . $curlerror);
        return ['success' => false, 'message' => 'Could not reach Razorpay'];
    }

    $decoded = json_decode($response, true);
    if ($httpcode !== 200 || empty($decoded['id'])) {
        logDebug('[razorpayCreateOrder] HTTP ' . $httpcode . ' response: ' . $response);
        return ['success' => false, 'message' => $decoded['error']['description'] ?? 'Razorpay order creation failed'];
    }

    return ['success' => true, 'order' => $decoded];
}

// Verify signature returned by Razorpay Checkout after successful payment (client-side callback)
function razorpayVerifyPaymentSignature($orderid, $paymentid, $signature) {
    $expected = hash_hmac('sha256', $orderid . '|' . $paymentid, RAZORPAYKEYSECRET);
    return hash_equals($expected, $signature);
}

// Verify signature of an incoming webhook request
function razorpayVerifyWebhookSignature($rawbody, $signature) {
    $expected = hash_hmac('sha256', $rawbody, RAZORPAYWEBHOOKSECRET);
    return hash_equals($expected, $signature);
}

// Marks a paymentorder as paid and activates the villager's pass. Safe to call more than once
// (idempotent) since it checks current state before making changes.
function markOrderPaidAndActivatePass($pdo, $razorpayorderid, $razorpaypaymentid, $razorpaysignature = null) {
    $stmt = $pdo->prepare("SELECT * FROM paymentorder WHERE razorpayorderid = ? AND isdeleted = 0");
    $stmt->execute([$razorpayorderid]);
    $order = $stmt->fetch();

    if (!$order) {
        return ['success' => false, 'message' => 'Order not found'];
    }
    if ($order['status'] === 'paid') {
        return ['success' => true, 'message' => 'Order already marked as paid']; // idempotent
    }

    $upd = $pdo->prepare("UPDATE paymentorder SET status = 'paid', razorpaypaymentid = ?, razorpaysignature = ?, paidat = NOW() WHERE id = ?");
    $upd->execute([$razorpaypaymentid, $razorpaysignature, $order['id']]);

    $villagerid = $order['villagerid'];
    $vstmt = $pdo->prepare("SELECT * FROM villager WHERE id = ? AND isdeleted = 0");
    $vstmt->execute([$villagerid]);
    $villager = $vstmt->fetch();

    if ($villager && $villager['paymentstatus'] !== 'paid') {
        $validityyears = getSetting($pdo, 'passvalidityyears', '2');
        $startdate = date('Y-m-d');
        $enddate = calcPassEndDate($startdate, $validityyears);
        $barcode = $villager['barcodetext'] ?: generateBarcodeText($villagerid);

        $vupd = $pdo->prepare("UPDATE villager SET paymentstatus = 'paid', paidat = NOW(), registrationfee = ?,
            passstartdate = ?, passenddate = ?, passactive = 1, barcodetext = ? WHERE id = ?");
        $vupd->execute([moneyStr($order['amount']), $startdate, $enddate, $barcode, $villagerid]);

        logActivity($pdo, 'villager', $villagerid, 'passactivated', 'system', null, 'Pass activated after payment: ' . $razorpayorderid);
    }

    return ['success' => true, 'message' => 'Payment recorded and pass activated'];
}

// When the registration fee setting is 0, the payment step is skipped entirely and the
// pass activates the moment the panchayat approves the villager. Creates a matching
// zero-amount paymentorder row (status 'paid') purely for audit/reporting consistency.
function activateFreePass($pdo, $villagerid) {
    $vstmt = $pdo->prepare("SELECT * FROM villager WHERE id = ? AND isdeleted = 0");
    $vstmt->execute([$villagerid]);
    $villager = $vstmt->fetch();

    if (!$villager || $villager['paymentstatus'] === 'paid') {
        return; // nothing to do / already active
    }

    $validityyears = getSetting($pdo, 'passvalidityyears', '2');
    $startdate = date('Y-m-d');
    $enddate = calcPassEndDate($startdate, $validityyears);
    $barcode = $villager['barcodetext'] ?: generateBarcodeText($villagerid);
    $syntheticorderid = 'FREE-' . $villagerid . '-' . time();

    $vupd = $pdo->prepare("UPDATE villager SET paymentstatus = 'paid', paidat = NOW(), registrationfee = '0.00',
        passstartdate = ?, passenddate = ?, passactive = 1, barcodetext = ? WHERE id = ?");
    $vupd->execute([$startdate, $enddate, $barcode, $villagerid]);

    $ins = $pdo->prepare("INSERT INTO paymentorder (villagerid, razorpayorderid, amount, currency, status, createdbytype, paymentmethod, paidat)
        VALUES (?, ?, '0.00', 'INR', 'paid', 'system', 'free', NOW())");
    $ins->execute([$villagerid, $syntheticorderid]);

    logActivity($pdo, 'villager', $villagerid, 'passactivated', 'system', null, 'Registration fee is Rs. 0 - pass activated immediately on approval');
}

// Returns the panchayatid mapped to a given Aadhaar village name (the 'vtc' QR field),
// or null if the village isn't part of Divar (Golti-Navelim / Malar-Sao Mathias).
// Matching is case-insensitive and trims surrounding whitespace.
function getPanchayatIdByVillage($pdo, $villagename) {
    $stmt = $pdo->prepare("SELECT panchayatid FROM villagepanchayat
        WHERE LOWER(TRIM(villagename)) = LOWER(TRIM(?)) AND isdeleted = 0");
    $stmt->execute([$villagename]);
    $row = $stmt->fetch();
    return $row ? (int)$row['panchayatid'] : null;
}
