<?php
// ============================================================
//  MediCore — Customer Portal Auth API (phone + OTP + password)
//  File: api/customer-auth.php
//  Public endpoint (no staff login). Accepts JSON POST, returns JSON.
//
//  OTP delivery goes through includes/gateways.php's send_otp_code()
//  (SMS/WhatsApp/Email, whichever gateways are enabled in Settings).
//  If none are configured, send_otp_code() sends nothing and this
//  file falls back to demo mode — the code is returned directly in
//  the JSON response as `debug_otp` and shown on-screen.
//
//  Password login is optional: a customer who has never set a
//  password can only log in via OTP. Once they set one (from
//  shop/profile.php's "set_password" action), phone+password
//  becomes a second valid login path — OTP still always works too,
//  since it doubles as the account-recovery mechanism.
// ============================================================
require_once '../includes/customer_auth.php';
// NOTE: unlike most api/*.php files, session_write_close() must NOT be
// called here — verify_otp()/login_password() (customer_login_session())
// and logout() all WRITE to $_SESSION, and anything written after the
// session is closed is silently discarded, never persisted. That was the
// original bug: OTP verification "succeeded" for one request but the
// login never stuck, bouncing the customer straight back to the login page.

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

$body   = json_decode(file_get_contents('php://input'), true) ?? [];
$action = trim($body['action'] ?? '');

try {
    match ($action) {
        'request_otp'    => api_request_otp($body),
        'verify_otp'     => api_verify_otp($body),
        'login_password' => api_login_password($body),
        'logout'         => api_logout($body),
        'me'             => api_me($body),
        'update_profile' => api_update_profile($body),
        'set_password'   => api_set_password($body),
        default          => fail('Unknown action'),
    };
} catch (Throwable $e) {
    fail($e->getMessage());
}

// ── Request OTP (covers both new registration and returning login —
//    the phone number decides which one this turns out to be) ─────
function api_request_otp(array $b): void {
    if (!portal_enabled()) fail('Online ordering is currently unavailable.');

    $phone = trim($b['phone'] ?? '');
    $name  = trim($b['name']  ?? '');

    if (!preg_match('/^[0-9+\-\s]{7,20}$/', $phone)) {
        fail('Enter a valid phone number.');
    }

    // ORDER BY id DESC — if an older duplicate row happens to share
    // this phone number (e.g. a walk-in customer entered in-store),
    // always operate on the most recently touched one deterministically,
    // so request_otp() and verify_otp() never disagree on which row
    // the code belongs to.
    $customer = db_row("SELECT * FROM customers WHERE phone = ? ORDER BY id DESC LIMIT 1", [$phone]);

    if (!$customer) {
        // Brand-new phone number — create the customer row now (name
        // may be blank at this stage; OTP verification still gates
        // whether it's ever usable to place an order).
        $newId = db_insert(
            "INSERT INTO customers (name, phone) VALUES (?, ?)",
            [$name ?: 'Customer', $phone]
        );
        $customer = db_row("SELECT * FROM customers WHERE id = ?", [$newId]);
    } elseif ($name && $customer['name'] === '') {
        db_run("UPDATE customers SET name = ? WHERE id = ?", [$name, $customer['id']]);
    }

    // Basic resend cooldown — block re-requests within 30s of the
    // previous OTP still being "fresh". Compared entirely in SQL
    // (MySQL's own NOW() against its own stored value) rather than
    // mixing in PHP's time(), so a clock/timezone difference between
    // the PHP and MySQL servers can never make this misbehave.
    if (!empty($customer['otp_expires_at'])) {
        $stillFresh = (bool) db_val(
            "SELECT TIMESTAMPDIFF(SECOND, NOW(), otp_expires_at) > 270 FROM customers WHERE id = ?",
            [$customer['id']]
        );
        if ($stillFresh) {
            fail('Please wait a few seconds before requesting another code.');
        }
    }

    $otp = generate_otp();
    db_run(
        "UPDATE customers
            SET otp_code = ?, otp_expires_at = DATE_ADD(NOW(), INTERVAL 5 MINUTE),
                otp_attempts = 0
          WHERE id = ?",
        [$otp, $customer['id']]
    );

    // Try every gateway that's enabled in Settings (SMS/WhatsApp/Email);
    // falls back to demo mode (code shown on-screen) if none succeed.
    $sent = send_otp_code($phone, $otp, $customer['email'] ?: null);

    ok([
        'phone'       => $phone,
        'is_new'      => empty($customer['portal_verified']),
        'sms_sent'    => $sent,
        'debug_otp'   => $sent ? null : $otp, // demo mode only — omitted once a gateway actually sends it
        'msg'         => $sent
            ? 'A verification code has been sent to your phone.'
            : 'Demo mode: no gateway configured yet — your code is shown on screen.',
    ]);
}

// ── Verify OTP → log the customer in ────────────────────────────
function api_verify_otp(array $b): void {
    $phone = trim($b['phone'] ?? '');
    $otp   = trim($b['otp']   ?? '');
    if (!$phone || !$otp) fail('Phone and code are required.');

    $customer = db_row("SELECT * FROM customers WHERE phone = ? ORDER BY id DESC LIMIT 1", [$phone]);
    if (!$customer) fail('No verification in progress for this number.');

    if (empty($customer['otp_code']) || empty($customer['otp_expires_at'])) {
        fail('No active code — please request a new one.');
    }
    // Expiry judged entirely by MySQL's own clock (NOW() vs. the
    // value it generated for otp_expires_at) — never compared against
    // PHP's time(), so a PHP/MySQL server clock or timezone mismatch
    // can't make a fresh code look expired.
    $expired = (bool) db_val(
        "SELECT otp_expires_at < NOW() FROM customers WHERE id = ?",
        [$customer['id']]
    );
    if ($expired) {
        fail('This code has expired — please request a new one.');
    }
    if ((int)$customer['otp_attempts'] >= 5) {
        fail('Too many incorrect attempts — please request a new code.');
    }

    if (!hash_equals((string)$customer['otp_code'], $otp)) {
        db_run("UPDATE customers SET otp_attempts = otp_attempts + 1 WHERE id = ?", [$customer['id']]);
        $left = max(0, 5 - ((int)$customer['otp_attempts'] + 1));
        fail("Incorrect code. $left attempt(s) remaining.");
    }

    db_run(
        "UPDATE customers
            SET portal_verified = 1, portal_last_login = NOW(),
                otp_code = NULL, otp_expires_at = NULL, otp_attempts = 0
          WHERE id = ?",
        [$customer['id']]
    );
    $customer = db_row("SELECT * FROM customers WHERE id = ?", [$customer['id']]);

    customer_login_session($customer);

    ok([
        'customer' => [
            'id'    => (int)$customer['id'],
            'name'  => $customer['name'],
            'phone' => $customer['phone'],
        ],
        'msg' => 'Logged in successfully.',
    ]);
}

// ── Phone + password login (only works once a password has been set
//    from the profile page — OTP always remains available too) ────
function api_login_password(array $b): void {
    if (!portal_enabled()) fail('Online ordering is currently unavailable.');

    $phone = trim($b['phone'] ?? '');
    $pass  = (string) ($b['password'] ?? '');
    if (!$phone || $pass === '') fail('Phone and password are required.');

    $customer = db_row("SELECT * FROM customers WHERE phone = ? ORDER BY id DESC LIMIT 1", [$phone]);
    if (!$customer || empty($customer['password_hash'])) {
        fail('No password set for this number — please log in with a verification code instead.');
    }
    if (!password_verify($pass, $customer['password_hash'])) {
        fail('Incorrect phone number or password.');
    }

    db_run("UPDATE customers SET portal_last_login = NOW() WHERE id = ?", [$customer['id']]);
    customer_login_session($customer);

    ok([
        'customer' => [
            'id'    => (int)$customer['id'],
            'name'  => $customer['name'],
            'phone' => $customer['phone'],
        ],
        'msg' => 'Logged in successfully.',
    ]);
}

// ── Logout ────────────────────────────────────────────────────
function api_logout(array $b): void {
    unset($_SESSION['customer_id'], $_SESSION['customer_name'], $_SESSION['customer_phone']);
    ok(['msg' => 'Logged out.']);
}

// ── Current session info ─────────────────────────────────────
function api_me(array $b): void {
    if (!customer_logged_in()) {
        echo json_encode(['success' => true, 'logged_in' => false]);
        return;
    }
    $customer = db_row(
        "SELECT id,name,phone,address,email,due,advance,profile_pic,
                (password_hash IS NOT NULL) AS has_password
           FROM customers WHERE id = ?",
        [current_customer_id()]
    );
    if (!$customer) {
        echo json_encode(['success' => true, 'logged_in' => false]);
        return;
    }
    $customer['has_password'] = (bool)$customer['has_password'];
    ok(['logged_in' => true, 'customer' => $customer]);
}

// ── Update profile (name / address / email / profile picture) ────
function api_update_profile(array $b): void {
    require_customer_login();
    $id = current_customer_id();

    $name    = trim($b['name']    ?? '');
    $address = trim($b['address'] ?? '');
    $email   = trim($b['email']   ?? '');
    if ($name === '') fail('Name is required.');
    if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
        fail('Enter a valid email address.');
    }

    $sql    = "UPDATE customers SET name = ?, address = ?, email = ?";
    $params = [$name, $address, $email !== '' ? $email : null];

    // Profile picture arrives as a base64 data-URI from the browser's
    // FileReader (same client-side pattern as Settings > Logo — this
    // app has no server-side upload/file-storage mechanism anywhere).
    if (array_key_exists('profile_pic', $b)) {
        $pic = trim((string) $b['profile_pic']);
        if ($pic !== '' && !str_starts_with($pic, 'data:image/')) {
            fail('Invalid profile picture format.');
        }
        // Soft cap so a customer can't wedge a huge blob into the DB row.
        if ($pic !== '' && strlen($pic) > 2_000_000) {
            fail('Profile picture is too large — please use a smaller image.');
        }
        $sql    .= ", profile_pic = ?";
        $params[] = $pic !== '' ? $pic : null;
    }

    $sql .= " WHERE id = ?";
    $params[] = $id;
    db_run($sql, $params);

    $_SESSION['customer_name'] = $name;

    $customer = db_row(
        "SELECT id,name,phone,address,email,due,advance,profile_pic,
                (password_hash IS NOT NULL) AS has_password
           FROM customers WHERE id = ?",
        [$id]
    );
    $customer['has_password'] = (bool)$customer['has_password'];
    ok(['customer' => $customer, 'msg' => 'Profile updated.']);
}

// ── Set / change password (OTP-verified session is the auth — if a
//    password is already set, the current one must be supplied too) ──
function api_set_password(array $b): void {
    require_customer_login();
    $id = current_customer_id();

    $newPass = (string) ($b['new_password'] ?? '');
    $curPass = (string) ($b['current_password'] ?? '');
    if (strlen($newPass) < 6) fail('New password must be at least 6 characters.');

    $customer = db_row("SELECT password_hash FROM customers WHERE id = ?", [$id]);
    if (!empty($customer['password_hash'])) {
        if ($curPass === '' || !password_verify($curPass, $customer['password_hash'])) {
            fail('Current password is incorrect.');
        }
    }

    db_run(
        "UPDATE customers SET password_hash = ? WHERE id = ?",
        [password_hash($newPass, PASSWORD_DEFAULT), $id]
    );

    ok(['msg' => 'Password saved. You can now log in with your phone number and password.']);
}

// ── Response helpers ──────────────────────────────────────────
function ok(array $extra = []): void {
    echo json_encode(['success' => true] + $extra);
}
function fail(string $msg): never {
    echo json_encode(['success' => false, 'error' => $msg]);
    exit;
}
