<?php
declare(strict_types=1);

session_start();
require_once 'config.php';
require_once 'includes/canteen_helpers.php';
require_once 'includes/payment_helpers.php';
$conn = getDBConnection();

ini_set('display_errors', '0');
error_reporting(E_ALL);

http_response_code(200);
header('Content-Type: application/json');

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    echo json_encode(['status' => 'ok']);
    exit;
}

if (!$conn) {
    echo json_encode(['status' => 'ok']);
    exit;
}

/**
 * Authenticate the caller with a shared secret.
 *
 * Hubtel does not sign callbacks, so we require an unguessable secret that is
 * configured on BOTH ends: HUBTEL_WEBHOOK_SECRET in .env, and appended to the
 * callback URL registered with Hubtel (e.g. ...hubtel.php?secret=XXXX) or sent
 * as HTTP Basic Auth / an X-Webhook-Secret header. Without a valid secret the
 * request is rejected, so a "ResponseCode=0000" body can no longer be forged.
 */
function hubtelSecretPresented(): string
{
    $header = $_SERVER['HTTP_X_WEBHOOK_SECRET'] ?? '';
    if ($header !== '') {
        return (string) $header;
    }
    if (!empty($_SERVER['PHP_AUTH_PW'])) {
        return (string) $_SERVER['PHP_AUTH_PW'];
    }
    return (string) ($_GET['secret'] ?? '');
}

$configuredSecret = defined('HUBTEL_WEBHOOK_SECRET') ? (string) HUBTEL_WEBHOOK_SECRET : '';
if ($configuredSecret === '') {
    // Not yet configured: log loudly so it is not silently left open in production.
    error_log('SECURITY: HUBTEL_WEBHOOK_SECRET is not set — Hubtel webhook is unauthenticated. Set it in .env and on the Hubtel callback URL.');
} elseif (!hash_equals($configuredSecret, hubtelSecretPresented())) {
    error_log('Hubtel webhook rejected: bad or missing secret from ' . ($_SERVER['REMOTE_ADDR'] ?? '?'));
    http_response_code(401);
    echo json_encode(['status' => 'unauthorized']);
    exit;
}

function logHubtelWebhook(mysqli $conn, string $payload, string $status = 'pending'): void
{
    $recipient = 'hubtel';
    $type = 'hubtel_webhook';
    $stmt = $conn->prepare(
        "INSERT INTO sms_logs (recipient, message, status, type, sent_at)
         VALUES (?, ?, ?, ?, NOW())"
    );
    $stmt->bind_param('ssss', $recipient, $payload, $status, $type);
    $stmt->execute();
    $stmt->close();
}

function fetchHubtelPayment(mysqli $conn, string $reference): ?array
{
    $stmt = $conn->prepare(
        "SELECT op.*, s.first_name, s.last_name
         FROM online_payments op
         INNER JOIN students s ON s.id = op.student_id
         WHERE op.gateway_reference = ?
         LIMIT 1"
    );
    $stmt->bind_param('s', $reference);
    $stmt->execute();
    $result = $stmt->get_result();
    $row = $result->fetch_assoc();
    $stmt->close();
    return $row ?: null;
}

/**
 * Fan a single Hubtel transaction back out into one `payments` row per fee
 * item, split proportionally to each item's own amount (not evenly — see
 * proportionalPaymentShares()), and generate canteen entitlements for any
 * feeding-fee item covered. Online payments never reached
 * canteen_auto_generate_entitlement() before; the webhooks were the one
 * payment path it wasn't wired into, so a feeding fee paid via mobile money
 * used to grant zero canteen access. weeks_covered comes from the checkout
 * form now (online_payments.weeks_covered) instead of always defaulting to
 * 1 week regardless of how much was actually paid.
 *
 * @return array<int, string> The fee_type of every item paid, for the SMS.
 */
function insertHubtelPaymentRows(mysqli $conn, array $payment): array
{
    $feeIds = json_decode((string) $payment['fee_structure_ids'], true);
    if (!is_array($feeIds) || $feeIds === []) {
        $feeIds = [null];
    }
    $feeIds = array_map(static fn($id) => $id !== null ? (int) $id : null, $feeIds);

    $details = fetchFeeStructureDetails($conn, $feeIds);
    $weights = array_map(static fn($d) => $d['amount'], $details);
    $shares  = proportionalPaymentShares($feeIds, $weights, (float) $payment['amount']);
    $weeksCovered = max(1, (int) ($payment['weeks_covered'] ?? 1));

    $studentId     = (int) $payment['student_id'];
    $method        = 'online';
    $transactionId = (string) $payment['gateway_reference'];
    $notes         = 'Online payment via Hubtel mobile money';

    $stmt = $conn->prepare(
        "INSERT INTO payments
         (student_id, fee_structure_id, amount_paid, payment_date, payment_method, transaction_id, notes)
         VALUES (?, ?, ?, NOW(), ?, ?, ?)"
    );

    $feeTypesPaid = [];
    foreach ($feeIds as $index => $feeId) {
        $amountPaid = $shares[$index];
        $stmt->bind_param('iidsss', $studentId, $feeId, $amountPaid, $method, $transactionId, $notes);
        $stmt->execute();
        $paymentId = $stmt->insert_id;

        if ($feeId !== null && isset($details[$feeId])) {
            $feeTypesPaid[] = $details[$feeId]['fee_type'];
            if (function_exists('canteen_auto_generate_entitlement')) {
                canteen_auto_generate_entitlement($studentId, $paymentId, $details[$feeId]['fee_type'], $weeksCovered);
            }
        }
    }
    $stmt->close();

    return $feeTypesPaid;
}

$rawBody = file_get_contents('php://input') ?: '{}';
logHubtelWebhook($conn, $rawBody);
$payload = json_decode($rawBody, true);

if (!is_array($payload) || empty($payload['Data']['ClientReference'])) {
    echo json_encode(['status' => 'ok']);
    exit;
}

$reference = (string) $payload['Data']['ClientReference'];
$payment = fetchHubtelPayment($conn, $reference);
if (!$payment) {
    echo json_encode(['status' => 'ok']);
    exit;
}

if ($payment['status'] === 'success') {
    echo json_encode(['status' => 'ok']);
    exit;
}

$responseJson = json_encode($payload, JSON_UNESCAPED_UNICODE);
$success = (string) ($payload['ResponseCode'] ?? '') === '0000';

if ($success) {
    $conn->begin_transaction();
    try {
        $paymentId = (int) $payment['id'];
        $stmt = $conn->prepare(
            "UPDATE online_payments
             SET status='success', gateway_status='success', gateway_response=?, completed_at=NOW()
             WHERE id=?"
        );
        $stmt->bind_param('si', $responseJson, $paymentId);
        $stmt->execute();
        $stmt->close();

        $feeTypesPaid = insertHubtelPaymentRows($conn, $payment);

        // Record coupon usage if a coupon was applied
        if (!empty($payment['coupon_id'])) {
            $couponId = (int) $payment['coupon_id'];
            $cuStmt = $conn->prepare("INSERT IGNORE INTO coupon_usages (coupon_id, student_id, payment_id, discount_amount, used_at) VALUES (?, ?, NULL, ?, NOW())");
            $cuStmt->bind_param('iid', $couponId, $payment['student_id'], $payment['discount_amount']);
            $cuStmt->execute();
            $cuStmt->close();
            $conn->query("UPDATE coupons SET times_used = times_used + 1 WHERE id = {$couponId}");
        }

        $conn->commit();
    } catch (Throwable $e) {
        $conn->rollback();
        error_log('Hubtel webhook failed: ' . $e->getMessage());
        echo json_encode(['status' => 'ok']);
        exit;
    }

    if (!empty($payment['parent_phone'])) {
        $studentName = trim($payment['first_name'] . ' ' . $payment['last_name']);
        $itemsPhrase = $feeTypesPaid !== [] ? ' covering ' . implode(', ', $feeTypesPaid) : '';
        $message = 'EduTrack: Payment of GHS ' . number_format((float) $payment['amount'], 2) . ' received for ' . $studentName . $itemsPhrase . '. Receipt: ' . $payment['receipt_number'] . '. Thank you.';
        sendSMS((string) $payment['parent_phone'], $message);
    }
} else {
    $paymentId = (int) $payment['id'];
    $gatewayStatus = (string) ($payload['ResponseCode'] ?? 'failed');
    $stmt = $conn->prepare(
        "UPDATE online_payments
         SET status='failed', gateway_status=?, gateway_response=?
         WHERE id=?"
    );
    $stmt->bind_param('ssi', $gatewayStatus, $responseJson, $paymentId);
    $stmt->execute();
    $stmt->close();
    if (!empty($payment['parent_phone'])) {
        $message = 'EduTrack: Your payment of GHS ' . number_format((float) $payment['amount'], 2) . ' was not completed. Please try again or contact the school.';
        sendSMS((string) $payment['parent_phone'], $message);
    }
}

echo json_encode(['status' => 'ok']);
