<?php
// ============================================================
//  MediCore — Notification Gateways (SMS / WhatsApp / Email)
//  File: includes/gateways.php
//  Generic HTTP callers for SMS/WhatsApp (works with most simple
//  REST-style providers) plus a small raw-socket SMTP client for
//  Email — no external libraries/composer needed, works out of the
//  box on plain XAMPP.
//
//  Everything here is designed to fail SILENTLY (return false,
//  error_log() the detail) rather than break the calling flow —
//  a mis-configured or not-yet-configured gateway should never stop
//  an OTP request, an order placement, or an approval from working.
//
//  Settings keys (see includes/db.php get_settings() for defaults):
//    gw_sms_enabled / gw_sms_api_url / gw_sms_method / gw_sms_api_key /
//      gw_sms_sender_id / gw_sms_auth_header
//    gw_wa_*  (same shape, WhatsApp)
//    gw_email_enabled / gw_email_smtp_host / gw_email_smtp_port /
//      gw_email_smtp_user / gw_email_smtp_pass /
//      gw_email_smtp_encryption (none|tls|ssl) /
//      gw_email_from_address / gw_email_from_name
//
//  gw_sms_api_url / gw_wa_api_url may contain the placeholders
//  {phone} {message} {apikey} {sender} — substituted before the
//  request is made. On POST, the same four values are also always
//  sent as form fields (phone, message, apikey, sender) so providers
//  that expect a request body rather than a query string work too.
//  Because providers vary widely, gw_*_auth_header lets you set one
//  raw header line (e.g. "Authorization: Bearer xxxx") for APIs that
//  need a bearer token instead of a query-string API key.
// ============================================================

// ── Generic HTTP gateway caller (shared by SMS + WhatsApp) ──────
function gateway_http_send(string $url, string $method, array $placeholders, string $authHeader = ''): bool {
    if (!$url) return false;

    $subst = function (string $s) use ($placeholders): string {
        foreach ($placeholders as $key => $val) {
            $s = str_replace($key, rawurlencode((string)$val), $s);
        }
        return $s;
    };

    $method = strtoupper($method) === 'POST' ? 'POST' : 'GET';
    $finalUrl = $subst($url);

    // Plain field names (no braces) for POST body / fallback GET params —
    // covers providers whose docs just say "POST these fields" without
    // any placeholder templating in the URL itself.
    $fields = [];
    foreach ($placeholders as $key => $val) {
        $fields[trim($key, '{}')] = $val;
    }

    $headers = [];
    if ($authHeader !== '') $headers[] = $authHeader;

    try {
        if (function_exists('curl_init')) {
            $ch = curl_init();
            if ($method === 'GET') {
                $sep = str_contains($finalUrl, '?') ? '&' : '?';
                curl_setopt($ch, CURLOPT_URL, $finalUrl . $sep . http_build_query($fields));
            } else {
                curl_setopt($ch, CURLOPT_URL, $finalUrl);
                curl_setopt($ch, CURLOPT_POST, true);
                curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($fields));
                $headers[] = 'Content-Type: application/x-www-form-urlencoded';
            }
            if ($headers) curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_TIMEOUT, 10);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
            $resp = curl_exec($ch);
            $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            $err  = curl_error($ch);
            curl_close($ch);
            if ($resp === false) {
                error_log("gateway_http_send curl error: $err");
                return false;
            }
            if ($code < 200 || $code >= 300) {
                error_log("gateway_http_send non-2xx ($code): " . substr((string)$resp, 0, 300));
                return false;
            }
            return true;
        }

        // Fallback: no cURL extension — use a stream context instead.
        $ctxHeaders = implode("\r\n", array_merge($headers, ['Connection: close']));
        if ($method === 'GET') {
            $sep = str_contains($finalUrl, '?') ? '&' : '?';
            $reqUrl = $finalUrl . $sep . http_build_query($fields);
            $ctx = stream_context_create(['http' => [
                'method' => 'GET', 'header' => $ctxHeaders, 'timeout' => 10, 'ignore_errors' => true,
            ]]);
        } else {
            $body = http_build_query($fields);
            $ctxHeaders .= "\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: " . strlen($body);
            $reqUrl = $finalUrl;
            $ctx = stream_context_create(['http' => [
                'method' => 'POST', 'header' => $ctxHeaders, 'content' => $body,
                'timeout' => 10, 'ignore_errors' => true,
            ]]);
        }
        $resp = @file_get_contents($reqUrl, false, $ctx);
        $statusLine = $http_response_header[0] ?? '';
        preg_match('/\s(\d{3})\s/', $statusLine, $m);
        $code = isset($m[1]) ? (int)$m[1] : 0;
        if ($resp === false || $code < 200 || $code >= 300) {
            error_log("gateway_http_send failed (status $code): " . substr((string)$resp, 0, 300));
            return false;
        }
        return true;
    } catch (Throwable $e) {
        error_log('gateway_http_send exception: ' . $e->getMessage());
        return false;
    }
}

// ── SMS ───────────────────────────────────────────────────────
function send_sms(string $phone, string $message): bool {
    $cfg = get_settings();
    if (($cfg['gw_sms_enabled'] ?? '0') !== '1') return false;
    $url = trim($cfg['gw_sms_api_url'] ?? '');
    if (!$url || !$phone) return false;

    return gateway_http_send($url, $cfg['gw_sms_method'] ?? 'GET', [
        '{phone}'   => $phone,
        '{message}' => $message,
        '{apikey}'  => $cfg['gw_sms_api_key']   ?? '',
        '{sender}'  => $cfg['gw_sms_sender_id'] ?? '',
    ], trim($cfg['gw_sms_auth_header'] ?? ''));
}

// ── WhatsApp ──────────────────────────────────────────────────
function send_whatsapp(string $phone, string $message): bool {
    $cfg = get_settings();
    if (($cfg['gw_wa_enabled'] ?? '0') !== '1') return false;
    $url = trim($cfg['gw_wa_api_url'] ?? '');
    if (!$url || !$phone) return false;

    return gateway_http_send($url, $cfg['gw_wa_method'] ?? 'POST', [
        '{phone}'   => $phone,
        '{message}' => $message,
        '{apikey}'  => $cfg['gw_wa_api_key']   ?? '',
        '{sender}'  => $cfg['gw_wa_sender_id'] ?? '',
    ], trim($cfg['gw_wa_auth_header'] ?? ''));
}

// ── Email (raw SMTP over a socket — no external library needed) ──
function send_email(string $to, string $subject, string $htmlBody): bool {
    $cfg = get_settings();
    if (($cfg['gw_email_enabled'] ?? '0') !== '1') return false;
    $host = trim($cfg['gw_email_smtp_host'] ?? '');
    if (!$host || !$to) return false;

    $port = (int) ($cfg['gw_email_smtp_port'] ?? 587);
    $user = trim($cfg['gw_email_smtp_user'] ?? '');
    $pass = (string) ($cfg['gw_email_smtp_pass'] ?? '');
    $enc  = $cfg['gw_email_smtp_encryption'] ?? 'tls'; // none | tls | ssl
    $fromAddr = trim($cfg['gw_email_from_address'] ?? '') ?: $user;
    $fromName = trim($cfg['gw_email_from_name'] ?? '') ?: portal_shop_name();

    if (!$fromAddr) return false;

    try {
        return smtp_send_mail($host, $port, $enc, $user, $pass, $fromAddr, $fromName, $to, $subject, $htmlBody);
    } catch (Throwable $e) {
        error_log('send_email failed: ' . $e->getMessage());
        return false;
    }
}

// ── Minimal SMTP client ──────────────────────────────────────
// Supports plain, STARTTLS (typically port 587) and implicit SSL
// (typically port 465), with optional AUTH LOGIN.
function smtp_send_mail(
    string $host, int $port, string $enc, string $user, string $pass,
    string $fromAddr, string $fromName, string $to, string $subject, string $htmlBody
): bool {
    $transport = $enc === 'ssl' ? "ssl://$host" : $host;
    $fp = @fsockopen($transport, $port, $errno, $errstr, 10);
    if (!$fp) {
        error_log("smtp_send_mail: connect failed to $host:$port — $errstr ($errno)");
        return false;
    }
    stream_set_timeout($fp, 10);

    $read = function () use ($fp): string {
        $data = '';
        while (($line = fgets($fp, 515)) !== false) {
            $data .= $line;
            if (isset($line[3]) && $line[3] === ' ') break; // last line of a multi-line reply
        }
        return $data;
    };
    $write = function (string $cmd) use ($fp): void {
        fwrite($fp, $cmd . "\r\n");
    };
    $expect = function (string $resp, array $codes) use ($fp): bool {
        $code = (int) substr($resp, 0, 3);
        if (!in_array($code, $codes, true)) {
            error_log("smtp_send_mail: unexpected response: " . trim($resp));
            fclose($fp);
            return false;
        }
        return true;
    };

    $banner = $read();
    if (!$expect($banner, [220])) return false;

    $localHost = $_SERVER['SERVER_NAME'] ?? 'localhost';
    $write("EHLO $localHost");
    $resp = $read();
    if (!$expect($resp, [250])) return false;

    if ($enc === 'tls') {
        $write('STARTTLS');
        $resp = $read();
        if (!$expect($resp, [220])) return false;
        if (!@stream_socket_enable_crypto($fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
            error_log('smtp_send_mail: STARTTLS negotiation failed');
            fclose($fp);
            return false;
        }
        $write("EHLO $localHost");
        $resp = $read();
        if (!$expect($resp, [250])) return false;
    }

    if ($user !== '') {
        $write('AUTH LOGIN');
        $resp = $read();
        if (!$expect($resp, [334])) return false;
        $write(base64_encode($user));
        $resp = $read();
        if (!$expect($resp, [334])) return false;
        $write(base64_encode($pass));
        $resp = $read();
        if (!$expect($resp, [235])) return false;
    }

    $write("MAIL FROM:<$fromAddr>");
    $resp = $read();
    if (!$expect($resp, [250])) return false;

    $write("RCPT TO:<$to>");
    $resp = $read();
    if (!$expect($resp, [250, 251])) return false;

    $write('DATA');
    $resp = $read();
    if (!$expect($resp, [354])) return false;

    $boundary = uniqid('mc_');
    $headers  = [
        'From: ' . mb_encode_mimeheader($fromName) . " <$fromAddr>",
        "To: <$to>",
        'Subject: ' . mb_encode_mimeheader($subject),
        'MIME-Version: 1.0',
        'Content-Type: text/html; charset=UTF-8',
        'Content-Transfer-Encoding: 8bit',
        'Date: ' . date('r'),
    ];
    // Dot-stuff any line that starts with a lone "." per RFC 5321.
    $body = preg_replace('/^\./m', '..', $htmlBody);

    $write(implode("\r\n", $headers) . "\r\n\r\n" . $body . "\r\n.");
    $resp = $read();
    if (!$expect($resp, [250])) return false;

    $write('QUIT');
    fclose($fp);
    return true;
}

// ============================================================
//  Trigger wrappers — called from the OTP/order/due-notification
//  flows. Each one tries every enabled channel and never throws.
// ============================================================

// ── OTP / login code ──────────────────────────────────────────
function send_otp_code(string $phone, string $otp, ?string $email = null): bool {
    $shop = portal_shop_name();
    $msg  = "Your $shop verification code is: $otp (valid 5 minutes)";
    $ok = false;
    if (send_sms($phone, $msg))      $ok = true;
    if (send_whatsapp($phone, $msg)) $ok = true;
    if ($email) {
        $sent = send_email($email, "Your $shop verification code",
            "<p>Your verification code is <strong>$otp</strong>.</p><p>This code is valid for 5 minutes.</p>");
        if ($sent) $ok = true;
    }
    return $ok;
}

// ── Order placed (pending review) — confirmation to the customer ──
function notify_order_placed(string $phone, ?string $email, string $orderNo, float $total): void {
    $shop = portal_shop_name();
    $cur  = get_settings()['currency'];
    $msg  = "$shop: Order $orderNo received (Total $cur" . number_format($total, 2) . "). We'll confirm shortly.";
    send_sms($phone, $msg);
    send_whatsapp($phone, $msg);
    if ($email) {
        send_email($email, "Order Confirmation — $orderNo",
            "<p>Thank you! Your order <strong>$orderNo</strong> (Total $cur" . number_format($total, 2) . ") has been received and is pending confirmation.</p>");
    }
}

// ── Order approved / rejected — status update to the customer ────
function notify_order_status(string $phone, ?string $email, string $orderNo, string $status, string $reason = ''): void {
    $shop = portal_shop_name();
    if ($status === 'approved') {
        $msg  = "$shop: Your order $orderNo has been approved and is being prepared.";
        $subj = "Order Approved — $orderNo";
    } else {
        $msg  = "$shop: Your order $orderNo was not approved." . ($reason ? " Reason: $reason" : '');
        $subj = "Order Update — $orderNo";
    }
    send_sms($phone, $msg);
    send_whatsapp($phone, $msg);
    if ($email) send_email($email, $subj, "<p>" . htmlspecialchars($msg) . "</p>");
}

// ── Outstanding due reminder ─────────────────────────────────
function notify_due_update(string $phone, string $customerName, float $due): void {
    if ($due <= 0 || !$phone) return;
    $shop = portal_shop_name();
    $cur  = get_settings()['currency'];
    $msg  = "$shop: Hi $customerName, your outstanding due is $cur" . number_format($due, 2) . ".";
    send_sms($phone, $msg);
    send_whatsapp($phone, $msg);
}
