<?php
function e($v){return htmlspecialchars((string)$v,ENT_QUOTES,'UTF-8');}
function redirect($path='home'){header('Location: '.url($path));exit;}
function flash($key,$value=null){if($value!==null){$_SESSION['_flash'][$key]=$value;return;} $v=$_SESSION['_flash'][$key]??null;unset($_SESSION['_flash'][$key]);return $v;}
function old($key,$default=''){if(isset($_POST[$key]))return e((string)$_POST[$key]);$v=$_SESSION['_old'][$key]??$default;return e((string)$v);}
function url($path=''){global $config;return rtrim($config['app_url'],'/').'/'.ltrim($path,'/');}
function asset($path){return url('assets/'.$path);}
function current_user(){return $_SESSION['user']??null;}
function require_login(){if(!current_user()) redirect('login');}
function require_role($role){require_login(); if((current_user()['role']??'')!==$role){http_response_code(403);exit('Forbidden');}}
function age_from_dob($dob){return (new DateTime($dob))->diff(new DateTime('today'))->y;}
function notify($userId,$type,$title,$body){global $db;$db->exec('INSERT INTO notifications(user_id,type,title,body) VALUES(?,?,?,?)',[$userId,$type,$title,$body]);}
function log_admin($action,$type=null,$id=null,$meta=[]){global $db;$u=current_user();if(!$u)return;$db->exec('INSERT INTO admin_activity_logs(admin_id,action,target_type,target_id,metadata) VALUES(?,?,?,?,?)',[$u['id'],$action,$type,$id,json_encode($meta)]);}
function user_plan($uid){global $db;$r=$db->one('SELECT p.* FROM user_subscriptions s JOIN subscription_plans p ON p.id=s.plan_id WHERE s.user_id=? AND s.status="active" AND (s.ends_at IS NULL OR s.ends_at>NOW()) ORDER BY s.ends_at DESC LIMIT 1',[$uid]);return $r?:$db->one('SELECT * FROM subscription_plans WHERE name="Free" LIMIT 1');}
function has_plan($uid,$name){$p=user_plan($uid);$rank=['Free'=>0,'Plus'=>1,'Gold'=>2,'VIP'=>3,'Monthly Access'=>2];$current=is_array($p)?($p['name']??'Free'):'Free';return ($rank[$current]??0)>=($rank[$name]??0);}
function csrf(){return Csrf::input();}

/** Load admin payment settings (single row). */
function payment_settings(): array
{
    global $db;
    try {
        $row = $db->one('SELECT * FROM payment_settings WHERE id=1');
        if (is_array($row)) {
            return $row;
        }
    } catch (Throwable $e) {
        // table may not exist yet
    }
    return [
        'mpesa_enabled' => 0,
        'mpesa_env' => 'sandbox',
        'mpesa_consumer_key' => '',
        'mpesa_consumer_secret' => '',
        'mpesa_shortcode' => '',
        'mpesa_passkey' => '',
        'mpesa_callback_url' => '',
        'card_enabled' => 0,
        'card_provider' => 'stripe',
        'card_publishable_key' => '',
        'card_secret_key' => '',
        'card_webhook_secret' => '',
        'monthly_price_kes' => 500,
        'trial_hours' => 72,
        'mpesa_strict_ip' => 0,
    ];
}

function monthly_price_kes(): float
{
    return (float) (payment_settings()['monthly_price_kes'] ?? 500);
}

/**
 * True if user is within free trial OR has active paid access.
 */
function has_active_access(int $uid): bool
{
    global $db;
    try {
        $u = $db->one('SELECT trial_ends_at, paid_until, is_verified_paid, role FROM users WHERE id=?', [$uid]);
    } catch (Throwable $e) {
        // Migration not applied yet — fall back to subscription only
        try {
            $u = $db->one('SELECT role FROM users WHERE id=?', [$uid]);
        } catch (Throwable $e2) {
            return false;
        }
        if (($u['role'] ?? '') === 'admin') {
            return true;
        }
        $plan = user_plan($uid);
        return is_array($plan) && ($plan['name'] ?? 'Free') !== 'Free' && (float) ($plan['price_kes'] ?? 0) > 0;
    }
    if (!$u) {
        return false;
    }
    if (($u['role'] ?? '') === 'admin') {
        return true;
    }
    if (!empty($u['paid_until']) && strtotime($u['paid_until']) > time()) {
        return true;
    }
    if (!empty($u['is_verified_paid'])) {
        $sub = $db->one(
            'SELECT id FROM user_subscriptions WHERE user_id=? AND status="active" AND (ends_at IS NULL OR ends_at>NOW()) LIMIT 1',
            [$uid]
        );
        if ($sub) {
            return true;
        }
        if (!empty($u['paid_until']) && strtotime($u['paid_until']) > time()) {
            return true;
        }
    }
    if (!empty($u['trial_ends_at']) && strtotime($u['trial_ends_at']) > time()) {
        return true;
    }
    $plan = user_plan($uid);
    if (is_array($plan) && ($plan['name'] ?? 'Free') !== 'Free' && (float) ($plan['price_kes'] ?? 0) > 0) {
        return true;
    }
    return false;
}

/** Paid member (blue badge) — not trial-only. */
function is_paid_member(int $uid): bool
{
    global $db;
    try {
        $u = $db->one('SELECT paid_until, is_verified_paid, role FROM users WHERE id=?', [$uid]);
    } catch (Throwable $e) {
        try {
            $u = $db->one('SELECT role FROM users WHERE id=?', [$uid]);
        } catch (Throwable $e2) {
            return false;
        }
        if (($u['role'] ?? '') === 'admin') {
            return true;
        }
        $plan = user_plan($uid);
        return is_array($plan) && ($plan['name'] ?? 'Free') !== 'Free' && (float) ($plan['price_kes'] ?? 0) > 0;
    }
    if (!$u) {
        return false;
    }
    if (($u['role'] ?? '') === 'admin') {
        return true;
    }
    if (!empty($u['is_verified_paid']) && !empty($u['paid_until']) && strtotime($u['paid_until']) > time()) {
        return true;
    }
    if (!empty($u['paid_until']) && strtotime($u['paid_until']) > time()) {
        return true;
    }
    $plan = user_plan($uid);
    return is_array($plan) && ($plan['name'] ?? 'Free') !== 'Free' && (float) ($plan['price_kes'] ?? 0) > 0;
}

function trial_hours_left(int $uid): int
{
    global $db;
    if (is_paid_member($uid)) {
        return 0;
    }
    try {
        $u = $db->one('SELECT trial_ends_at FROM users WHERE id=?', [$uid]);
    } catch (Throwable $e) {
        return 0;
    }
    if (!$u || empty($u['trial_ends_at'])) {
        return 0;
    }
    $left = strtotime($u['trial_ends_at']) - time();
    return $left > 0 ? (int) ceil($left / 3600) : 0;
}

/** Activate paid monthly access after successful payment. */
function activate_paid_access(int $uid, string $provider, string $reference, ?float $amount = null): void
{
    global $db;
    $plan = $db->one('SELECT id FROM subscription_plans WHERE name="Monthly Access" AND active=1 LIMIT 1')
        ?: $db->one('SELECT id FROM subscription_plans WHERE price_kes > 0 AND active=1 ORDER BY price_kes ASC LIMIT 1');
    $planId = $plan ? (int) $plan['id'] : null;

    $db->exec('UPDATE user_subscriptions SET status="expired" WHERE user_id=? AND status="active"', [$uid]);

    if ($planId) {
        $db->exec(
            'INSERT INTO user_subscriptions(user_id, plan_id, status, starts_at, ends_at, payment_provider, provider_reference) VALUES(?,?,"active",NOW(),DATE_ADD(NOW(), INTERVAL 30 DAY),?,?)',
            [$uid, $planId, $provider, $reference]
        );
    }

    $db->exec(
        'UPDATE users SET paid_until=DATE_ADD(NOW(), INTERVAL 30 DAY), is_verified_paid=1, updated_at=NOW() WHERE id=?',
        [$uid]
    );

    notify($uid, 'system', 'Payment confirmed', 'Your Monthly Access is active for 30 days. You now have the blue verified badge.');
}

/** Generate icebreaker pickup lines for a chat partner. */
function icebreaker_lines(array $me, array $them, array $theirProfile = []): array
{
    global $db;
    $name = explode(' ', trim($them['name'] ?? 'there'))[0] ?: 'there';
    $city = $theirProfile['city'] ?? $theirProfile['location_label'] ?? '';
    $hobby = '';
    if (!empty($theirProfile['hobbies'])) {
        $parts = preg_split('/[,|\/]+/', $theirProfile['hobbies']);
        $hobby = trim($parts[0] ?? '');
    }
    if ($hobby === '' && !empty($theirProfile['interests'])) {
        $hobby = trim(explode(',', $theirProfile['interests'])[0] ?? '');
    }
    $goal = $theirProfile['relationship_goal'] ?? 'something real';

    $templates = [];
    try {
        $rows = $db->all('SELECT line, category FROM icebreaker_suggestions WHERE active=1 ORDER BY RAND() LIMIT 12');
        foreach ($rows as $r) {
            $templates[] = $r;
        }
    } catch (Throwable $e) {
        // ignore
    }
    if (!$templates) {
        $templates = [
            ['category' => 'general', 'line' => 'Hey {name}! Your profile made me smile — what\'s something fun you\'re into this week?'],
            ['category' => 'general', 'line' => 'Hi {name} 👋 Random question: coffee date or sunset walk?'],
            ['category' => 'general', 'line' => 'Hey! I noticed we might have a spark — what\'s your go-to weekend vibe?'],
            ['category' => 'hobbies', 'line' => 'Your hobbies caught my eye — tell me more about {hobby}?'],
            ['category' => 'city', 'line' => 'Hey from nearby {city}! Ever found a hidden gem spot around there?'],
            ['category' => 'goal', 'line' => 'I saw you\'re looking for {goal}. What does a great connection look like for you?'],
        ];
    }

    $out = [];
    foreach ($templates as $t) {
        $line = $t['line'] ?? '';
        $cat = $t['category'] ?? 'general';
        if ($cat === 'hobbies' && $hobby === '') {
            continue;
        }
        if ($cat === 'city' && $city === '') {
            continue;
        }
        $line = str_replace(
            ['{name}', '{hobby}', '{city}', '{goal}'],
            [$name, $hobby ?: 'that', $city ?: 'your area', $goal],
            $line
        );
        $out[] = $line;
        if (count($out) >= 4) {
            break;
        }
    }
    if (!$out) {
        $out[] = "Hey {$name}! Great to match — what's making you smile today?";
    }
    return $out;
}

/** Inbox / chat requires an active paid subscription (not free trial). */
function can_message(int $uid): bool
{
    return is_paid_member($uid);
}

function require_paid_messaging(): void
{
    require_login();
    $u = current_user();
    if (!can_message((int) $u['id'])) {
        flash('error', 'Messaging is for paid members only. Activate Monthly Access (KES ' . number_format(monthly_price_kes()) . ') to unlock your inbox and chat with matches.');
        redirect('premium');
    }
}

function paid_badge_html(int $uid): string
{
    if (!is_paid_member($uid)) {
        return '';
    }
    return '<span class="paid-badge" title="Verified paid member" aria-label="Verified">✓</span>';
}

/**
 * Null-safe helpers — use in views/controllers instead of ad-hoc isset/is_array checks.
 */

function arr($value): array
{
    return is_array($value) ? $value : [];
}

function arr_first(...$candidates): array
{
    foreach ($candidates as $c) {
        if (is_array($c)) {
            return $c;
        }
    }
    return [];
}

function row($value, array $default = []): array
{
    return is_array($value) ? $value : $default;
}

function str_val($value, string $default = ''): string
{
    if ($value === null || is_array($value) || is_object($value)) {
        return $default;
    }
    return (string) $value;
}

function int_val($value, int $default = 0): int
{
    if (is_int($value)) {
        return $value;
    }
    if (is_numeric($value)) {
        return (int) $value;
    }
    return $default;
}

function float_val($value, float $default = 0.0): float
{
    if (is_float($value) || is_int($value)) {
        return (float) $value;
    }
    if (is_numeric($value)) {
        return (float) $value;
    }
    return $default;
}

function data_get($data, string $key, $default = null)
{
    if (!is_array($data)) {
        return $default;
    }
    return array_key_exists($key, $data) && $data[$key] !== null ? $data[$key] : $default;
}

function json_arr($value): array
{
    if (is_array($value)) {
        return $value;
    }
    if (!is_string($value) || $value === '') {
        return [];
    }
    $decoded = json_decode($value, true);
    return is_array($decoded) ? $decoded : [];
}

function can_foreach($value): bool
{
    return is_array($value) && count($value) > 0;
}


/** Global country list for profiles (all continents). */
function world_countries(): array
{
    return [
        'Africa' => ['Kenya','Uganda','Tanzania','Rwanda','Ethiopia','Nigeria','Ghana','South Africa','Egypt','Morocco','Botswana','Zimbabwe','Zambia','Senegal','Ivory Coast'],
        'Europe' => ['United Kingdom','Ireland','Germany','France','Netherlands','Spain','Italy','Sweden','Norway','Poland','Portugal'],
        'Americas' => ['United States','Canada','Mexico','Brazil','Argentina','Colombia','Chile','Jamaica'],
        'Asia' => ['India','United Arab Emirates','Saudi Arabia','China','Japan','Singapore','Philippines','Indonesia','Pakistan','South Korea'],
        'Oceania' => ['Australia','New Zealand','Fiji'],
    ];
}

function country_continent(string $country): string
{
    foreach (world_countries() as $cont => $list) {
        if (in_array($country, $list, true)) {
            return $cont;
        }
    }
    return 'Global';
}

function audit_admin(string $action, ?string $type = null, $id = null, array $meta = []): void
{
    log_admin($action, $type, $id, $meta);
}


function visitor_session_key(): string
{
    if (empty($_SESSION['visitor_sid'])) {
        $_SESSION['visitor_sid'] = bin2hex(random_bytes(16));
    }
    return (string) $_SESSION['visitor_sid'];
}

function client_ip(): string
{
    $ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? '';
    if (str_contains($ip, ',')) {
        $ip = trim(explode(',', $ip)[0]);
    }
    return mb_substr($ip, 0, 45);
}

function looks_like_bot(?string $ua): int
{
    $ua = strtolower($ua ?? '');
    if ($ua === '') return 0;
    foreach (['bot', 'crawl', 'spider', 'slurp', 'facebookexternalhit', 'preview'] as $w) {
        if (str_contains($ua, $w)) return 1;
    }
    return 0;
}

/**
 * Record a page view / visit. Best-effort; never breaks the request.
 */
function track_visit(string $path, string $method = 'GET'): void
{
    global $db;
    try {
        if (!$db) return;
        // skip noisy endpoints
        $skip = ['health', 'premium/mpesa/callback', 'premium/stripe/webhook', 'chat/poll', 'chat/typing', 'analytics/geo', 'premium/mpesa/status'];
        foreach ($skip as $s) {
            if ($path === $s || str_starts_with($path, $s . '/')) return;
        }
        $ua = mb_substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 500);
        $ref = mb_substr($_SERVER['HTTP_REFERER'] ?? '', 0, 500);
        $uid = current_user()['id'] ?? null;
        $db->exec(
            'INSERT INTO visitor_events(session_key, user_id, path, method, ip, user_agent, referrer, is_bot, geo_source)
             VALUES(?,?,?,?,?,?,?,?,?)',
            [
                visitor_session_key(),
                $uid ? (int) $uid : null,
                mb_substr($path === '' ? 'home' : $path, 0, 255),
                mb_substr($method, 0, 10),
                client_ip(),
                $ua,
                $ref !== '' ? $ref : null,
                looks_like_bot($ua),
                'none',
            ]
        );
        $_SESSION['last_visit_event_id'] = (int) ($db->one('SELECT LAST_INSERT_ID() AS id')['id'] ?? 0);
    } catch (Throwable $e) {
        // table may not exist yet
    }
}

function update_visit_geo(int $eventId, array $geo): void
{
    global $db;
    if ($eventId < 1) return;
    try {
        $db->exec(
            'UPDATE visitor_events SET
                latitude=?, longitude=?, accuracy_m=?,
                city=COALESCE(?, city), region=COALESCE(?, region), country=COALESCE(?, country),
                continent=COALESCE(?, continent), geo_source=?
             WHERE id=?',
            [
                $geo['lat'] ?? null,
                $geo['lng'] ?? null,
                $geo['accuracy'] ?? null,
                $geo['city'] ?? null,
                $geo['region'] ?? null,
                $geo['country'] ?? null,
                $geo['continent'] ?? null,
                $geo['source'] ?? 'gps',
                $eventId,
            ]
        );
    } catch (Throwable $e) {}
}
