<?php
declare(strict_types=1);
/**
 * Firebase / GCP usage for dashboard: Cloud Monitoring metrics (project-level).
 * POST JSON: { "clientId": "...", "idToken": "..." }
 * Requires api/firebase-admin-bootstrap.local.php + service account with monitoring.viewer (or broader).
 */
header('Content-Type: application/json; charset=UTF-8');
header('Cache-Control: no-store');

require_once __DIR__ . '/firebase-rtdb-admin.php';

const MONITORING_READ_SCOPE = 'https://www.googleapis.com/auth/monitoring.read';

/**
 * @return array{project_id?: string, error?: string}
 */
function fum_read_gcp_project_id_from_service_account(string $jsonPath): array {
    if (!is_readable($jsonPath)) {
        return ['error' => 'Service account file not readable.'];
    }
    $raw = file_get_contents($jsonPath);
    $j = json_decode($raw !== false ? $raw : 'null', true);
    if (!is_array($j) || empty($j['project_id'])) {
        return ['error' => 'service_account JSON missing project_id.'];
    }
    return ['project_id' => (string) $j['project_id']];
}

/**
 * Default RTDB instance id from classic *.firebaseio.com or *.firebasedatabase.app host.
 */
function fum_rtdb_table_name_from_database_url(string $databaseUrl, ?string $override): string {
    if ($override !== null && trim($override) !== '') {
        return trim($override);
    }
    $databaseUrl = trim($databaseUrl);
    if ($databaseUrl === '') {
        return 'default-rtdb';
    }
    $p = parse_url($databaseUrl);
    $host = isset($p['host']) ? strtolower((string) $p['host']) : '';
    if ($host === '') {
        return 'default-rtdb';
    }
    $parts = explode('.', $host);
    $first = $parts[0] ?? '';
    return $first !== '' ? $first : 'default-rtdb';
}

/**
 * @return array{data?: array<string,mixed>, error?: string, http?: int}
 */
function fum_monitoring_get_json(string $accessToken, string $url): array {
    if (!function_exists('curl_init')) {
        return ['error' => 'PHP curl required.', 'http' => 500];
    }
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $accessToken, 'Accept: application/json'],
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 45,
    ]);
    $res = curl_exec($ch);
    $http = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    if ($res === false) {
        return ['error' => 'Monitoring request failed.', 'http' => $http];
    }
    $data = json_decode((string) $res, true);
    if (!is_array($data)) {
        return ['error' => 'Invalid Monitoring JSON (HTTP ' . $http . ').', 'http' => $http];
    }
    if ($http >= 400) {
        $msg = isset($data['error']['message']) ? (string) $data['error']['message'] : 'Monitoring HTTP ' . $http;
        return ['error' => $msg, 'http' => $http, 'data' => $data];
    }
    return ['data' => $data, 'http' => $http];
}

/**
 * List all time series pages for a filter + interval.
 *
 * @return array{timeSeries?: list<array<string,mixed>>, error?: string, http?: int}
 */
function fum_list_all_timeseries(string $accessToken, string $projectId, string $filter, string $startIso, string $endIso): array {
    $all = [];
    $pageToken = '';
    do {
        $q = [
            'filter' => $filter,
            'interval.endTime' => $endIso,
            'interval.startTime' => $startIso,
            'pageSize' => '500',
        ];
        if ($pageToken !== '') {
            $q['pageToken'] = $pageToken;
        }
        $url = 'https://monitoring.googleapis.com/v3/projects/' . rawurlencode($projectId) . '/timeSeries?' . http_build_query($q, '', '&', PHP_QUERY_RFC3986);
        $got = fum_monitoring_get_json($accessToken, $url);
        if (isset($got['error'])) {
            return ['error' => $got['error'], 'http' => $got['http'] ?? 502];
        }
        $data = $got['data'] ?? [];
        $chunk = isset($data['timeSeries']) && is_array($data['timeSeries']) ? $data['timeSeries'] : [];
        foreach ($chunk as $row) {
            $all[] = $row;
        }
        $pageToken = isset($data['nextPageToken']) ? (string) $data['nextPageToken'] : '';
    } while ($pageToken !== '');

    return ['timeSeries' => $all, 'http' => 200];
}

/**
 * Sum int64/double values across all points (for DELTA byte counters).
 */
function fum_sum_metric_points(array $timeSeriesList): int {
    $sum = 0;
    foreach ($timeSeriesList as $ts) {
        if (!is_array($ts)) {
            continue;
        }
        $points = isset($ts['points']) && is_array($ts['points']) ? $ts['points'] : [];
        foreach ($points as $pt) {
            if (!is_array($pt)) {
                continue;
            }
            $v = $pt['value'] ?? null;
            if (!is_array($v)) {
                continue;
            }
            if (isset($v['int64Value'])) {
                $sum += (int) $v['int64Value'];
            } elseif (isset($v['doubleValue'])) {
                $sum += (int) round((float) $v['doubleValue']);
            }
        }
    }
    return $sum;
}

/**
 * Latest gauge int64 by max interval.endTime.
 */
function fum_latest_gauge_int64(array $timeSeriesList): ?int {
    $best = null;
    $bestEnd = '';
    foreach ($timeSeriesList as $ts) {
        if (!is_array($ts)) {
            continue;
        }
        $points = isset($ts['points']) && is_array($ts['points']) ? $ts['points'] : [];
        foreach ($points as $pt) {
            if (!is_array($pt)) {
                continue;
            }
            $end = isset($pt['interval']['endTime']) ? (string) $pt['interval']['endTime'] : '';
            $v = $pt['value'] ?? null;
            if (!is_array($v) || $end === '') {
                continue;
            }
            $n = null;
            if (isset($v['int64Value'])) {
                $n = (int) $v['int64Value'];
            } elseif (isset($v['doubleValue'])) {
                $n = (int) round((float) $v['doubleValue']);
            }
            if ($n === null) {
                continue;
            }
            if ($bestEnd === '' || strcmp($end, $bestEnd) > 0) {
                $bestEnd = $end;
                $best = $n;
            }
        }
    }
    return $best;
}

function fum_json_fail(int $code, string $key, string $message = ''): void {
    http_response_code($code);
    echo json_encode([
        'ok' => false,
        'error' => $key,
        'message' => $message,
    ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    exit;
}

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    fum_json_fail(405, 'method_not_allowed', 'POST only.');
}

$raw = file_get_contents('php://input');
$body = json_decode($raw !== false && $raw !== '' ? $raw : 'null', true);
if (!is_array($body)) {
    fum_json_fail(400, 'invalid_json', 'Body must be JSON.');
}

$clientId = isset($body['clientId']) ? trim((string) $body['clientId']) : '';
$idToken = isset($body['idToken']) ? trim((string) $body['idToken']) : '';

if ($clientId === '' || !preg_match('/^[-A-Za-z0-9_]{1,128}$/', $clientId)) {
    fum_json_fail(400, 'invalid_client', 'Valid clientId is required.');
}
if ($idToken === '') {
    fum_json_fail(401, 'missing_token', 'idToken is required.');
}

$bootPath = __DIR__ . '/firebase-admin-bootstrap.local.php';
if (!is_readable($bootPath)) {
    fum_json_fail(503, 'bootstrap_missing', 'Create api/firebase-admin-bootstrap.local.php.');
}
/** @var array<string, mixed> $boot */
$boot = include $bootPath;
if (!is_array($boot)) {
    fum_json_fail(503, 'bootstrap_invalid', 'Invalid bootstrap PHP return.');
}

$saPath = isset($boot['service_account_json_path']) ? trim((string) $boot['service_account_json_path']) : '';
$dbUrl = isset($boot['firebase_database_url']) ? trim((string) $boot['firebase_database_url']) : '';
$webKey = isset($boot['firebase_web_api_key']) ? trim((string) $boot['firebase_web_api_key']) : '';

if ($saPath === '' || $dbUrl === '' || $webKey === '' || !is_readable($saPath)) {
    fum_json_fail(503, 'config_invalid', 'Bootstrap: service_account_json_path, firebase_database_url, firebase_web_api_key required.');
}

$verify = firebase_verify_id_token($idToken, $webKey);
if (isset($verify['error'])) {
    fum_json_fail(401, 'auth_failed', (string) $verify['error']);
}
$uid = (string) $verify['uid'];

$dbTok = firebase_service_account_access_token($saPath);
if (isset($dbTok['error'])) {
    fum_json_fail(503, 'oauth_failed', (string) $dbTok['error']);
}
$dbAccess = (string) $dbTok['access_token'];

$adminSnap = firebase_rtdb_get($dbUrl, 'clients/' . $clientId . '/admins/' . $uid, $dbAccess);
if (isset($adminSnap['error']) || !firebase_rtdb_is_truthy_admin($adminSnap['data'] ?? null)) {
    fum_json_fail(403, 'forbidden', 'Admin access required for this store.');
}

$projFromBoot = isset($boot['gcp_monitoring_project_id']) ? trim((string) $boot['gcp_monitoring_project_id']) : '';
$projRead = fum_read_gcp_project_id_from_service_account($saPath);
if (isset($projRead['error'])) {
    fum_json_fail(503, 'sa_invalid', (string) $projRead['error']);
}
$gcpProjectId = $projFromBoot !== '' ? $projFromBoot : (string) $projRead['project_id'];

$tableOverride = isset($boot['firebase_rtdb_table_name']) ? trim((string) $boot['firebase_rtdb_table_name']) : null;
if ($tableOverride === '') {
    $tableOverride = null;
}
$tableName = fum_rtdb_table_name_from_database_url($dbUrl, $tableOverride);

$storageBucket = isset($boot['gcp_storage_bucket_name']) ? trim((string) $boot['gcp_storage_bucket_name']) : '';
if ($storageBucket === '') {
    $storageBucket = isset($boot['firebase_storage_bucket']) ? trim((string) $boot['firebase_storage_bucket']) : '';
}

$monTok = firebase_service_account_access_token($saPath, MONITORING_READ_SCOPE);
if (isset($monTok['error'])) {
    fum_json_fail(503, 'monitoring_oauth_failed', (string) $monTok['error'] . ' Grant this service account roles/monitoring.viewer on the GCP project.');
}
$monAccess = (string) $monTok['access_token'];

$end = time();
$start = $end - 86400;
$endIso = gmdate('Y-m-d\TH:i:s\Z', $end);
$startIso = gmdate('Y-m-d\TH:i:s\Z', $start);

$escTable = str_replace(['\\', '"'], ['\\\\', '\\"'], $tableName);
$rtdbFilterDl = 'metric.type = "firebasedatabase.googleapis.com/network/sent_bytes_count" AND resource.type = "firebase_namespace" AND resource.label.table_name = "' . $escTable . '"';
$rtdbFilterStorage = 'metric.type = "firebasedatabase.googleapis.com/storage/total_bytes" AND resource.type = "firebase_namespace" AND resource.label.table_name = "' . $escTable . '"';
$rtdbFilterConn = 'metric.type = "firebasedatabase.googleapis.com/network/active_connections" AND resource.type = "firebase_namespace" AND resource.label.table_name = "' . $escTable . '"';

$dlList = fum_list_all_timeseries($monAccess, $gcpProjectId, $rtdbFilterDl, $startIso, $endIso);
if (isset($dlList['error'])) {
    fum_json_fail($dlList['http'] >= 400 ? $dlList['http'] : 502, 'monitoring_error', (string) $dlList['error']);
}
$download24h = fum_sum_metric_points($dlList['timeSeries'] ?? []);

$stList = fum_list_all_timeseries($monAccess, $gcpProjectId, $rtdbFilterStorage, $startIso, $endIso);
$storedBytes = null;
if (!isset($stList['error'])) {
    $storedBytes = fum_latest_gauge_int64($stList['timeSeries'] ?? []);
}

$connList = fum_list_all_timeseries($monAccess, $gcpProjectId, $rtdbFilterConn, $startIso, $endIso);
$activeConn = null;
if (!isset($connList['error'])) {
    $activeConn = fum_latest_gauge_int64($connList['timeSeries'] ?? []);
}

$storageOut = null;
if ($storageBucket !== '') {
    $escB = str_replace(['\\', '"'], ['\\\\', '\\"'], $storageBucket);
    $gcsFilter = 'metric.type = "storage.googleapis.com/storage/total_bytes" AND resource.type = "gcs_bucket" AND resource.label.bucket_name = "' . $escB . '"';
    $gcsList = fum_list_all_timeseries($monAccess, $gcpProjectId, $gcsFilter, $startIso, $endIso);
    if (!isset($gcsList['error'])) {
        $storageOut = [
            'bucketName' => $storageBucket,
            'totalBytes' => fum_latest_gauge_int64($gcsList['timeSeries'] ?? []),
        ];
    }
}

echo json_encode([
    'ok' => true,
    'scope' => 'gcp_project',
    'scopeNote' => 'These numbers are for the whole Firebase/GCP project and RTDB instance — not split per store (clientId). Multiple stores sharing the same database add together here.',
    'scopeNoteBn' => 'এই সংখ্যাগুলো পুরো Firebase/GCP প্রোজেক্ট ও RTDB ইনস্ট্যান্সের — প্রতিটি স্টোর (clientId) আলাদা করে নয়। একই ডাটাবেস শেয়ার করলে সব মিলিয়ে দেখাবে।',
    'samplingNote' => 'Cloud Monitoring can lag a few minutes. RTDB stored size is sampled on a coarse schedule (often up to ~24h). Console totals may differ slightly from billing.',
    'samplingNoteBn' => 'Cloud Monitoring-এ কিছু মিনিট দেরি হতে পারে। RTDB স্টোর সাইজ মোটা সময় অন্তর স্যাম্পল হয়। কনসোল vs বিলিং রিপোর্টে সামান্য পার্থক্য থাকতে পারে।',
    'lastUpdated' => (int) round(microtime(true) * 1000),
    'gcpProjectId' => $gcpProjectId,
    'rtdb' => [
        'tableName' => $tableName,
        'downloadBytesLast24h' => $download24h,
        'storedBytes' => $storedBytes,
        'activeConnections' => $activeConn,
    ],
    'storage' => $storageOut,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
