<?php

declare(strict_types=1);

require_once __DIR__ . '/includes/bootstrap.php';

$action = $_GET['action'] ?? $_POST['action'] ?? null;
if (!is_string($action) || $action === '') {
    fail('Missing action', 400);
}

function get_param(array $body, string $key): ?string
{
    $v = $body[$key] ?? $_POST[$key] ?? $_GET[$key] ?? null;
    if ($v === null) {
        return null;
    }
    if (is_string($v)) {
        return trim($v);
    }
    return trim((string)$v);
}

function normalize_email(string $email): string
{
    return strtolower(trim($email));
}

function business_accounts_path(): string
{
    return defined('BUSINESS_ACCOUNTS_CSV') ? BUSINESS_ACCOUNTS_CSV : (DATA_DIR . '/business_accounts.csv');
}

function business_accounts_read(): array
{
    return csv_read_assoc(business_accounts_path(), business_accounts_headers());
}

function push_lib_available(): bool
{
    return file_exists(__DIR__ . '/includes/push.php') && file_exists(__DIR__ . '/vendor/autoload.php');
}

function push_platform_supported(): bool
{
    // minishlink/web-push (v9) requires PHP >= 8.2 via Composer's platform_check.
    return defined('PHP_VERSION_ID') && PHP_VERSION_ID >= 80200;
}

function push_require_library(): void
{
    if (!push_platform_supported()) {
        throw new RuntimeException('Push requires PHP >= 8.2.0 (server is ' . PHP_VERSION . ')');
    }

    // Composer's platform_check.php may echo plain text before throwing.
    // If that happens during an API request, it corrupts our JSON response.
    $level = ob_get_level();
    ob_start();
    try {
        require_once __DIR__ . '/includes/push.php';

        $out = (string)ob_get_clean();
        $outTrim = trim($out);
        if ($outTrim !== '') {
            $oneLine = preg_replace('/\s+/', ' ', $outTrim);
            $snippet = function_exists('mb_substr')
                ? mb_substr((string)$oneLine, 0, 200)
                : substr((string)$oneLine, 0, 200);
            throw new RuntimeException('Push init produced unexpected output: ' . $snippet);
        }
    } catch (Throwable $e) {
        $out = '';
        while (ob_get_level() > $level) {
            $out .= (string)ob_get_clean();
        }

        $outTrim = trim($out);
        if ($outTrim !== '') {
            // Include a short snippet for debugging but do not leak the raw output.
            $oneLine = preg_replace('/\s+/', ' ', $outTrim);
            $snippet = function_exists('mb_substr')
                ? mb_substr((string)$oneLine, 0, 200)
                : substr((string)$oneLine, 0, 200);
            throw new RuntimeException($e->getMessage() . ' (output suppressed: ' . $snippet . ')', 0, $e);
        }

        throw $e;
    }
}

function request_base_url(): string
{
    $xfProto = (string)($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '');
    $xfHost = (string)($_SERVER['HTTP_X_FORWARDED_HOST'] ?? '');

    $isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
    if ($xfProto !== '') {
        $isHttps = strtolower(trim(explode(',', $xfProto)[0])) === 'https';
    }

    $scheme = $isHttps ? 'https' : 'http';
    $host = $xfHost !== '' ? trim(explode(',', $xfHost)[0]) : (string)($_SERVER['HTTP_HOST'] ?? '');
    $script = (string)($_SERVER['SCRIPT_NAME'] ?? '/api.php');
    $dir = rtrim(str_replace('\\', '/', dirname($script)), '/');
    return $scheme . '://' . $host . ($dir === '' ? '' : $dir) . '/';
}

function business_accounts_write(array $rows): void
{
    csv_write_assoc(business_accounts_path(), business_accounts_headers(), $rows);
}

function require_business_login_json(): array
{
    $acc = business_session();
    if ($acc === null) {
        fail('Unauthorized (business)', 401);
    }
    return $acc;
}

function sales_headers(): array
{
    return ['business_id', 'message', 'end_at', 'created_at', 'updated_at'];
}

function sale_events_headers(): array
{
    return ['business_id', 'message', 'end_at', 'created_at'];
}

function parse_iso_time(string $iso): ?int
{
    $t = strtotime($iso);
    if ($t === false) {
        return null;
    }
    return $t;
}

function get_active_sale_for_business(string $businessId): ?array
{
    if ($businessId === '') {
        return null;
    }

    $sales = csv_read_assoc(SALES_CSV, sales_headers());
    $row = csv_find_one($sales, fn($s) => (string)($s['business_id'] ?? '') === $businessId);
    if ($row === null) {
        return null;
    }

    $endAt = (string)($row['end_at'] ?? '');
    $endTs = parse_iso_time($endAt);
    if ($endTs === null) {
        return null;
    }
    $now = time();
    if ($endTs <= $now) {
        return null;
    }

    return [
        'message' => (string)($row['message'] ?? ''),
        'end_at' => $endAt,
        'remaining_seconds' => max(0, $endTs - $now),
    ];
}

function now_iso(): string
{
    return gmdate('c');
}

function is_valid_lat_lng(string $lat, string $lng): bool
{
    if (!is_numeric($lat) || !is_numeric($lng)) {
        return false;
    }
    $latF = (float)$lat;
    $lngF = (float)$lng;
    return $latF >= -90 && $latF <= 90 && $lngF >= -180 && $lngF <= 180;
}

$body = [];
if (in_array(strtoupper($_SERVER['REQUEST_METHOD'] ?? ''), ['POST', 'PUT', 'PATCH'], true)) {
    // Accept JSON bodies (fetch) and regular form posts
    $contentType = $_SERVER['CONTENT_TYPE'] ?? '';
    if (str_contains($contentType, 'application/json')) {
        $body = read_json_body();
    }
}

try {
    switch ($action) {
        case 'debug_push_status': {
            require_method('GET');

            $user = current_user();
            $uid = $user ? (string)($user['id'] ?? '') : '';

            $pushAvailable = push_lib_available();
            $pushSupported = push_platform_supported();
            $vapidExists = file_exists(VAPID_KEYS_JSON);

            $myCount = 0;
            $total = 0;
            if ($pushAvailable && $pushSupported && file_exists(PUSH_SUBSCRIPTIONS_CSV)) {
                try {
                    push_require_library();
                    $rows = csv_read_assoc(PUSH_SUBSCRIPTIONS_CSV, push_subscriptions_headers());
                    $total = count($rows);
                    if ($uid !== '') {
                        foreach ($rows as $r) {
                            if ((string)($r['user_id'] ?? '') === $uid) {
                                $myCount++;
                            }
                        }
                    }
                } catch (Throwable $e) {
                    // Diagnostics only; do not fail the endpoint.
                }
            }

            ok([
                'logged_in' => $user !== null,
                'push_lib_available' => $pushAvailable,
                'push_platform_supported' => $pushSupported,
                'vapid_keys_present' => $vapidExists,
                'push_subscriptions_total' => $total,
                'push_subscriptions_for_me' => $myCount,
            ]);
        }

        case 'session': {
            $user = current_user();
            ok([
                'logged_in' => $user !== null,
                'user' => $user ? public_user_fields($user) : null,
            ]);
        }

        case 'push_public_key': {
            require_method('GET');
            require_login_json();
            if (!push_lib_available()) {
                fail('Push not installed', 503);
            }
            if (!push_platform_supported()) {
                fail('Push requires PHP >= 8.2 on the server', 503, ['php_version' => PHP_VERSION]);
            }
            if (!file_exists(VAPID_KEYS_JSON)) {
                fail('Push not configured (missing VAPID keys). Run tools/generate_vapid.php', 503);
            }
            push_require_library();
            ok(['public_key' => push_get_public_key()]);
        }

        case 'push_subscribe': {
            require_method('POST');
            $user = require_login_json();
            if (!push_lib_available()) {
                fail('Push not installed', 503);
            }
            if (!push_platform_supported()) {
                fail('Push requires PHP >= 8.2 on the server', 503, ['php_version' => PHP_VERSION]);
            }
            if (!file_exists(VAPID_KEYS_JSON)) {
                fail('Push not configured (missing VAPID keys). Run tools/generate_vapid.php', 503);
            }

            $sub = $body['subscription'] ?? $body;
            if (!is_array($sub)) {
                fail('Invalid subscription payload', 400);
            }

            push_require_library();
            push_upsert_subscription((string)($user['id'] ?? ''), $sub);
            ok(['subscribed' => true]);
        }

        case 'my_subscriptions': {
            require_method('GET');
            $user = require_login_json();

            $uid = (string)($user['id'] ?? '');
            $subs = csv_read_assoc(SUBSCRIPTIONS_CSV, subscriptions_headers());
            $subs = csv_filter($subs, fn($s) => (string)($s['user_id'] ?? '') === $uid);

            $biz = csv_read_assoc(BUSINESSES_CSV, businesses_headers());
            $bizById = [];
            foreach ($biz as $b) {
                $bid = (string)($b['id'] ?? '');
                if ($bid === '') {
                    continue;
                }
                $bizById[$bid] = $b;
            }

            $out = [];
            foreach ($subs as $s) {
                $bid = (string)($s['business_id'] ?? '');
                if ($bid === '' || !isset($bizById[$bid])) {
                    continue;
                }
                $b = $bizById[$bid];
                $out[] = [
                    'business_id' => $bid,
                    'name' => (string)($b['name'] ?? ''),
                    'icon_url' => (string)($b['icon_url'] ?? ''),
                    'pin_color' => (string)($b['pin_color'] ?? ''),
                ];
            }

            ok(['subscriptions' => $out]);
        }

        case 'business_session': {
            $acc = business_session();
            if ($acc === null) {
                ok(['logged_in' => false, 'account' => null, 'business' => null]);
            }

            $biz = csv_read_assoc(BUSINESSES_CSV, businesses_headers());
            $b = csv_find_one($biz, fn($row) => (string)($row['id'] ?? '') === (string)($acc['business_id'] ?? ''));

            ok([
                'logged_in' => true,
                'account' => [
                    'email' => (string)($acc['email'] ?? ''),
                    'business_id' => (string)($acc['business_id'] ?? ''),
                ],
                'business' => $b ? [
                    'id' => (string)($b['id'] ?? ''),
                    'name' => (string)($b['name'] ?? ''),
                    'address' => (string)($b['address'] ?? ''),
                    'description' => (string)($b['description'] ?? ''),
                    'lat' => (float)($b['lat'] ?? 0),
                    'lng' => (float)($b['lng'] ?? 0),
                    'image_url' => (string)($b['image_url'] ?? ''),
                    'icon_url' => (string)($b['icon_url'] ?? ''),
                    'pin_color' => (string)($b['pin_color'] ?? ''),
                ] : null,
            ]);
        }

        case 'business_login': {
            require_method('POST');
            $email = normalize_email(get_param($body, 'email') ?? '');
            $password = get_param($body, 'password') ?? '';

            if ($email === '' || $password === '') {
                fail('Email and password are required', 400);
            }

            $accounts = business_accounts_read();
            $acc = csv_find_one($accounts, fn($a) => normalize_email($a['email'] ?? '') === $email);
            if ($acc === null) {
                fail('Invalid business credentials', 401);
            }
            $hash = (string)($acc['password_hash'] ?? '');
            if ($hash === '' || !password_verify($password, $hash)) {
                fail('Invalid business credentials', 401);
            }

            set_business_session((string)$acc['id'], (string)$acc['business_id']);
            ok(['logged_in' => true]);
        }

        case 'business_logout': {
            require_method('POST');
            logout_business();
            ok(['logged_out' => true]);
        }

        case 'business_update_my': {
            require_method('POST');
            $acc = require_business_login_json();
            $businessId = (string)($acc['business_id'] ?? '');
            if ($businessId === '') {
                fail('Business session invalid', 401);
            }

            $name = get_param($body, 'name') ?? '';
            $address = get_param($body, 'address') ?? '';
            $description = get_param($body, 'description') ?? '';
            $lat = get_param($body, 'lat') ?? '';
            $lng = get_param($body, 'lng') ?? '';
            $imageUrl = get_param($body, 'image_url') ?? '';
            $iconUrl = get_param($body, 'icon_url') ?? '';
            $pinColor = get_param($body, 'pin_color') ?? '';

            if ($name === '' || $address === '' || $description === '' || $lat === '' || $lng === '') {
                fail('Missing required fields', 400);
            }
            if (!is_valid_lat_lng($lat, $lng)) {
                fail('Invalid coordinates', 400);
            }

            $biz = csv_read_assoc(BUSINESSES_CSV, businesses_headers());
            $found = false;
            $updated = [];
            foreach ($biz as $b) {
                if ((string)($b['id'] ?? '') !== $businessId) {
                    $updated[] = $b;
                    continue;
                }

                $b['name'] = $name;
                $b['address'] = $address;
                $b['description'] = $description;
                $b['lat'] = (string)(float)$lat;
                $b['lng'] = (string)(float)$lng;
                $b['image_url'] = $imageUrl;
                $b['icon_url'] = $iconUrl;
                $b['pin_color'] = $pinColor;
                $updated[] = $b;
                $found = true;
            }

            if (!$found) {
                fail('Business not found', 404);
            }

            csv_write_assoc(BUSINESSES_CSV, businesses_headers(), $updated);
            ok(['updated' => true]);
        }

        case 'business_start_sale': {
            require_method('POST');
            $acc = require_business_login_json();
            $businessId = (string)($acc['business_id'] ?? '');
            if ($businessId === '') {
                fail('Business session invalid', 401);
            }

            $message = get_param($body, 'message') ?? '';
            $durationMinutes = get_param($body, 'duration_minutes') ?? '';

            if ($message === '' || $durationMinutes === '') {
                fail('Message and duration_minutes are required', 400);
            }
            if (!ctype_digit($durationMinutes)) {
                fail('duration_minutes must be a whole number', 400);
            }
            $mins = (int)$durationMinutes;
            if ($mins < 1 || $mins > 24 * 60) {
                fail('duration_minutes must be between 1 and 1440', 400);
            }
            if (mb_strlen($message) > 240) {
                fail('Message is too long (max 240 chars)', 400);
            }

            // Ensure business exists
            $biz = csv_read_assoc(BUSINESSES_CSV, businesses_headers());
            $exists = csv_find_one($biz, fn($b) => (string)($b['id'] ?? '') === $businessId);
            if ($exists === null) {
                fail('Business not found', 404);
            }

            $nowTs = time();
            $endTs = $nowTs + ($mins * 60);
            $endAt = gmdate('c', $endTs);
            $ts = now_iso();

            // Upsert sales
            $sales = csv_read_assoc(SALES_CSV, sales_headers());
            $next = [];
            $updated = false;
            foreach ($sales as $s) {
                if ((string)($s['business_id'] ?? '') !== $businessId) {
                    $next[] = $s;
                    continue;
                }
                $s['message'] = $message;
                $s['end_at'] = $endAt;
                $s['updated_at'] = $ts;
                $next[] = $s;
                $updated = true;
            }
            if (!$updated) {
                $next[] = [
                    'business_id' => $businessId,
                    'message' => $message,
                    'end_at' => $endAt,
                    'created_at' => $ts,
                    'updated_at' => $ts,
                ];
            }
            csv_write_assoc(SALES_CSV, sales_headers(), $next);

            // Append event so subscribers can be notified
            csv_append_assoc(SALE_EVENTS_CSV, sale_events_headers(), [
                'business_id' => $businessId,
                'message' => $message,
                'end_at' => $endAt,
                'created_at' => $ts,
            ]);

            // Web Push (background notifications). Best-effort; sale still succeeds if push fails.
            $pushStats = null;

            // Collect basic diagnostics (safe to include in responses/logs).
            $pushDiag = [
                'php_version' => PHP_VERSION,
                'push_lib_available' => push_lib_available(),
                'push_platform_supported' => push_platform_supported(),
                'vapid_keys_present' => file_exists(VAPID_KEYS_JSON),
                'business_id' => $businessId,
                'stage' => 'init',
            ];

            try {
                // Push is best-effort. On hosts with older PHP, Composer may emit plaintext
                // (platform_check) which would break our JSON output; so we hard-disable it.
                if (push_lib_available() && push_platform_supported() && file_exists(VAPID_KEYS_JSON)) {
                    push_require_library();
                    $pushDiag['stage'] = 'loaded';

                    $subs = csv_read_assoc(SUBSCRIPTIONS_CSV, subscriptions_headers());
                    $userIds = [];
                    foreach ($subs as $s) {
                        if ((string)($s['business_id'] ?? '') !== $businessId) {
                            continue;
                        }
                        $uid = (string)($s['user_id'] ?? '');
                        if ($uid !== '') {
                            $userIds[] = $uid;
                        }
                    }

                    $pushDiag['business_subscriptions_count'] = count($userIds);

                    // Count how many device push subscriptions exist for these users.
                    // (This frequently explains "0 devices registered" while the business has subscribers.)
                    $pushDiag['push_subscriptions_total'] = 0;
                    $pushDiag['push_subscriptions_for_targets'] = 0;
                    if (file_exists(PUSH_SUBSCRIPTIONS_CSV)) {
                        $rows = csv_read_assoc(PUSH_SUBSCRIPTIONS_CSV, ['id', 'user_id', 'endpoint', 'p256dh', 'auth', 'created_at', 'updated_at']);
                        $pushDiag['push_subscriptions_total'] = count($rows);
                        if (!empty($userIds)) {
                            $uidSet = [];
                            foreach ($userIds as $uid) {
                                $uidSet[(string)$uid] = true;
                            }
                            $countTargets = 0;
                            foreach ($rows as $r) {
                                $uid = (string)($r['user_id'] ?? '');
                                if ($uid !== '' && isset($uidSet[$uid])) {
                                    $countTargets++;
                                }
                            }
                            $pushDiag['push_subscriptions_for_targets'] = $countTargets;
                        }
                    }

                    $pushDiag['stage'] = 'send';

                    $payload = [
                        'title' => 'TimeZup Sale',
                        'message' => $message,
                        'business_id' => $businessId,
                        'end_at' => $endAt,
                        'url' => request_base_url() . 'index.php',
                    ];

                    $pushStats = push_send_to_users($userIds, $payload);
                    if (is_array($pushStats)) {
                        $pushStats['debug'] = $pushDiag;
                    }
                }
            } catch (Throwable $e) {
                // Log server-side for easy diagnosis (Apache/PHP error log).
                $log = [
                    'error_type' => get_class($e),
                    'error_message' => $e->getMessage(),
                    'diag' => $pushDiag,
                ];
                error_log('TimeZup push failed: ' . json_encode($log, JSON_UNESCAPED_SLASHES));

                $pushStats = [
                    'error' => $e->getMessage(),
                    'error_type' => get_class($e),
                    'debug' => $pushDiag,
                ];
            }

            ok([
                'business_id' => $businessId,
                'message' => $message,
                'end_at' => $endAt,
                'remaining_seconds' => max(0, $endTs - $nowTs),
                'push' => $pushStats,
            ]);
        }

        case 'signup': {
            require_method('POST');

            $name = get_param($body, 'name') ?? '';
            $surname = get_param($body, 'surname') ?? '';
            $email = normalize_email(get_param($body, 'email') ?? '');
            $password = get_param($body, 'password') ?? '';

            if ($name === '' || $surname === '' || $email === '' || $password === '') {
                fail('Missing required fields', 400);
            }
            if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
                fail('Invalid email', 400);
            }
            if (strlen($password) < 6) {
                fail('Password must be at least 6 characters', 400);
            }

            $users = csv_read_assoc(USERS_CSV, users_headers());
            $existing = csv_find_one($users, fn($u) => normalize_email($u['email'] ?? '') === $email);
            if ($existing !== null) {
                fail('Email already registered', 409);
            }

            $id = generate_id();
            $ts = now_iso();
            $new = [
                'id' => $id,
                'name' => $name,
                'surname' => $surname,
                'email' => $email,
                'password_hash' => password_hash($password, PASSWORD_DEFAULT),
                'role' => 'user',
                'created_at' => $ts,
                'updated_at' => $ts,
            ];

            $users[] = $new;
            csv_write_assoc(USERS_CSV, users_headers(), $users);

            set_session_user_id($id);
            ok(['user' => public_user_fields($new)]);
        }

        case 'login': {
            require_method('POST');

            $email = normalize_email(get_param($body, 'email') ?? '');
            $password = get_param($body, 'password') ?? '';

            if ($email === '' || $password === '') {
                fail('Email and password are required', 400);
            }

            $users = csv_read_assoc(USERS_CSV, users_headers());
            $user = csv_find_one($users, fn($u) => normalize_email($u['email'] ?? '') === $email);
            if ($user === null) {
                fail('Invalid credentials', 401);
            }

            $hash = (string)($user['password_hash'] ?? '');
            if ($hash === '' || !password_verify($password, $hash)) {
                fail('Invalid credentials', 401);
            }

            set_session_user_id((string)$user['id']);
            ok(['user' => public_user_fields($user)]);
        }

        case 'logout': {
            require_method('POST');
            logout_user();
            ok(['logged_out' => true]);
        }

        case 'update_profile': {
            require_method('POST');
            $user = require_login_json();

            $name = get_param($body, 'name') ?? '';
            $surname = get_param($body, 'surname') ?? '';
            $email = normalize_email(get_param($body, 'email') ?? '');
            $newPassword = get_param($body, 'new_password');

            if ($name === '' || $surname === '' || $email === '') {
                fail('Name, surname and email are required', 400);
            }
            if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
                fail('Invalid email', 400);
            }
            if ($newPassword !== null && $newPassword !== '' && strlen($newPassword) < 6) {
                fail('New password must be at least 6 characters', 400);
            }

            $users = csv_read_assoc(USERS_CSV, users_headers());
            $myId = (string)($user['id'] ?? '');

            $emailTaken = csv_find_one($users, function ($u) use ($email, $myId) {
                $uid = (string)($u['id'] ?? '');
                if ($uid === $myId) {
                    return false;
                }
                return normalize_email($u['email'] ?? '') === $email;
            });
            if ($emailTaken !== null) {
                fail('Email already in use', 409);
            }

            $updatedUsers = [];
            $updatedMe = null;
            $ts = now_iso();
            foreach ($users as $u) {
                if ((string)($u['id'] ?? '') !== $myId) {
                    $updatedUsers[] = $u;
                    continue;
                }

                $u['name'] = $name;
                $u['surname'] = $surname;
                $u['email'] = $email;
                if ($newPassword !== null && $newPassword !== '') {
                    $u['password_hash'] = password_hash($newPassword, PASSWORD_DEFAULT);
                }
                $u['updated_at'] = $ts;
                $updatedMe = $u;
                $updatedUsers[] = $u;
            }

            if ($updatedMe === null) {
                fail('User not found', 404);
            }

            csv_write_assoc(USERS_CSV, users_headers(), $updatedUsers);
            ok(['user' => public_user_fields($updatedMe)]);
        }

        case 'businesses_list': {
            require_method('GET');
            $biz = csv_read_assoc(BUSINESSES_CSV, businesses_headers());

            $out = [];
            foreach ($biz as $b) {
                if (($b['id'] ?? '') === '') {
                    continue;
                }
                $out[] = [
                    'id' => (string)$b['id'],
                    'name' => (string)($b['name'] ?? ''),
                    'lat' => (float)($b['lat'] ?? 0),
                    'lng' => (float)($b['lng'] ?? 0),
                    'icon_url' => (string)($b['icon_url'] ?? ''),
                    'avg_rating' => (float)($b['avg_rating'] ?? 0),
                    'pin_color' => (string)($b['pin_color'] ?? ''),
                ];
            }

            ok(['businesses' => $out]);
        }

        case 'business_get': {
            require_method('GET');
            $id = get_param($body, 'id') ?? '';
            if ($id === '') {
                fail('Missing id', 400);
            }

            $biz = csv_read_assoc(BUSINESSES_CSV, businesses_headers());
            $b = csv_find_one($biz, fn($row) => (string)($row['id'] ?? '') === $id);
            if ($b === null) {
                fail('Business not found', 404);
            }

            $user = current_user();
            $subscribed = false;
            if ($user !== null) {
                $subs = csv_read_assoc(SUBSCRIPTIONS_CSV, subscriptions_headers());
                $uid = (string)($user['id'] ?? '');
                $subscribed = csv_find_one($subs, fn($s) => (string)($s['user_id'] ?? '') === $uid && (string)($s['business_id'] ?? '') === $id) !== null;
            }

            $sale = get_active_sale_for_business($id);

            ok([
                'business' => [
                    'id' => (string)($b['id'] ?? ''),
                    'name' => (string)($b['name'] ?? ''),
                    'address' => (string)($b['address'] ?? ''),
                    'description' => (string)($b['description'] ?? ''),
                    'lat' => (float)($b['lat'] ?? 0),
                    'lng' => (float)($b['lng'] ?? 0),
                    'image_url' => (string)($b['image_url'] ?? ''),
                    'icon_url' => (string)($b['icon_url'] ?? ''),
                    'avg_rating' => (float)($b['avg_rating'] ?? 0),
                    'pin_color' => (string)($b['pin_color'] ?? ''),
                ],
                'sale' => $sale,
                'subscribed' => $subscribed,
                'logged_in' => $user !== null,
            ]);
        }

        case 'subscription_toggle': {
            require_method('POST');
            $user = require_login_json();

            $businessId = get_param($body, 'business_id') ?? '';
            if ($businessId === '') {
                fail('Missing business_id', 400);
            }

            $biz = csv_read_assoc(BUSINESSES_CSV, businesses_headers());
            $exists = csv_find_one($biz, fn($b) => (string)($b['id'] ?? '') === $businessId);
            if ($exists === null) {
                fail('Business not found', 404);
            }

            $subs = csv_read_assoc(SUBSCRIPTIONS_CSV, subscriptions_headers());
            $uid = (string)($user['id'] ?? '');

            $already = csv_find_one($subs, fn($s) => (string)($s['user_id'] ?? '') === $uid && (string)($s['business_id'] ?? '') === $businessId);

            if ($already !== null) {
                $subs = csv_filter($subs, fn($s) => !((string)($s['user_id'] ?? '') === $uid && (string)($s['business_id'] ?? '') === $businessId));
                csv_write_assoc(SUBSCRIPTIONS_CSV, subscriptions_headers(), $subs);
                ok(['subscribed' => false]);
            }

            $subs[] = [
                'user_id' => $uid,
                'business_id' => $businessId,
                'created_at' => now_iso(),
            ];
            csv_write_assoc(SUBSCRIPTIONS_CSV, subscriptions_headers(), $subs);
            ok(['subscribed' => true]);
        }

        case 'admin_business_list': {
            require_method('GET');
            require_admin_json();
            $biz = csv_read_assoc(BUSINESSES_CSV, businesses_headers());

            $accounts = business_accounts_read();
            $emailByBiz = [];
            foreach ($accounts as $a) {
                $bid = (string)($a['business_id'] ?? '');
                if ($bid === '') {
                    continue;
                }
                $emailByBiz[$bid] = (string)($a['email'] ?? '');
            }

            $out = [];
            foreach ($biz as $b) {
                $bid = (string)($b['id'] ?? '');
                if ($bid !== '') {
                    $b['login_email'] = $emailByBiz[$bid] ?? '';
                }
                $out[] = $b;
            }

            ok(['businesses' => $out]);
        }

        case 'admin_business_create': {
            require_method('POST');
            require_admin_json();

            $name = get_param($body, 'name') ?? '';
            $address = get_param($body, 'address') ?? '';
            $description = get_param($body, 'description') ?? '';
            $lat = get_param($body, 'lat') ?? '';
            $lng = get_param($body, 'lng') ?? '';
            $imageUrl = get_param($body, 'image_url') ?? '';
            $iconUrl = get_param($body, 'icon_url') ?? '';
            $avgRating = get_param($body, 'avg_rating') ?? '0';
            $pinColor = get_param($body, 'pin_color') ?? '';
            $loginEmail = normalize_email(get_param($body, 'login_email') ?? '');
            $loginPassword = get_param($body, 'login_password') ?? '';

            if ($name === '' || $address === '' || $description === '' || $lat === '' || $lng === '') {
                fail('Missing required fields', 400);
            }
            if (!is_valid_lat_lng($lat, $lng)) {
                fail('Invalid coordinates', 400);
            }
            if ($avgRating !== '' && !is_numeric($avgRating)) {
                fail('avg_rating must be numeric', 400);
            }

            if ($loginEmail === '' || !filter_var($loginEmail, FILTER_VALIDATE_EMAIL)) {
                fail('Business login email is required', 400);
            }
            if (strlen($loginPassword) < 6) {
                fail('Business login password must be at least 6 characters', 400);
            }

            $biz = csv_read_assoc(BUSINESSES_CSV, businesses_headers());
            $id = generate_id();

            $biz[] = [
                'id' => $id,
                'name' => $name,
                'address' => $address,
                'description' => $description,
                'lat' => (string)(float)$lat,
                'lng' => (string)(float)$lng,
                'image_url' => $imageUrl,
                'icon_url' => $iconUrl,
                'avg_rating' => (string)(float)$avgRating,
                'pin_color' => $pinColor,
            ];

            csv_write_assoc(BUSINESSES_CSV, businesses_headers(), $biz);

            // Create or replace business account
            $accounts = business_accounts_read();
            $accounts = csv_filter($accounts, fn($a) => (string)($a['business_id'] ?? '') !== $id && normalize_email($a['email'] ?? '') !== $loginEmail);
            $ts = now_iso();
            $accounts[] = [
                'id' => generate_id(),
                'business_id' => $id,
                'email' => $loginEmail,
                'password_hash' => password_hash($loginPassword, PASSWORD_DEFAULT),
                'created_at' => $ts,
                'updated_at' => $ts,
            ];
            business_accounts_write($accounts);

            ok(['id' => $id]);
        }

        case 'admin_business_update': {
            require_method('POST');
            require_admin_json();

            $id = get_param($body, 'id') ?? '';
            if ($id === '') {
                fail('Missing id', 400);
            }

            $name = get_param($body, 'name') ?? '';
            $address = get_param($body, 'address') ?? '';
            $description = get_param($body, 'description') ?? '';
            $lat = get_param($body, 'lat') ?? '';
            $lng = get_param($body, 'lng') ?? '';
            $imageUrl = get_param($body, 'image_url') ?? '';
            $iconUrl = get_param($body, 'icon_url') ?? '';
            $avgRating = get_param($body, 'avg_rating') ?? '0';
            $pinColor = get_param($body, 'pin_color') ?? '';
            $loginEmailRaw = get_param($body, 'login_email');
            $loginPassword = get_param($body, 'login_password');
            $loginEmail = $loginEmailRaw === null ? null : normalize_email($loginEmailRaw);

            if ($name === '' || $address === '' || $description === '' || $lat === '' || $lng === '') {
                fail('Missing required fields', 400);
            }
            if (!is_valid_lat_lng($lat, $lng)) {
                fail('Invalid coordinates', 400);
            }
            if ($avgRating !== '' && !is_numeric($avgRating)) {
                fail('avg_rating must be numeric', 400);
            }

            if ($loginEmail !== null && $loginEmail !== '' && !filter_var($loginEmail, FILTER_VALIDATE_EMAIL)) {
                fail('Invalid business login email', 400);
            }
            if ($loginPassword !== null && $loginPassword !== '' && strlen($loginPassword) < 6) {
                fail('Business login password must be at least 6 characters', 400);
            }

            $biz = csv_read_assoc(BUSINESSES_CSV, businesses_headers());
            $found = false;
            $updated = [];
            foreach ($biz as $b) {
                if ((string)($b['id'] ?? '') !== $id) {
                    $updated[] = $b;
                    continue;
                }

                $b['name'] = $name;
                $b['address'] = $address;
                $b['description'] = $description;
                $b['lat'] = (string)(float)$lat;
                $b['lng'] = (string)(float)$lng;
                $b['image_url'] = $imageUrl;
                $b['icon_url'] = $iconUrl;
                $b['avg_rating'] = (string)(float)$avgRating;
                $b['pin_color'] = $pinColor;
                $updated[] = $b;
                $found = true;
            }

            if (!$found) {
                fail('Business not found', 404);
            }

            csv_write_assoc(BUSINESSES_CSV, businesses_headers(), $updated);

            // Update business login (optional)
            if ($loginEmail !== null) {
                $accounts = business_accounts_read();
                $ts = now_iso();

                if ($loginEmail === '') {
                    // Remove login
                    $accounts = csv_filter($accounts, fn($a) => (string)($a['business_id'] ?? '') !== $id);
                    business_accounts_write($accounts);
                } else {
                    $existing = csv_find_one($accounts, fn($a) => (string)($a['business_id'] ?? '') === $id);
                    $isNew = ($existing === null);

                    if ($isNew && ($loginPassword === null || $loginPassword === '')) {
                        fail('Set a password to create a new business login', 400);
                    }

                    // Ensure unique email across business accounts
                    $accounts = csv_filter($accounts, fn($a) => normalize_email($a['email'] ?? '') !== $loginEmail || (string)($a['business_id'] ?? '') === $id);

                    $next = [];
                    $updatedAccount = false;
                    foreach ($accounts as $a) {
                        if ((string)($a['business_id'] ?? '') !== $id) {
                            $next[] = $a;
                            continue;
                        }
                        $a['email'] = $loginEmail;
                        if ($loginPassword !== null && $loginPassword !== '') {
                            $a['password_hash'] = password_hash($loginPassword, PASSWORD_DEFAULT);
                        }
                        $a['updated_at'] = $ts;
                        $next[] = $a;
                        $updatedAccount = true;
                    }
                    if (!$updatedAccount) {
                        $next[] = [
                            'id' => generate_id(),
                            'business_id' => $id,
                            'email' => $loginEmail,
                            'password_hash' => password_hash((string)$loginPassword, PASSWORD_DEFAULT),
                            'created_at' => $ts,
                            'updated_at' => $ts,
                        ];
                    }
                    business_accounts_write($next);
                }
            }

            ok(['updated' => true]);
        }

        case 'admin_business_delete': {
            require_method('POST');
            require_admin_json();

            $id = get_param($body, 'id') ?? '';
            if ($id === '') {
                fail('Missing id', 400);
            }

            $biz = csv_read_assoc(BUSINESSES_CSV, businesses_headers());
            $before = count($biz);
            $biz = csv_filter($biz, fn($b) => (string)($b['id'] ?? '') !== $id);
            if (count($biz) === $before) {
                fail('Business not found', 404);
            }
            csv_write_assoc(BUSINESSES_CSV, businesses_headers(), $biz);

            // Remove subscriptions pointing to this business
            $subs = csv_read_assoc(SUBSCRIPTIONS_CSV, subscriptions_headers());
            $subs = csv_filter($subs, fn($s) => (string)($s['business_id'] ?? '') !== $id);
            csv_write_assoc(SUBSCRIPTIONS_CSV, subscriptions_headers(), $subs);

            // Remove business login
            $accounts = business_accounts_read();
            $accounts = csv_filter($accounts, fn($a) => (string)($a['business_id'] ?? '') !== $id);
            business_accounts_write($accounts);

            ok(['deleted' => true]);
        }

        default:
            fail('Unknown action', 404, ['action' => $action]);
    }
} catch (Throwable $e) {
    fail('Server error', 500, ['message' => $e->getMessage()]);
}
