<?php

const GPS_PRODUCT_ID = 'stok_pro_premium';
const GPS_PACKAGE_NAME = 'com.stokkupro';

const GPS_BASE_PLAN_BASIC = 'medium';
const GPS_BASE_PLAN_PRO = 'pro';

const GPS_COMPANY_TABLE = 'companies';
const GPS_COMPANY_ID_COLUMN = 'id';

const GPS_SUBSCRIPTION_TABLE = 'store_subscriptions';
const GPS_SUBSCRIPTION_COMPANY_COLUMN = 'store_id';
const GPS_RTND_LOG_TABLE = 'google_play_rtdn_events';

const GPS_FREE_TRIAL_DAYS = 7;
const GPS_FREE_LIMIT = 5;
const GPS_BASIC_LIMIT = 1000;
const GPS_PRO_LIMIT = 5000;

function gps_json_response($success, $message, $data = null, $httpCode = 200)
{
    http_response_code($httpCode);

    $response = [
        'success' => (bool)$success,
        'message' => $message,
        'request_id' => gps_get_request_id(),
    ];

    if ($data !== null) {
        $response['data'] = $data;
    }

    gps_log(
        $success ? 'response success' : 'response error',
        [
            'http_code' => $httpCode,
            'message' => $message,
            'has_data' => $data !== null,
        ],
        $success ? 'INFO' : 'ERROR'
    );

    echo json_encode($response, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    exit;
}

function gps_generate_request_id()
{
    try {
        return 'gps_' . bin2hex(random_bytes(8));
    } catch (Throwable $e) {
        return 'gps_' . str_replace('.', '', uniqid('', true));
    }
}

function gps_set_request_id($requestId)
{
    $GLOBALS['gps_request_id'] = trim((string)$requestId) !== '' ? trim((string)$requestId) : gps_generate_request_id();
}

function gps_reserve_emergency_memory()
{
    if (!isset($GLOBALS['gps_emergency_memory_reserve'])) {
        $GLOBALS['gps_emergency_memory_reserve'] = str_repeat('R', 262144);
    }
}

function gps_get_request_id()
{
    if (empty($GLOBALS['gps_request_id'])) {
        $GLOBALS['gps_request_id'] = gps_generate_request_id();
    }

    return $GLOBALS['gps_request_id'];
}

function gps_mask_string($value)
{
    $value = (string)$value;
    $length = strlen($value);

    if ($length <= 8) {
        return str_repeat('*', $length);
    }

    return substr($value, 0, 4) . str_repeat('*', max(0, $length - 8)) . substr($value, -4);
}

function gps_limit_string($value, $limit = 800)
{
    $value = (string)$value;

    if (strlen($value) <= $limit) {
        return $value;
    }

    return substr($value, 0, $limit) . '...<truncated>';
}

function gps_resolve_log_file()
{
    $envPath = trim((string)getenv('GOOGLE_PLAY_LOG_FILE'));
    if ($envPath !== '') {
        return $envPath;
    }

    return dirname(__DIR__) . '/logs/google-play-subscription.log';
}

function gps_write_log_line($line)
{
    error_log($line);

    $logFile = gps_resolve_log_file();
    if ($logFile === '') {
        return;
    }

    $logDir = dirname($logFile);
    if (!is_dir($logDir) && !@mkdir($logDir, 0775, true) && !is_dir($logDir)) {
        return;
    }

    error_log($line . PHP_EOL, 3, $logFile);
}

function gps_sanitize_log_value($value, $key = '')
{
    $lowerKey = strtolower((string)$key);

    if (is_array($value)) {
        $sanitized = [];
        foreach ($value as $itemKey => $itemValue) {
            $sanitized[$itemKey] = gps_sanitize_log_value($itemValue, (string)$itemKey);
        }
        return $sanitized;
    }

    if (is_object($value)) {
        return gps_sanitize_log_value(json_decode(json_encode($value), true), $key);
    }

    if (is_string($value)) {
        $value = preg_replace(
            '#(/tokens/|/subscriptionsv2/tokens/)([^/:?]+)#',
            '$1[masked]',
            $value
        );

        if (
            strpos($lowerKey, 'token') !== false ||
            strpos($lowerKey, 'secret') !== false ||
            strpos($lowerKey, 'private_key') !== false ||
            strpos($lowerKey, 'authorization') !== false
        ) {
            return gps_mask_string($value);
        }

        return gps_limit_string($value);
    }

    return $value;
}

function gps_log($message, $context = [], $level = 'INFO')
{
    $safeContext = gps_sanitize_log_value($context);

    gps_write_log_line(
        '[google_play_subscription][' . $level . '][' . gps_get_request_id() . '] '
        . $message
        . (empty($safeContext) ? '' : ' ' . json_encode($safeContext, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES))
    );
}

function gps_log_exception($message, Throwable $e, $context = [])
{
    $context['error_class'] = get_class($e);
    $context['error_message'] = $e->getMessage();
    $context['error_file'] = $e->getFile();
    $context['error_line'] = $e->getLine();
    $context['trace'] = gps_limit_string($e->getTraceAsString(), 4000);

    gps_log($message, $context, 'ERROR');
}

function gps_register_shutdown_logger($label)
{
    register_shutdown_function(function () use ($label) {
        if (isset($GLOBALS['gps_emergency_memory_reserve'])) {
            $GLOBALS['gps_emergency_memory_reserve'] = null;
        }

        $error = error_get_last();

        if ($error === null) {
            return;
        }

        $fatalTypes = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR];
        if (!in_array((int)$error['type'], $fatalTypes, true)) {
            return;
        }

        $line = '[google_play_subscription][FATAL_SHUTDOWN]['
            . gps_get_request_id()
            . '] '
            . $label
            . ' type=' . (int)($error['type'] ?? 0)
            . ' message=' . ($error['message'] ?? '')
            . ' file=' . ($error['file'] ?? '')
            . ' line=' . (int)($error['line'] ?? 0);

        error_log($line);
    });
}

function gps_get_bearer_token()
{
    $headers = function_exists('getallheaders') ? getallheaders() : [];
    $authHeader = '';

    foreach ($headers as $key => $value) {
        if (strtolower($key) === 'authorization') {
            $authHeader = $value;
            break;
        }
    }

    if ($authHeader === '' && isset($_SERVER['HTTP_AUTHORIZATION'])) {
        $authHeader = $_SERVER['HTTP_AUTHORIZATION'];
    }

    if ($authHeader === '' && isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) {
        $authHeader = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
    }

    if (!preg_match('/Bearer\s+(.+)/i', $authHeader, $matches)) {
        return '';
    }

    return trim($matches[1]);
}

function gps_require_auth_user()
{
    $token = gps_get_bearer_token();

    if ($token === '') {
        gps_json_response(false, 'Token login wajib dikirim', null, 401);
    }

    if (function_exists('verify_jwt')) {
        $user = verify_jwt($token);
    } elseif (function_exists('verifyJWT')) {
        $user = verifyJWT($token);
    } elseif (function_exists('validate_jwt')) {
        $user = validate_jwt($token);
    } elseif (function_exists('validateJWT')) {
        $user = validateJWT($token);
    } elseif (function_exists('decode_jwt')) {
        $user = decode_jwt($token);
    } elseif (function_exists('decodeJWT')) {
        $user = decodeJWT($token);
    } else {
        gps_json_response(false, 'Fungsi validasi JWT tidak ditemukan di config/jwt.php', null, 500);
    }

    if (is_object($user)) {
        $user = json_decode(json_encode($user), true);
    }

    if (!is_array($user)) {
        gps_json_response(false, 'Token login tidak valid', null, 401);
    }

    return $user;
}

function gps_get_company_id_from_auth(array $authUser)
{
    $keys = ['company_id', 'store_id', 'companyId', 'storeId'];

    foreach ($keys as $key) {
        if (!empty($authUser[$key])) {
            return trim((string)$authUser[$key]);
        }
    }

    if (!empty($authUser['data']) && is_array($authUser['data'])) {
        return gps_get_company_id_from_auth($authUser['data']);
    }

    if (!empty($authUser['user']) && is_array($authUser['user'])) {
        return gps_get_company_id_from_auth($authUser['user']);
    }

    return '';
}

function gps_get_user_id_from_auth(array $authUser)
{
    $keys = ['uuid', 'user_uuid', 'user_id', 'id', 'sub', 'uid', 'userId'];

    foreach ($keys as $key) {
        if (!empty($authUser[$key])) {
            return trim((string)$authUser[$key]);
        }
    }

    if (!empty($authUser['data']) && is_array($authUser['data'])) {
        return gps_get_user_id_from_auth($authUser['data']);
    }

    if (!empty($authUser['user']) && is_array($authUser['user'])) {
        return gps_get_user_id_from_auth($authUser['user']);
    }

    return '';
}

function gps_get_db_connection()
{
    global $conn, $koneksi;

    if (isset($conn) && $conn instanceof mysqli) {
        return $conn;
    }

    if (isset($koneksi) && $koneksi instanceof mysqli) {
        return $koneksi;
    }

    gps_json_response(false, 'Koneksi database tidak tersedia', null, 500);
}

function gps_stmt_fetch_assoc($stmt)
{
    if (function_exists('mysqli_stmt_get_result')) {
        $result = mysqli_stmt_get_result($stmt);
        if ($result instanceof mysqli_result) {
            $row = $result->fetch_assoc();
            $result->free();
            return $row ?: null;
        }
    }

    $metadata = $stmt->result_metadata();
    if (!$metadata) {
        return null;
    }

    $row = [];
    $bindValues = [];
    $fields = $metadata->fetch_fields();

    foreach ($fields as $field) {
        $row[$field->name] = null;
        $bindValues[] = &$row[$field->name];
    }

    call_user_func_array([$stmt, 'bind_result'], $bindValues);

    if (!$stmt->fetch()) {
        return null;
    }

    $result = [];
    foreach ($row as $key => $value) {
        $result[$key] = $value;
    }

    return $result;
}

function gps_company_exists($mysqli, $companyId)
{
    $sql = 'SELECT ' . GPS_COMPANY_ID_COLUMN . ' FROM ' . GPS_COMPANY_TABLE . ' WHERE ' . GPS_COMPANY_ID_COLUMN . ' = ? LIMIT 1';
    $stmt = $mysqli->prepare($sql);

    if (!$stmt) {
        gps_log('db prepare failed', [
            'operation' => 'company_exists',
            'db_error' => $mysqli->error,
            'company_id' => $companyId,
        ], 'ERROR');
        gps_json_response(false, 'Gagal menyiapkan query company', null, 500);
    }

    $stmt->bind_param('s', $companyId);
    $stmt->execute();
    $result = gps_stmt_fetch_assoc($stmt);
    $stmt->close();

    return $result ?: null;
}

function gps_base64url_encode($data)
{
    return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}

function gps_resolve_service_account_path()
{
    $envPath = getenv('GOOGLE_PLAY_SERVICE_ACCOUNT_PATH');

    if ($envPath && file_exists($envPath)) {
        return $envPath;
    }

    $candidates = [
        __DIR__ . '/../config/stokpro-4287a-277139bf3623.json',
        __DIR__ . '/../config/stokpro-4287a-277139bf3623.json',
        dirname(__DIR__) . '/config/stokpro-4287a-277139bf3623.json',
        dirname(__DIR__) . '/config/stokpro-4287a-277139bf3623.json',
    ];

    foreach ($candidates as $path) {
        if (file_exists($path)) {
            return $path;
        }
    }

    return null;
}

function gps_get_google_access_token()
{
    $serviceAccountPath = gps_resolve_service_account_path();

    gps_log('google access token request started', [
        'service_account_path' => $serviceAccountPath,
    ]);

    if (!$serviceAccountPath) {
        gps_json_response(false, 'File service account Google tidak ditemukan', null, 500);
    }

    $serviceAccount = json_decode(file_get_contents($serviceAccountPath), true);

    if (
        !is_array($serviceAccount) ||
        empty($serviceAccount['client_email']) ||
        empty($serviceAccount['private_key'])
    ) {
        gps_json_response(false, 'Konfigurasi service account Google tidak valid', null, 500);
    }

    $tokenUri = $serviceAccount['token_uri'] ?? 'https://oauth2.googleapis.com/token';
    $now = time();

    $jwtHeader = gps_base64url_encode(json_encode(['alg' => 'RS256', 'typ' => 'JWT']));
    $jwtClaim = gps_base64url_encode(json_encode([
        'iss' => $serviceAccount['client_email'],
        'scope' => 'https://www.googleapis.com/auth/androidpublisher',
        'aud' => $tokenUri,
        'exp' => $now + 3600,
        'iat' => $now,
    ]));

    $signingInput = $jwtHeader . '.' . $jwtClaim;
    $signature = '';

    if (!openssl_sign($signingInput, $signature, $serviceAccount['private_key'], OPENSSL_ALGO_SHA256)) {
        gps_json_response(false, 'Gagal membuat signature Google OAuth', null, 500);
    }

    $jwt = $signingInput . '.' . gps_base64url_encode($signature);
    $response = gps_google_http_request(
        'POST',
        $tokenUri,
        null,
        http_build_query([
            'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
            'assertion' => $jwt,
        ]),
        ['Content-Type: application/x-www-form-urlencoded']
    );

    if ($response['http_code'] < 200 || $response['http_code'] >= 300 || empty($response['json']['access_token'])) {
        $message = $response['json']['error_description'] ?? $response['json']['error'] ?? 'Gagal mendapatkan access token Google';
        gps_json_response(false, 'Gagal mendapatkan access token Google: ' . $message, null, 502);
    }

    gps_log('google access token request finished', [
        'http_code' => $response['http_code'],
        'token_uri' => $tokenUri,
    ]);

    return $response['json']['access_token'];
}

function gps_google_http_request($method, $url, $accessToken = null, $body = null, $extraHeaders = [])
{
    gps_log('google http request start', [
        'method' => $method,
        'url' => $url,
        'body_length' => $body === null ? 0 : strlen((string)$body),
        'extra_headers' => $extraHeaders,
        'has_access_token' => $accessToken !== null && $accessToken !== '',
    ]);

    $headers = array_merge(
        [
            'Accept: application/json',
        ],
        $extraHeaders
    );

    if ($accessToken !== null && $accessToken !== '') {
        $headers[] = 'Authorization: Bearer ' . $accessToken;
    }

    $ch = curl_init($url);

    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CUSTOMREQUEST => $method,
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_CONNECTTIMEOUT => 10,
        CURLOPT_TIMEOUT => 30,
    ]);

    if ($body !== null) {
        curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
    }

    $raw = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $curlErrno = curl_errno($ch);
    $curlError = curl_error($ch);
    curl_close($ch);

    if ($raw === false) {
        gps_log('google http request curl failed', [
            'method' => $method,
            'url' => $url,
            'curl_errno' => $curlErrno,
            'curl_error' => $curlError,
        ], 'ERROR');
        gps_json_response(false, 'Gagal menghubungi Google Play: ' . $curlError, null, 502);
    }

    $json = json_decode($raw, true);

    gps_log('google http request finished', [
        'method' => $method,
        'url' => $url,
        'http_code' => $httpCode,
        'curl_errno' => $curlErrno,
        'json_decoded' => is_array($json),
        'response_excerpt' => gps_limit_string($raw),
    ]);

    return [
        'http_code' => $httpCode,
        'raw' => $raw,
        'json' => is_array($json) ? $json : [],
    ];
}

function gps_verify_google_subscription($accessToken, $purchaseToken)
{
    gps_log('verify google subscription started', [
        'product_id' => GPS_PRODUCT_ID,
        'package_name' => GPS_PACKAGE_NAME,
        'purchase_token' => $purchaseToken,
    ]);

    $url = 'https://androidpublisher.googleapis.com/androidpublisher/v3/applications/'
        . rawurlencode(GPS_PACKAGE_NAME)
        . '/purchases/subscriptionsv2/tokens/'
        . rawurlencode($purchaseToken);

    $response = gps_google_http_request('GET', $url, $accessToken);

    if ($response['http_code'] < 200 || $response['http_code'] >= 300) {
        $message = $response['json']['error']['message'] ?? 'Subscription tidak valid atau tidak ditemukan di Google Play';
        gps_json_response(false, $message, null, 400);
    }

    if (($response['json']['kind'] ?? '') !== 'androidpublisher#subscriptionPurchaseV2') {
        gps_json_response(false, 'Respons Google Play tidak valid', null, 400);
    }

    if (empty($response['json']['lineItems']) || !is_array($response['json']['lineItems'])) {
        gps_json_response(false, 'Data subscription dari Google Play tidak lengkap', null, 400);
    }

    gps_log('verify google subscription finished', [
        'subscription_state' => $response['json']['subscriptionState'] ?? '',
        'acknowledgement_state' => $response['json']['acknowledgementState'] ?? '',
        'line_item_count' => count($response['json']['lineItems']),
    ]);

    return $response['json'];
}

function gps_is_acknowledged(array $googleResponse)
{
    return ($googleResponse['acknowledgementState'] ?? '') === 'ACKNOWLEDGEMENT_STATE_ACKNOWLEDGED';
}

function gps_acknowledge_google_subscription($accessToken, $purchaseToken)
{
    gps_log('acknowledge subscription started', [
        'product_id' => GPS_PRODUCT_ID,
        'purchase_token' => $purchaseToken,
    ]);

    $url = 'https://androidpublisher.googleapis.com/androidpublisher/v3/applications/'
        . rawurlencode(GPS_PACKAGE_NAME)
        . '/purchases/subscriptions/'
        . rawurlencode(GPS_PRODUCT_ID)
        . '/tokens/'
        . rawurlencode($purchaseToken)
        . ':acknowledge';

    $response = gps_google_http_request(
        'POST',
        $url,
        $accessToken,
        json_encode(new stdClass()),
        ['Content-Type: application/json']
    );

    if (
        $response['http_code'] === 204 ||
        ($response['http_code'] >= 200 && $response['http_code'] < 300)
    ) {
        gps_log('acknowledge subscription finished', [
            'http_code' => $response['http_code'],
            'result' => 'success',
        ]);
        return true;
    }

    $message = $response['json']['error']['message'] ?? '';
    if (
        $response['http_code'] === 409 ||
        stripos($message, 'already') !== false ||
        stripos($message, 'acknowledged') !== false
    ) {
        gps_log('acknowledge subscription finished', [
            'http_code' => $response['http_code'],
            'result' => 'already_acknowledged',
            'message' => $message,
        ]);
        return true;
    }

    gps_log('acknowledge subscription failed', [
        'http_code' => $response['http_code'],
        'message' => $message,
        'response' => $response['json'],
    ], 'ERROR');
    gps_json_response(false, $message !== '' ? $message : 'Gagal acknowledge subscription Google Play', null, 502);
}

function gps_map_plan_type($basePlanId)
{
    if ($basePlanId === GPS_BASE_PLAN_BASIC) {
        return 'basic';
    }

    if ($basePlanId === GPS_BASE_PLAN_PRO) {
        return 'pro';
    }

    return null;
}

function gps_find_line_item(array $googleResponse, $expectedProductId, $expectedBasePlanId = null)
{
    foreach ($googleResponse['lineItems'] as $item) {
        $itemProductId = $item['productId'] ?? '';
        $itemBasePlanId = $item['offerDetails']['basePlanId'] ?? '';

        if ($itemProductId !== $expectedProductId) {
            continue;
        }

        if ($expectedBasePlanId !== null && $expectedBasePlanId !== '') {
            if ($itemBasePlanId === $expectedBasePlanId) {
                return $item;
            }
            continue;
        }

        if (gps_map_plan_type($itemBasePlanId) !== null) {
            return $item;
        }
    }

    return null;
}

function gps_parse_google_datetime_to_mysql($value)
{
    if ($value === null || trim((string)$value) === '') {
        return null;
    }

    try {
        $date = new DateTime((string)$value);
        $timezone = new DateTimeZone(date_default_timezone_get());
        $date->setTimezone($timezone);
        return $date->format('Y-m-d H:i:s');
    } catch (Throwable $e) {
        return null;
    }
}

function gps_is_expired_datetime($mysqlDateTime)
{
    if ($mysqlDateTime === null || trim((string)$mysqlDateTime) === '') {
        return false;
    }

    try {
        $timezone = new DateTimeZone(date_default_timezone_get());
        $date = new DateTime(trim((string)$mysqlDateTime), $timezone);
        return $date->getTimestamp() < time();
    } catch (Throwable $e) {
        return false;
    }
}

function gps_get_existing_subscription($mysqli, $purchaseToken)
{
    $sql = 'SELECT 
        ' . GPS_SUBSCRIPTION_COMPANY_COLUMN . ',
        base_plan_id,
        order_id,
        acknowledged_at
        FROM ' . GPS_SUBSCRIPTION_TABLE . ' 
        WHERE purchase_token = ? 
        LIMIT 1';
    $stmt = $mysqli->prepare($sql);

    if (!$stmt) {
        gps_json_response(false, 'Gagal menyiapkan query subscription', null, 500);
    }

    $stmt->bind_param('s', $purchaseToken);
    $stmt->execute();
    $result = gps_stmt_fetch_assoc($stmt);
    $stmt->close();

    return $result ?: null;
}

function gps_get_company_row($mysqli, $companyId)
{
    $sql = 'SELECT id, plan_type, expired_at, created_at FROM ' . GPS_COMPANY_TABLE . ' WHERE ' . GPS_COMPANY_ID_COLUMN . ' = ? LIMIT 1';
    $stmt = $mysqli->prepare($sql);

    if (!$stmt) {
        gps_json_response(false, 'Gagal menyiapkan query company plan', null, 500);
    }

    $stmt->bind_param('s', $companyId);
    $stmt->execute();
    $result = gps_stmt_fetch_assoc($stmt);
    $stmt->close();

    return $result ?: null;
}

function gps_count_transactions($mysqli, $companyId, $currentMonthOnly = false)
{
    $tables = ['tb_pembelian', 'tb_penjualan'];
    $total = 0;

    foreach ($tables as $table) {
        $sql = 'SELECT COUNT(*) AS total FROM ' . $table . ' WHERE company_id = ? AND IFNULL(is_deleted, 0) = 0';
        if ($currentMonthOnly) {
            $sql .= " AND DATE_FORMAT(COALESCE(NULLIF(created_at, ''), NULLIF(tanggal, '')), '%Y-%m') = DATE_FORMAT(NOW(), '%Y-%m')";
        }

        $stmt = $mysqli->prepare($sql);
        if (!$stmt) {
            gps_json_response(false, 'Gagal menyiapkan query hitung transaksi', null, 500);
        }

        $stmt->bind_param('s', $companyId);
        $stmt->execute();
        $row = gps_stmt_fetch_assoc($stmt);
        $stmt->close();

        $total += (int)($row['total'] ?? 0);
    }

    return $total;
}

function gps_build_plan_status($mysqli, $companyId)
{
    $company = gps_get_company_row($mysqli, $companyId);

    if (!$company) {
        gps_json_response(false, 'Company tidak ditemukan', null, 404);
    }

    $rawPlanType = strtolower(trim((string)($company['plan_type'] ?? 'free')));
    if ($rawPlanType === 'standard' || $rawPlanType === 'medium') {
        $rawPlanType = 'basic';
    }

    if (!in_array($rawPlanType, ['free', 'basic', 'pro', 'business'], true)) {
        $rawPlanType = 'free';
    }

    $expiredAt = !empty($company['expired_at']) ? $company['expired_at'] : null;
    $isExpired = $rawPlanType !== 'business' && gps_is_expired_datetime($expiredAt);
    $effectivePlanType = $isExpired ? 'free' : $rawPlanType;

    $createdAt = !empty($company['created_at']) ? strtotime((string)$company['created_at'] . ' UTC') : false;
    $trialActive = false;

    if ($effectivePlanType === 'free' && !$isExpired) {
        if ($expiredAt !== null && trim((string)$expiredAt) !== '') {
            $trialActive = !gps_is_expired_datetime($expiredAt);
        } elseif ($createdAt !== false) {
            $trialActive = (time() <= ($createdAt + (GPS_FREE_TRIAL_DAYS * 86400)));
        }
    }

    $lifetimeUsed = gps_count_transactions($mysqli, $companyId, false);
    $monthlyUsed = gps_count_transactions($mysqli, $companyId, true);

    if ($effectivePlanType === 'business') {
        return [
            'plan_type' => 'business',
            'expired_at' => $expiredAt,
            'is_expired' => false,
            'is_premium' => true,
            'trial_active' => false,
            'free_limit' => GPS_FREE_LIMIT,
            'used_transactions' => $monthlyUsed,
            'remaining_transactions' => null,
            'can_create_transaction' => true,
        ];
    }

    if ($effectivePlanType === 'basic') {
        $remaining = max(0, GPS_BASIC_LIMIT - $monthlyUsed);
        return [
            'plan_type' => 'basic',
            'expired_at' => $expiredAt,
            'is_expired' => false,
            'is_premium' => true,
            'trial_active' => false,
            'free_limit' => GPS_FREE_LIMIT,
            'used_transactions' => $monthlyUsed,
            'remaining_transactions' => $remaining,
            'can_create_transaction' => $monthlyUsed < GPS_BASIC_LIMIT,
        ];
    }

    if ($effectivePlanType === 'pro') {
        $remaining = max(0, GPS_PRO_LIMIT - $monthlyUsed);
        return [
            'plan_type' => 'pro',
            'expired_at' => $expiredAt,
            'is_expired' => false,
            'is_premium' => true,
            'trial_active' => false,
            'free_limit' => GPS_FREE_LIMIT,
            'used_transactions' => $monthlyUsed,
            'remaining_transactions' => $remaining,
            'can_create_transaction' => $monthlyUsed < GPS_PRO_LIMIT,
        ];
    }

    if ($trialActive) {
        return [
            'plan_type' => 'free',
            'expired_at' => $expiredAt,
            'is_expired' => $isExpired,
            'is_premium' => false,
            'trial_active' => true,
            'free_limit' => GPS_FREE_LIMIT,
            'used_transactions' => $lifetimeUsed,
            'remaining_transactions' => null,
            'can_create_transaction' => true,
        ];
    }

    $remaining = max(0, GPS_FREE_LIMIT - $lifetimeUsed);

    return [
        'plan_type' => 'free',
        'expired_at' => $expiredAt,
        'is_expired' => $isExpired,
        'is_premium' => false,
        'trial_active' => false,
        'free_limit' => GPS_FREE_LIMIT,
        'used_transactions' => $lifetimeUsed,
        'remaining_transactions' => $remaining,
        'can_create_transaction' => $lifetimeUsed < GPS_FREE_LIMIT,
    ];
}

function gps_upsert_subscription_record(
    $mysqli,
    $companyId,
    $productId,
    $basePlanId,
    $planType,
    $purchaseToken,
    $orderId,
    $status,
    $startedAt,
    $expiredAt,
    $acknowledgedAt,
    $googleState,
    $rawResponse
) {
    $sql = '
        INSERT INTO ' . GPS_SUBSCRIPTION_TABLE . ' (
            ' . GPS_SUBSCRIPTION_COMPANY_COLUMN . ',
            product_id,
            base_plan_id,
            plan_type,
            purchase_token,
            order_id,
            platform,
            status,
            started_at,
            expired_at,
            acknowledged_at,
            google_state,
            last_verified_at,
            raw_response
        )
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), ?)
        ON DUPLICATE KEY UPDATE
            ' . GPS_SUBSCRIPTION_COMPANY_COLUMN . ' = VALUES(' . GPS_SUBSCRIPTION_COMPANY_COLUMN . '),
            product_id = VALUES(product_id),
            base_plan_id = VALUES(base_plan_id),
            plan_type = VALUES(plan_type),
            order_id = VALUES(order_id),
            platform = VALUES(platform),
            status = VALUES(status),
            started_at = VALUES(started_at),
            expired_at = VALUES(expired_at),
            acknowledged_at = COALESCE(' . GPS_SUBSCRIPTION_TABLE . '.acknowledged_at, VALUES(acknowledged_at)),
            google_state = VALUES(google_state),
            last_verified_at = NOW(),
            raw_response = VALUES(raw_response)
    ';

    $stmt = $mysqli->prepare($sql);

    if (!$stmt) {
        gps_log('db prepare failed', [
            'operation' => 'upsert_subscription',
            'db_error' => $mysqli->error,
        ], 'ERROR');
        gps_json_response(false, 'Gagal menyiapkan simpan subscription', null, 500);
    }

    $platform = 'android';
    $stmt->bind_param(
        'sssssssssssss',
        $companyId,
        $productId,
        $basePlanId,
        $planType,
        $purchaseToken,
        $orderId,
        $platform,
        $status,
        $startedAt,
        $expiredAt,
        $acknowledgedAt,
        $googleState,
        $rawResponse
    );

    if (!$stmt->execute()) {
        gps_log('db execute failed', [
            'operation' => 'upsert_subscription',
            'stmt_error' => $stmt->error,
            'company_id' => $companyId,
            'product_id' => $productId,
            'base_plan_id' => $basePlanId,
            'status' => $status,
        ], 'ERROR');
        $stmt->close();
        gps_json_response(false, 'Gagal menyimpan data subscription', null, 500);
    }

    $stmt->close();
}

function gps_update_company_plan($mysqli, $companyId, $planType, $expiredAt)
{
    $sql = 'UPDATE ' . GPS_COMPANY_TABLE . ' SET plan_type = ?, expired_at = ?, updated_at = NOW() WHERE ' . GPS_COMPANY_ID_COLUMN . ' = ?';
    $stmt = $mysqli->prepare($sql);

    if (!$stmt) {
        gps_log('db prepare failed', [
            'operation' => 'update_company_plan',
            'db_error' => $mysqli->error,
            'company_id' => $companyId,
        ], 'ERROR');
        gps_json_response(false, 'Gagal menyiapkan update company plan', null, 500);
    }

    $stmt->bind_param('sss', $planType, $expiredAt, $companyId);

    if (!$stmt->execute()) {
        gps_log('db execute failed', [
            'operation' => 'update_company_plan',
            'stmt_error' => $stmt->error,
            'company_id' => $companyId,
            'plan_type' => $planType,
            'expired_at' => $expiredAt,
        ], 'ERROR');
        $stmt->close();
        gps_json_response(false, 'Gagal mengupdate plan company', null, 500);
    }

    $stmt->close();
}

function gps_update_company_owner_user_id($mysqli, $companyId, $ownerUserId)
{
    $ownerUserId = trim((string)$ownerUserId);
    if ($ownerUserId === '') {
        return;
    }

    $sql = 'UPDATE ' . GPS_COMPANY_TABLE . ' SET owner_user_id = ?, updated_at = NOW() WHERE ' . GPS_COMPANY_ID_COLUMN . ' = ?';
    $stmt = $mysqli->prepare($sql);

    if (!$stmt) {
        gps_log('db prepare failed', [
            'operation' => 'update_company_owner_user_id',
            'db_error' => $mysqli->error,
            'company_id' => $companyId,
        ], 'ERROR');
        gps_json_response(false, 'Gagal menyiapkan update owner company', null, 500);
    }

    $stmt->bind_param('ss', $ownerUserId, $companyId);

    if (!$stmt->execute()) {
        gps_log('db execute failed', [
            'operation' => 'update_company_owner_user_id',
            'stmt_error' => $stmt->error,
            'company_id' => $companyId,
            'owner_user_id' => $ownerUserId,
        ], 'ERROR');
        $stmt->close();
        gps_json_response(false, 'Gagal mengupdate owner company', null, 500);
    }

    $stmt->close();
}

function gps_update_company_plan_from_google_state($mysqli, $companyId, $planType, $expiredAt, $googleState)
{
    $company = gps_get_company_row($mysqli, $companyId);

    if (!$company) {
        gps_json_response(false, 'Company tidak ditemukan', null, 404);
    }

    $currentPlanType = strtolower(trim((string)($company['plan_type'] ?? 'free')));
    if ($currentPlanType === 'business') {
        return;
    }

    $resolvedPlanType = $planType;
    if (
        $googleState === 'SUBSCRIPTION_STATE_EXPIRED' ||
        $googleState === 'SUBSCRIPTION_STATE_REVOKED' ||
        gps_is_expired_datetime($expiredAt)
    ) {
        $resolvedPlanType = 'free';
    }

    gps_update_company_plan($mysqli, $companyId, $resolvedPlanType, $expiredAt);
}

function gps_map_subscription_record_status($googleState, $expiredAt)
{
    if ($googleState === 'SUBSCRIPTION_STATE_REVOKED') {
        return 'revoked';
    }

    if ($googleState === 'SUBSCRIPTION_STATE_EXPIRED' || gps_is_expired_datetime($expiredAt)) {
        return 'expired';
    }

    if ($googleState === 'SUBSCRIPTION_STATE_ON_HOLD') {
        return 'on_hold';
    }

    if ($googleState === 'SUBSCRIPTION_STATE_PAUSED') {
        return 'paused';
    }

    if ($googleState === 'SUBSCRIPTION_STATE_IN_GRACE_PERIOD') {
        return 'grace_period';
    }

    if ($googleState === 'SUBSCRIPTION_STATE_CANCELED') {
        return 'canceled';
    }

    return 'active';
}

function gps_process_verified_subscription($mysqli, $args)
{
    $companyId = trim((string)($args['company_id'] ?? ''));
    $userId = trim((string)($args['user_id'] ?? ''));
    $productId = trim((string)($args['product_id'] ?? GPS_PRODUCT_ID));
    $purchaseToken = trim((string)($args['purchase_token'] ?? ''));
    $requestedBasePlanId = trim((string)($args['base_plan_id'] ?? ''));
    $orderId = trim((string)($args['order_id'] ?? ''));
    $shouldAcknowledge = !isset($args['acknowledge']) || $args['acknowledge'] === true;

    gps_log('process verified subscription started', [
        'company_id' => $companyId,
        'user_id' => $userId,
        'product_id' => $productId,
        'base_plan_id' => $requestedBasePlanId,
        'purchase_token' => $purchaseToken,
        'has_order_id' => $orderId !== '',
        'should_acknowledge' => $shouldAcknowledge,
    ]);

    if ($companyId === '') {
        gps_json_response(false, 'company_id dari token login kosong', null, 401);
    }

    if ($productId !== GPS_PRODUCT_ID) {
        gps_json_response(false, 'product_id tidak valid', null, 400);
    }

    if ($purchaseToken === '') {
        gps_json_response(false, 'purchase_token wajib diisi', null, 400);
    }

    if (!gps_company_exists($mysqli, $companyId)) {
        gps_json_response(false, 'Company tidak ditemukan', null, 404);
    }

    $existingSubscription = gps_get_existing_subscription($mysqli, $purchaseToken);
    if (
        $existingSubscription &&
        !empty($existingSubscription[GPS_SUBSCRIPTION_COMPANY_COLUMN]) &&
        (string)$existingSubscription[GPS_SUBSCRIPTION_COMPANY_COLUMN] !== $companyId
    ) {
        gps_json_response(false, 'purchase_token ini sudah terhubung ke company lain', null, 409);
    }

    $accessToken = gps_get_google_access_token();
    $googleResponse = gps_verify_google_subscription($accessToken, $purchaseToken);

    $lineItem = gps_find_line_item(
        $googleResponse,
        $productId,
        $requestedBasePlanId !== '' ? $requestedBasePlanId : null
    );

    if (!$lineItem) {
        gps_json_response(false, 'Produk subscription tidak cocok dengan base plan yang dibeli', null, 400);
    }

    $basePlanId = $lineItem['offerDetails']['basePlanId'] ?? '';
    $planType = gps_map_plan_type($basePlanId);

    if ($planType === null) {
        gps_json_response(false, 'Base plan Google Play tidak dikenali', null, 400);
    }

    gps_log('google subscription mapped to plan', [
        'base_plan_id' => $basePlanId,
        'plan_type' => $planType,
    ]);

    $startedAt = gps_parse_google_datetime_to_mysql($googleResponse['startTime'] ?? null);
    $expiredAt = gps_parse_google_datetime_to_mysql($lineItem['expiryTime'] ?? null);

    gps_log('google subscription dates resolved', [
        'google_start_time' => $googleResponse['startTime'] ?? null,
        'google_expiry_time' => $lineItem['expiryTime'] ?? null,
        'started_at' => $startedAt,
        'expired_at' => $expiredAt,
        'timezone' => date_default_timezone_get(),
    ]);

    if ($expiredAt === null) {
        gps_json_response(false, 'Data masa aktif subscription dari Google tidak valid', null, 400);
    }

    $acknowledgedAt = $existingSubscription['acknowledged_at'] ?? null;

    if ($shouldAcknowledge && !gps_is_acknowledged($googleResponse)) {
        gps_acknowledge_google_subscription($accessToken, $purchaseToken);
        $googleResponse = gps_verify_google_subscription($accessToken, $purchaseToken);

        if (!gps_is_acknowledged($googleResponse)) {
            gps_json_response(false, 'Subscription aktif tetapi belum berhasil di-acknowledge', null, 502);
        }

        $acknowledgedAt = gmdate('Y-m-d H:i:s');
    }

    $subscriptionState = $googleResponse['subscriptionState'] ?? '';
    $resolvedOrderId = $orderId !== ''
        ? $orderId
        : ($lineItem['latestSuccessfulOrderId'] ?? $googleResponse['latestOrderId'] ?? '');

    $recordStatus = gps_map_subscription_record_status($subscriptionState, $expiredAt);
    $rawResponse = json_encode($googleResponse, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

    gps_log('database transaction started', [
        'company_id' => $companyId,
        'plan_type' => $planType,
        'status' => $recordStatus,
        'expired_at' => $expiredAt,
    ]);
    $mysqli->begin_transaction();

    try {
        gps_upsert_subscription_record(
            $mysqli,
            $companyId,
            $productId,
            $basePlanId,
            $planType,
            $purchaseToken,
            $resolvedOrderId,
            $recordStatus,
            $startedAt,
            $expiredAt,
            $acknowledgedAt,
            $subscriptionState,
            $rawResponse
        );

        gps_mark_pending_rtdn_events_as_matched($mysqli, $purchaseToken, $companyId);

        gps_update_company_owner_user_id($mysqli, $companyId, $userId);

        gps_update_company_plan_from_google_state(
            $mysqli,
            $companyId,
            $planType,
            $expiredAt,
            $subscriptionState
        );
        $mysqli->commit();
        gps_log('database transaction committed', [
            'company_id' => $companyId,
            'plan_type' => $planType,
            'status' => $recordStatus,
        ]);
    } catch (Throwable $e) {
        $mysqli->rollback();
        gps_log_exception('process verified subscription failed', $e, [
            'company_id' => $companyId,
            'product_id' => $productId,
            'base_plan_id' => $basePlanId,
        ]);
        gps_json_response(false, $e->getMessage(), null, 500);
    }

    return [
        'company_id' => $companyId,
        'user_id' => $userId,
        'product_id' => $productId,
        'base_plan_id' => $basePlanId,
        'plan_type' => $planType,
        'expired_at' => $expiredAt,
        'status' => $recordStatus,
        'google_subscription_state' => $subscriptionState,
        'acknowledged' => gps_is_acknowledged($googleResponse),
        'verified_at' => gmdate('Y-m-d H:i:s'),
        'plan_status' => gps_build_plan_status($mysqli, $companyId),
    ];
}

function gps_process_manual_verify($mysqli, $input, $authContext)
{
    $companyId = trim((string)($authContext['company_id'] ?? ''));
    $userId = trim((string)($authContext['user_id'] ?? ''));

    $purchaseToken = trim((string)($input['purchase_token'] ?? ''));
    $productId = trim((string)($input['product_id'] ?? GPS_PRODUCT_ID));
    $basePlanId = trim((string)($input['base_plan_id'] ?? ''));
    $orderId = trim((string)($input['order_id'] ?? ''));

    gps_log('manual verify payload parsed', [
        'company_id' => $companyId,
        'user_id' => $userId,
        'product_id' => $productId,
        'base_plan_id' => $basePlanId,
        'purchase_token' => $purchaseToken,
        'has_order_id' => $orderId !== '',
    ]);

    if ($companyId === '') {
        gps_json_response(false, 'company_id dari token login kosong', null, 401);
    }

    if ($purchaseToken === '') {
        gps_json_response(false, 'purchase_token wajib diisi', null, 400);
    }

    if (!gps_company_exists($mysqli, $companyId)) {
        gps_json_response(false, 'Company tidak ditemukan', null, 404);
    }

    return gps_process_verified_subscription($mysqli, [
        'company_id' => $companyId,
        'user_id' => $userId,
        'product_id' => $productId,
        'base_plan_id' => $basePlanId,
        'purchase_token' => $purchaseToken,
        'order_id' => $orderId,
        'acknowledge' => true,
    ]);
}

function gps_resolve_rtdn_secret()
{
    $envSecret = trim((string)getenv('GOOGLE_PLAY_RTDN_SECRET'));
    if ($envSecret !== '') {
        return $envSecret;
    }

    $candidates = [
        dirname(__DIR__) . '/config/google-play-rtdn-secret.txt',
        dirname(__DIR__) . '/config/rtdn-secret.txt',
    ];

    foreach ($candidates as $path) {
        if (!file_exists($path)) {
            continue;
        }

        $secret = trim((string)file_get_contents($path));
        if ($secret !== '') {
            return $secret;
        }
    }

    gps_json_response(false, 'Secret RTDN belum dikonfigurasi di server', null, 500);
}

function gps_get_header_value($headerName)
{
    $headers = function_exists('getallheaders') ? getallheaders() : [];
    foreach ($headers as $key => $value) {
        if (strtolower($key) === strtolower($headerName)) {
            return trim((string)$value);
        }
    }

    $serverKey = 'HTTP_' . strtoupper(str_replace('-', '_', $headerName));
    return isset($_SERVER[$serverKey]) ? trim((string)$_SERVER[$serverKey]) : '';
}

function gps_require_rtdn_secret()
{
    $expectedSecret = gps_resolve_rtdn_secret();
    $receivedSecret = gps_get_header_value('X-RTDN-Secret');

    if ($receivedSecret === '') {
        $receivedSecret = trim((string)($_GET['secret'] ?? ''));
    }

    if ($receivedSecret === '' || !hash_equals($expectedSecret, $receivedSecret)) {
        gps_json_response(false, 'Secret RTDN tidak valid', null, 401);
    }
}

function gps_decode_pubsub_message_data($encodedData)
{
    $decoded = base64_decode((string)$encodedData, true);
    if ($decoded === false || $decoded === '') {
        gps_json_response(false, 'Data Pub/Sub tidak valid', null, 400);
    }

    $payload = json_decode($decoded, true);
    if (!is_array($payload)) {
        gps_json_response(false, 'Payload RTDN tidak valid', null, 400);
    }

    return $payload;
}

function gps_map_rtdn_notification_type($notificationType)
{
    $map = [
        1 => 'recovered',
        2 => 'renewed',
        3 => 'canceled',
        4 => 'purchased',
        5 => 'on_hold',
        6 => 'in_grace_period',
        7 => 'restarted',
        8 => 'price_change_confirmed',
        9 => 'deferred',
        10 => 'paused',
        11 => 'pause_schedule_changed',
        12 => 'revoked',
        13 => 'expired',
        19 => 'price_step_up_consent_updated',
        20 => 'pending_purchase_canceled',
        22 => 'price_change_updated',
    ];

    return $map[(int)$notificationType] ?? 'unknown';
}

function gps_safe_store_rtdn_log(
    $mysqli,
    $messageId,
    $purchaseToken,
    $companyId,
    $notificationType,
    $notificationLabel,
    $status,
    $payloadJson,
    $note
) {
    $sql = '
        INSERT INTO ' . GPS_RTND_LOG_TABLE . ' (
            message_id,
            purchase_token,
            company_id,
            notification_type,
            notification_label,
            status,
            note,
            payload_json,
            created_at,
            updated_at
        )
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
        ON DUPLICATE KEY UPDATE
            company_id = VALUES(company_id),
            notification_type = VALUES(notification_type),
            notification_label = VALUES(notification_label),
            status = VALUES(status),
            note = VALUES(note),
            payload_json = VALUES(payload_json),
            updated_at = NOW()
    ';

    $stmt = $mysqli->prepare($sql);
    if (!$stmt) {
        gps_log('RTDN log table belum tersedia', ['message_id' => $messageId]);
        return;
    }

    $stmt->bind_param(
        'sssissss',
        $messageId,
        $purchaseToken,
        $companyId,
        $notificationType,
        $notificationLabel,
        $status,
        $note,
        $payloadJson
    );
    $stmt->execute();
    $stmt->close();
}

function gps_mark_pending_rtdn_events_as_matched($mysqli, $purchaseToken, $companyId)
{
    $purchaseToken = trim((string)$purchaseToken);
    $companyId = trim((string)$companyId);

    if ($purchaseToken === '' || $companyId === '') {
        return;
    }

    $sql = '
        UPDATE ' . GPS_RTND_LOG_TABLE . '
        SET
            company_id = ?,
            status = ?,
            note = ?,
            updated_at = NOW()
        WHERE purchase_token = ?
          AND status = ?
    ';

    $stmt = $mysqli->prepare($sql);
    if (!$stmt) {
        gps_log('RTDN match update skipped', [
            'reason' => 'prepare_failed',
            'purchase_token' => $purchaseToken,
            'company_id' => $companyId,
        ], 'ERROR');
        return;
    }

    $matchedStatus = 'matched';
    $note = 'purchase_token berhasil dipetakan ke company setelah verify manual';
    $pendingStatus = 'pending_match';

    $stmt->bind_param('sssss', $companyId, $matchedStatus, $note, $purchaseToken, $pendingStatus);
    $stmt->execute();
    $affectedRows = $stmt->affected_rows;
    $stmt->close();

    if ($affectedRows > 0) {
        gps_log('pending RTDN events matched', [
            'purchase_token' => $purchaseToken,
            'company_id' => $companyId,
            'matched_rows' => $affectedRows,
        ]);
    }
}

function gps_parse_rtdn_request()
{
    $rawBody = file_get_contents('php://input');
    $request = json_decode($rawBody, true);

    if (!is_array($request) || empty($request['message']) || !is_array($request['message'])) {
        gps_json_response(false, 'Request RTDN Pub/Sub tidak valid', null, 400);
    }

    $message = $request['message'];
    $messageId = trim((string)($message['messageId'] ?? $message['message_id'] ?? ''));
    $publishTime = trim((string)($message['publishTime'] ?? ''));
    $data = gps_decode_pubsub_message_data($message['data'] ?? '');

    return [
        'message_id' => $messageId,
        'publish_time' => $publishTime,
        'subscription' => trim((string)($request['subscription'] ?? '')),
        'raw_request' => $request,
        'decoded_payload' => $data,
    ];
}

function gps_process_rtdn_notification($mysqli, array $requestData)
{
    $payload = $requestData['decoded_payload'];
    $messageId = trim((string)($requestData['message_id'] ?? ''));
    $payloadJson = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

    if (!empty($payload['testNotification'])) {
        gps_safe_store_rtdn_log(
            $mysqli,
            $messageId,
            '',
            '',
            0,
            'test_notification',
            'ignored',
            $payloadJson,
            'RTDN test notification diterima'
        );

        return [
            'message_id' => $messageId,
            'status' => 'ignored',
            'reason' => 'test_notification',
        ];
    }

    $subscriptionNotification = $payload['subscriptionNotification'] ?? null;
    if (!is_array($subscriptionNotification)) {
        gps_safe_store_rtdn_log(
            $mysqli,
            $messageId,
            '',
            '',
            0,
            'unsupported_payload',
            'ignored',
            $payloadJson,
            'Payload RTDN bukan subscription notification'
        );

        return [
            'message_id' => $messageId,
            'status' => 'ignored',
            'reason' => 'unsupported_payload',
        ];
    }

    $purchaseToken = trim((string)($subscriptionNotification['purchaseToken'] ?? ''));
    $subscriptionId = trim((string)($subscriptionNotification['subscriptionId'] ?? ''));
    $notificationType = (int)($subscriptionNotification['notificationType'] ?? 0);
    $notificationLabel = gps_map_rtdn_notification_type($notificationType);

    if ($purchaseToken === '') {
        gps_json_response(false, 'purchaseToken RTDN kosong', null, 400);
    }

    if ($subscriptionId !== GPS_PRODUCT_ID) {
        gps_safe_store_rtdn_log(
            $mysqli,
            $messageId,
            $purchaseToken,
            '',
            $notificationType,
            $notificationLabel,
            'ignored',
            $payloadJson,
            'subscriptionId tidak cocok dengan product aplikasi'
        );

        return [
            'message_id' => $messageId,
            'purchase_token' => $purchaseToken,
            'status' => 'ignored',
            'reason' => 'product_mismatch',
        ];
    }

    $existingSubscription = gps_get_existing_subscription($mysqli, $purchaseToken);
    if (!$existingSubscription) {
        gps_safe_store_rtdn_log(
            $mysqli,
            $messageId,
            $purchaseToken,
            '',
            $notificationType,
            $notificationLabel,
            'pending_match',
            $payloadJson,
            'purchase_token belum terhubung ke company mana pun'
        );

        return [
            'message_id' => $messageId,
            'purchase_token' => $purchaseToken,
            'status' => 'pending_match',
            'reason' => 'subscription_not_linked_yet',
        ];
    }

    $companyId = trim((string)($existingSubscription[GPS_SUBSCRIPTION_COMPANY_COLUMN] ?? ''));
    if ($companyId === '') {
        gps_safe_store_rtdn_log(
            $mysqli,
            $messageId,
            $purchaseToken,
            '',
            $notificationType,
            $notificationLabel,
            'pending_match',
            $payloadJson,
            'company_id subscription kosong'
        );

        return [
            'message_id' => $messageId,
            'purchase_token' => $purchaseToken,
            'status' => 'pending_match',
            'reason' => 'missing_company_id',
        ];
    }

    $accessToken = gps_get_google_access_token();
    $googleResponse = gps_verify_google_subscription($accessToken, $purchaseToken);

    if (!gps_is_acknowledged($googleResponse)) {
        gps_acknowledge_google_subscription($accessToken, $purchaseToken);
        $googleResponse = gps_verify_google_subscription($accessToken, $purchaseToken);
    }

    $lineItem = gps_find_line_item(
        $googleResponse,
        GPS_PRODUCT_ID,
        trim((string)($existingSubscription['base_plan_id'] ?? ''))
    );

    if (!$lineItem) {
        $lineItem = gps_find_line_item($googleResponse, GPS_PRODUCT_ID, null);
    }

    if (!$lineItem) {
        gps_safe_store_rtdn_log(
            $mysqli,
            $messageId,
            $purchaseToken,
            $companyId,
            $notificationType,
            $notificationLabel,
            'error',
            $payloadJson,
            'Line item Google Play tidak cocok'
        );

        gps_json_response(false, 'Line item subscription Google Play tidak cocok', null, 400);
    }

    $basePlanId = trim((string)($lineItem['offerDetails']['basePlanId'] ?? ''));
    $planType = gps_map_plan_type($basePlanId);
    if ($planType === null) {
        gps_json_response(false, 'Base plan RTDN tidak dikenali', null, 400);
    }

    $subscriptionState = trim((string)($googleResponse['subscriptionState'] ?? ''));
    $startedAt = gps_parse_google_datetime_to_mysql($googleResponse['startTime'] ?? null);
    $expiredAt = gps_parse_google_datetime_to_mysql($lineItem['expiryTime'] ?? null);
    $acknowledgedAt = gps_is_acknowledged($googleResponse)
        ? ($existingSubscription['acknowledged_at'] ?? gmdate('Y-m-d H:i:s'))
        : ($existingSubscription['acknowledged_at'] ?? null);
    $resolvedOrderId = trim((string)($lineItem['latestSuccessfulOrderId'] ?? $googleResponse['latestOrderId'] ?? $existingSubscription['order_id'] ?? ''));
    $recordStatus = gps_map_subscription_record_status($subscriptionState, $expiredAt);
    $rawResponse = json_encode($googleResponse, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

    $mysqli->begin_transaction();

    try {
        gps_upsert_subscription_record(
            $mysqli,
            $companyId,
            GPS_PRODUCT_ID,
            $basePlanId,
            $planType,
            $purchaseToken,
            $resolvedOrderId,
            $recordStatus,
            $startedAt,
            $expiredAt,
            $acknowledgedAt,
            $subscriptionState,
            $rawResponse
        );

        gps_update_company_plan_from_google_state(
            $mysqli,
            $companyId,
            $planType,
            $expiredAt,
            $subscriptionState
        );

        gps_safe_store_rtdn_log(
            $mysqli,
            $messageId,
            $purchaseToken,
            $companyId,
            $notificationType,
            $notificationLabel,
            'processed',
            $payloadJson,
            'RTDN berhasil diproses'
        );

        $mysqli->commit();
    } catch (Throwable $e) {
        $mysqli->rollback();

        gps_safe_store_rtdn_log(
            $mysqli,
            $messageId,
            $purchaseToken,
            $companyId,
            $notificationType,
            $notificationLabel,
            'error',
            $payloadJson,
            $e->getMessage()
        );

        gps_json_response(false, 'Gagal memproses RTDN: ' . $e->getMessage(), null, 500);
    }

    return [
        'message_id' => $messageId,
        'company_id' => $companyId,
        'purchase_token' => $purchaseToken,
        'notification_type' => $notificationType,
        'notification_label' => $notificationLabel,
        'plan_type' => $planType,
        'google_subscription_state' => $subscriptionState,
        'expired_at' => $expiredAt,
        'status' => $recordStatus,
        'plan_status' => gps_build_plan_status($mysqli, $companyId),
    ];
}
