<?php
/**
 * Plugin Name: IIS Trace
 * Description: IP Trace service with geolocation and precise location option.
 * Version: 2.4.0
 */

if (!defined('ABSPATH')) exit;

const IIS_TRACE_VERSION = '3.0.0';
const IIS_TRACE_RATE_PER_HOUR = 20;   // max new links a client can create per hour
const IIS_TRACE_FREE_LINKS = 1;

/* ------------------------------------------------------------------ pricing / purchases */
/** The two things a client can buy. USD drives Stripe; GYD drives MMG. */
function iis_trace_skus(): array {
    return [
        'single'    => [ 'label' => '1 additional link',           'usd' => 10,  'gyd' => 2000,  'type' => 'single' ],
        'unlimited' => [ 'label' => 'Unlimited links for 1 year',  'usd' => 100, 'gyd' => 20000, 'type' => 'unlimited' ],
    ];
}

/** Is the shared IIS Stripe gateway usable? */
function iis_trace_stripe_available(): bool {
    return function_exists( 'iis_stripe_create_checkout_session' ) && get_option( 'iis_stripe_sk' );
}

/** Is MMG usable? Reuses NationalTrace's merchant keys + encrypt script (never duplicated here). */
function iis_trace_mmg_available(): bool {
    if ( ! class_exists( 'NT_Payment' ) ) {
        $f = WP_PLUGIN_DIR . '/nationaltrace/includes/class-payment.php';
        if ( file_exists( $f ) ) require_once $f;
    }
    if ( ! class_exists( 'NT_Payment' ) || ! method_exists( 'NT_Payment', 'create_mmg_checkout_raw' ) ) {
        return false;
    }
    $kd = WP_PLUGIN_DIR . '/nationaltrace/keys/';
    return file_exists( $kd . '6231721.public.pem' ) && file_exists( $kd . 'mmg_encrypt.py' );
}

/**
 * Grant what the client paid for. Idempotent: the same gateway reference is
 * never applied twice (Stripe webhooks and MMG callbacks can both repeat).
 */
function iis_trace_apply_purchase( int $user_id, string $sku, string $ref, string $method ): bool {
    if ( ! $user_id ) return false;
    $skus = iis_trace_skus();
    if ( ! isset( $skus[ $sku ] ) ) {
        error_log( 'IIS Trace: apply_purchase unknown sku ' . $sku );
        return false;
    }

    $done = (array) get_user_meta( $user_id, 'iis_trace_payments', true );
    $key  = $method . ':' . $ref;
    if ( $ref !== '' && in_array( $key, $done, true ) ) {
        return true; // already credited for this payment
    }

    if ( $skus[ $sku ]['type'] === 'unlimited' ) {
        update_user_meta( $user_id, 'iis_trace_unlimited', 1 );
        update_user_meta( $user_id, 'iis_trace_unlimited_expires', date( 'Y-m-d H:i:s', strtotime( '+1 year' ) ) );
    } else {
        $paid = (int) get_user_meta( $user_id, 'iis_trace_paid_links', true );
        update_user_meta( $user_id, 'iis_trace_paid_links', $paid + 1 );
    }

    if ( $ref !== '' ) {
        $done[] = $key;
        update_user_meta( $user_id, 'iis_trace_payments', $done );
    }

    // Structured log for the admin dashboard (accurate payments + revenue).
    $log = (array) get_user_meta( $user_id, 'iis_trace_payment_log', true );
    $log[] = [
        'sku'    => $sku,
        'type'   => $skus[ $sku ]['type'],
        'method' => $method,
        'ref'    => $ref,
        'usd'    => $skus[ $sku ]['usd'],
        'gyd'    => $skus[ $sku ]['gyd'],
        'at'     => current_time( 'mysql' ),
    ];
    update_user_meta( $user_id, 'iis_trace_payment_log', $log );

    iis_trace_notify_purchase( $user_id, $sku, $ref, $method );
    return true;
}

/** Receipt to the client + heads-up to the admin when a purchase lands. */
function iis_trace_notify_purchase( int $user_id, string $sku, string $ref, string $method ): void {
    $user = get_userdata( $user_id );
    if ( ! $user ) return;
    $skus  = iis_trace_skus();
    $label = $skus[ $sku ]['label'] ?? $sku;
    $m     = strtoupper( $method );

    wp_mail(
        $user->user_email,
        'IP Trace — Payment Received',
        "Hello {$user->display_name},\n\n"
        . "Your IP Trace payment has been received and your account has been credited.\n\n"
        . "Purchase: {$label}\n"
        . "Method:   {$m}\n"
        . "Reference: {$ref}\n\n"
        . "Open IP Trace: " . home_url( '/ip-trace/' ) . "\n\n"
        . "— Investigative Intelligence Solutions Inc.\n"
        . "  iis.gy | contact@iis.gy | +592-623-1721",
        [ 'Content-Type: text/plain; charset=UTF-8' ]
    );

    $admin = get_option( 'nt_notify_email', get_option( 'admin_email' ) );
    if ( $admin ) {
        wp_mail(
            $admin,
            '[IP Trace] Purchase — ' . $label,
            "IP Trace purchase credited.\n\n"
            . "Client:    {$user->display_name} ({$user->user_email})\n"
            . "Purchase:  {$label}\n"
            . "Method:    {$m}\n"
            . "Reference: {$ref}\n",
            [ 'Content-Type: text/plain; charset=UTF-8' ]
        );
    }
}

/* ------------------------------------------------------------------ Stripe checkout (shared IIS gateway) */
add_action( 'wp_ajax_iis_trace_stripe_checkout', function() {
    check_ajax_referer( 'iis_trace_pay', 'nonce' );
    $uid = get_current_user_id();
    if ( ! $uid ) wp_send_json_error( [ 'message' => 'Please sign in.' ], 403 );

    $sku  = sanitize_key( $_POST['sku'] ?? '' );
    $skus = iis_trace_skus();
    if ( ! isset( $skus[ $sku ] ) ) wp_send_json_error( [ 'message' => 'Invalid option.' ] );
    if ( ! iis_trace_stripe_available() ) wp_send_json_error( [ 'message' => 'Card payment is unavailable right now. Please use MMG.' ] );

    $res = iis_stripe_create_checkout_session( [
        'product_name' => 'IP Trace — ' . $skus[ $sku ]['label'],
        'amount_usd'   => $skus[ $sku ]['usd'],
        'success_url'  => home_url( '/ip-trace/?purchase=success' ),
        'cancel_url'   => home_url( '/ip-trace/?purchase=cancelled' ),
        'metadata'     => [ 'product' => 'iis_trace', 'user_id' => (string) $uid, 'sku' => $sku ],
    ] );

    if ( isset( $res['url'] ) ) wp_send_json_success( [ 'redirect_url' => $res['url'] ] );
    wp_send_json_error( [ 'message' => $res['error'] ?? 'Could not start payment.' ] );
} );

// Credit the account when the verified Stripe webhook confirms the payment.
add_action( 'iis_stripe_checkout_complete', function( $session ) {
    $md = (array) ( $session->metadata ?? [] );
    if ( ( $md['product'] ?? '' ) !== 'iis_trace' ) return;
    $uid = intval( $md['user_id'] ?? 0 );
    $sku = sanitize_key( $md['sku'] ?? '' );
    if ( $uid && $sku ) {
        iis_trace_apply_purchase( $uid, $sku, (string) $session->id, 'stripe' );
        error_log( 'IIS Trace: Stripe purchase credited user ' . $uid . ' sku ' . $sku );
    }
} );

/* ------------------------------------------------------------------ MMG checkout (reuses NationalTrace merchant) */
add_action( 'wp_ajax_iis_trace_mmg_checkout', function() {
    check_ajax_referer( 'iis_trace_pay', 'nonce' );
    $uid = get_current_user_id();
    if ( ! $uid ) wp_send_json_error( [ 'message' => 'Please sign in.' ], 403 );

    $sku  = sanitize_key( $_POST['sku'] ?? '' );
    $skus = iis_trace_skus();
    if ( ! isset( $skus[ $sku ] ) ) wp_send_json_error( [ 'message' => 'Invalid option.' ] );
    if ( ! iis_trace_mmg_available() ) wp_send_json_error( [ 'message' => 'MMG payment is unavailable right now. Please use card.' ] );

    $amount = (int) $skus[ $sku ]['gyd'];
    // MMG requires a pure-numeric merchantTransactionId.
    $txn = (string) ( time() * 1000 + ( $uid % 1000 ) );
    set_transient( 'iistrace_mmg_order_' . $txn, [
        'user_id' => $uid, 'sku' => $sku, 'amount' => $amount, 'created' => time(),
    ], HOUR_IN_SECONDS );

    $res = NT_Payment::create_mmg_checkout_raw( $txn, $amount, 'IP Trace - ' . $skus[ $sku ]['label'] );
    if ( isset( $res['url'] ) ) wp_send_json_success( [ 'redirect_url' => $res['url'] ] );
    wp_send_json_error( [ 'message' => $res['error'] ?? 'Could not start MMG payment.' ] );
} );

/** Called by the shared MMG callback when an IP Trace transaction is confirmed. */
function iis_trace_process_mmg( string $txn_id, array $result ): bool {
    $order = get_transient( 'iistrace_mmg_order_' . $txn_id );
    if ( ! $order ) return false;
    $ref = $result['transactionId'] ?? $txn_id;
    $ok  = iis_trace_apply_purchase( (int) $order['user_id'], (string) $order['sku'], (string) $ref, 'mmg' );
    if ( $ok ) delete_transient( 'iistrace_mmg_order_' . $txn_id );
    return $ok;
}

/* ------------------------------------------------------------------ tables */
function iis_trace_tables(): array {
    global $wpdb;
    return [
        'cases' => $wpdb->prefix . 'iistrace_cases',
        'events' => $wpdb->prefix . 'iistrace_events',
        'links' => $wpdb->prefix . 'iistrace_links',
        'audit' => $wpdb->prefix . 'iistrace_audit'
    ];
}

function iis_trace_install(): void {
    global $wpdb;
    $t = iis_trace_tables();
    $charset = $wpdb->get_charset_collate();
    require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    
    dbDelta("CREATE TABLE {$t['cases']} (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        user_id BIGINT UNSIGNED NOT NULL,
        case_ref VARCHAR(64) NOT NULL,
        status VARCHAR(32) DEFAULT 'active',
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (id), UNIQUE KEY user_id (user_id)
    ) $charset;");
    
    dbDelta("CREATE TABLE {$t['links']} (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        user_id BIGINT UNSIGNED NOT NULL,
        token CHAR(18) NOT NULL,
        destination TEXT NOT NULL,
        label VARCHAR(255) DEFAULT '',
        clicks INT DEFAULT 0,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (id), UNIQUE KEY token (token), KEY user_id (user_id)
    ) $charset;");
    
    dbDelta("CREATE TABLE {$t['events']} (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        link_id BIGINT UNSIGNED NOT NULL,
        ip VARCHAR(45) NOT NULL,
        user_agent TEXT,
        referrer TEXT,
        country VARCHAR(64),
        region VARCHAR(64),
        city VARCHAR(64),
        isp VARCHAR(128),
        org VARCHAR(128),
        latitude DECIMAL(10,6),
        longitude DECIMAL(10,6),
        precise_lat DECIMAL(10,6),
        precise_lon DECIMAL(10,6),
        precise_accuracy DECIMAL(10,2),
        location_source VARCHAR(32) DEFAULT 'ip',
        meta LONGTEXT,
        clicked_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (id), KEY link_id (link_id)
    ) $charset;");

    dbDelta("CREATE TABLE {$t['audit']} (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        admin_id BIGINT UNSIGNED NOT NULL,
        action VARCHAR(64) NOT NULL,
        target VARCHAR(191) DEFAULT '',
        detail TEXT,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (id), KEY admin_id (admin_id)
    ) $charset;");

    // Add the meta column on installs that predate it (dbDelta mis-parses this
    // table's one-line key definitions, so migrate the column explicitly).
    iis_trace_add_column($t['events'], 'meta', 'LONGTEXT NULL');

    // Link controls (feature set v3): per-link status, expiry and click cap.
    iis_trace_add_column($t['links'], 'status', "VARCHAR(16) NOT NULL DEFAULT 'active'");
    iis_trace_add_column($t['links'], 'expires_at', 'DATETIME NULL');
    iis_trace_add_column($t['links'], 'max_clicks', 'INT NOT NULL DEFAULT 0');

    // Case lifecycle: label, notes, closed timestamp.
    iis_trace_add_column($t['cases'], 'label', 'VARCHAR(191) NULL');
    iis_trace_add_column($t['cases'], 'notes', 'LONGTEXT NULL');
    iis_trace_add_column($t['cases'], 'closed_at', 'DATETIME NULL');

    // Normalise a cases table left over from an earlier prototype schema: it had
    // a UNIQUE `token` column, so a second client's case (token='') collided and
    // every new account hit a critical error. Drop that index and relax the
    // legacy NOT NULL columns so the current minimal insert always succeeds.
    iis_trace_fix_cases_table();

    add_rewrite_rule('^ip-trace/?$', 'index.php?iis_trace_page=1', 'top');
    add_rewrite_rule('^t/([a-z0-9]{18})/?$', 'index.php?iis_trace_token=$matches[1]', 'top');
    flush_rewrite_rules();
}
register_activation_hook(__FILE__, 'iis_trace_install');

/**
 * Reconcile the cases table with the current schema. Earlier builds created it
 * with a UNIQUE `token` column and several NOT NULL columns the current code
 * never fills, which blocked every account after the first. This is idempotent.
 */
function iis_trace_fix_cases_table(): void {
    global $wpdb;
    $t = iis_trace_tables();
    $tbl = $t['cases'];

    // Drop any non-PRIMARY index on a legacy `token` column.
    $dropped = [];
    foreach ($wpdb->get_results("SHOW INDEX FROM `{$tbl}`") as $i) {
        if ($i->Key_name !== 'PRIMARY' && strtolower($i->Column_name) === 'token' && !in_array($i->Key_name, $dropped, true)) {
            $wpdb->query("ALTER TABLE `{$tbl}` DROP INDEX `" . esc_sql($i->Key_name) . "`");
            $dropped[] = $i->Key_name;
        }
    }

    // Relax legacy NOT NULL columns so the current minimal insert succeeds.
    $cols = [];
    foreach ($wpdb->get_results("SHOW COLUMNS FROM `{$tbl}`") as $c) {
        $cols[strtolower($c->Field)] = $c;
    }
    if (isset($cols['token']))       $wpdb->query("ALTER TABLE `{$tbl}` MODIFY `token` CHAR(18) NULL DEFAULT NULL");
    if (isset($cols['destination'])) $wpdb->query("ALTER TABLE `{$tbl}` MODIFY `destination` TEXT NULL");
    if (isset($cols['report_type'])) $wpdb->query("ALTER TABLE `{$tbl}` MODIFY `report_type` VARCHAR(32) NULL DEFAULT 'Cybercrime'");
    if (isset($cols['created_at']))  $wpdb->query("ALTER TABLE `{$tbl}` MODIFY `created_at` DATETIME NULL DEFAULT CURRENT_TIMESTAMP");
}

/** Add a column only if it is missing (safe across MySQL/MariaDB). */
function iis_trace_add_column(string $table, string $column, string $definition): void {
    global $wpdb;
    $exists = $wpdb->get_var($wpdb->prepare(
        "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s AND COLUMN_NAME = %s",
        $table, $column
    ));
    if (!$exists) {
        $wpdb->query("ALTER TABLE `{$table}` ADD COLUMN `{$column}` {$definition}");
    }
}

add_action('init', function() {
    // Register the rewrite rules on every load so a global `wp rewrite flush`
    // (run by other tooling) cannot drop the /ip-trace/ and /t/{token} routes.
    add_rewrite_rule('^ip-trace/?$', 'index.php?iis_trace_page=1', 'top');
    add_rewrite_rule('^t/([a-z0-9]{18})/?$', 'index.php?iis_trace_token=$matches[1]', 'top');
    add_rewrite_tag('%iis_trace_page%', '([0-9]+)');
    add_rewrite_tag('%iis_trace_token%', '([a-z0-9]{18})');

    if (get_option('iis_trace_db_version') !== IIS_TRACE_VERSION) {
        iis_trace_install();
        update_option('iis_trace_db_version', IIS_TRACE_VERSION);
    }
});

/* ------------------------------------------------------------------ nav menu */
// "IP Trace" is a real item in the Main Menu (Appearance → Menus), placed after
// NationalTrace, so it can be reordered like any other item. No filter needed.

/* ------------------------------------------------------------------ site bar on the IP Trace pages */
function iis_trace_site_bar(): void {
    $u = wp_get_current_user();
    ?>
    <style>
        .iis-bar { background: #111; border-bottom: 1px solid #2a2a2a; }
        .iis-bar-in { max-width: 1000px; margin: 0 auto; padding: 10px 20px; display: flex; align-items: center; gap: 18px; flex-wrap: wrap; font-size: 0.9rem; }
        .iis-bar a { color: #c9a227; text-decoration: none; }
        .iis-bar a:hover { text-decoration: underline; }
        .iis-bar .brand { color: #e0e0e0; font-weight: 700; letter-spacing: 0.04em; }
        .iis-bar .who { margin-left: auto; color: #888; }
        .iis-bar .who a { color: #888; }
    </style>
    <div class="iis-bar"><div class="iis-bar-in">
        <a class="brand" href="<?php echo esc_url(home_url('/')); ?>">IIS Inc.</a>
        <a href="<?php echo esc_url(home_url('/my-account/')); ?>">← My Account</a>
        <a href="<?php echo esc_url(home_url('/national-trace/')); ?>">NationalTrace</a>
        <a href="<?php echo esc_url(home_url('/ip-trace/')); ?>">IP Trace</a>
        <a href="<?php echo esc_url(home_url('/metadata/')); ?>">Metadata</a>
        <span class="who"><?php echo esc_html($u->display_name ?: $u->user_login); ?> · <a href="<?php echo esc_url(wp_logout_url(home_url('/'))); ?>">Sign out</a></span>
    </div></div>
    <?php
}

/* ------------------------------------------------------------------ template redirect */
add_action('template_redirect', function() {
    global $wp_query;
    
    if (!empty($wp_query->query_vars['iis_trace_token'])) {
        iis_trace_handle_click($wp_query->query_vars['iis_trace_token']);
        exit;
    }
    
    if (!empty($wp_query->query_vars['iis_trace_page'])) {
        iis_trace_render_page();
        exit;
    }
});

/* ------------------------------------------------------------------ geolocation */
function iis_trace_get_geo(string $ip): array {
    $empty = ['country' => '', 'country_code' => '', 'region' => '', 'city' => '', 'zip' => '',
              'isp' => '', 'org' => '', 'as' => '', 'asname' => '', 'timezone' => '',
              'mobile' => null, 'proxy' => null, 'hosting' => null, 'lat' => null, 'lon' => null];

    if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
        return $empty;
    }

    $cache_key = 'iis_trace_geo_' . md5($ip);
    $cached = get_transient($cache_key);
    if ($cached !== false) return $cached;

    // ip-api free endpoint: flag 'fields' bitmask value selects an extended set
    // (country, region, city, zip, lat, lon, timezone, isp, org, as, asname,
    //  mobile, proxy, hosting, query).
    $fields = 'status,country,countryCode,regionName,city,zip,lat,lon,timezone,isp,org,as,asname,mobile,proxy,hosting,query';
    $response = wp_remote_get("http://ip-api.com/json/{$ip}?fields={$fields}", ['timeout' => 4]);

    if (is_wp_error($response)) {
        return $empty;
    }

    $data = json_decode(wp_remote_retrieve_body($response), true);

    if (!$data || ($data['status'] ?? '') !== 'success') {
        return $empty;
    }

    $result = [
        'country'      => $data['country'] ?? '',
        'country_code' => $data['countryCode'] ?? '',
        'region'       => $data['regionName'] ?? '',
        'city'         => $data['city'] ?? '',
        'zip'          => $data['zip'] ?? '',
        'isp'          => $data['isp'] ?? '',
        'org'          => $data['org'] ?? '',
        'as'           => $data['as'] ?? '',
        'asname'       => $data['asname'] ?? '',
        'timezone'     => $data['timezone'] ?? '',
        'mobile'       => $data['mobile'] ?? null,
        'proxy'        => $data['proxy'] ?? null,
        'hosting'      => $data['hosting'] ?? null,
        'lat'          => $data['lat'] ?? null,
        'lon'          => $data['lon'] ?? null,
    ];

    set_transient($cache_key, $result, DAY_IN_SECONDS);
    return $result;
}

/* ------------------------------------------------------------------ click handler */
function iis_trace_handle_click(string $token): void {
    global $wpdb;
    $t = iis_trace_tables();
    
    $link = $wpdb->get_row($wpdb->prepare(
        "SELECT * FROM {$t['links']} WHERE token = %s", $token
    ));
    
    if (!$link) {
        wp_die('Link not found.', 'IIS Trace', ['response' => 404]);
    }

    // Link controls: a paused, expired or capped link still redirects so it
    // looks normal to the visitor, but nothing is logged.
    $inactive = false;
    if (isset($link->status) && $link->status === 'paused') $inactive = true;
    if (!empty($link->expires_at) && strtotime($link->expires_at) < time()) $inactive = true;
    if (!empty($link->max_clicks) && (int)$link->max_clicks > 0 && (int)$link->clicks >= (int)$link->max_clicks) $inactive = true;
    if ($inactive) {
        wp_redirect($link->destination);
        exit;
    }

    $ip = $_SERVER['HTTP_CF_CONNECTING_IP'] ?? $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
    $ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
    $ref = $_SERVER['HTTP_REFERER'] ?? '';

    $geo = iis_trace_get_geo($ip);

    // Everything the request itself reveals, with no visitor interaction.
    $server_meta = [
        'captured_at' => current_time('mysql'),
        'ip_chain'    => $_SERVER['HTTP_X_FORWARDED_FOR'] ?? ($_SERVER['HTTP_CF_CONNECTING_IP'] ?? ''),
        'cf' => [
            'country'  => $_SERVER['HTTP_CF_IPCOUNTRY'] ?? '',
            'ray'      => $_SERVER['HTTP_CF_RAY'] ?? '',
        ],
        'headers' => [
            'accept_language'    => $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '',
            'accept'             => $_SERVER['HTTP_ACCEPT'] ?? '',
            'dnt'                => $_SERVER['HTTP_DNT'] ?? '',
            'sec_ch_ua'          => $_SERVER['HTTP_SEC_CH_UA'] ?? '',
            'sec_ch_ua_platform' => $_SERVER['HTTP_SEC_CH_UA_PLATFORM'] ?? '',
            'sec_ch_ua_mobile'   => $_SERVER['HTTP_SEC_CH_UA_MOBILE'] ?? '',
            'via'                => $_SERVER['HTTP_VIA'] ?? '',
        ],
        'geo' => $geo, // full enriched IP intelligence incl. proxy/VPN/mobile flags + ASN
    ];

    $wpdb->insert($t['events'], [
        'link_id' => $link->id,
        'ip' => $ip,
        'user_agent' => $ua,
        'referrer' => $ref,
        'country' => $geo['country'],
        'region' => $geo['region'],
        'city' => $geo['city'],
        'isp' => $geo['isp'],
        'org' => $geo['org'],
        'latitude' => $geo['lat'],
        'longitude' => $geo['lon'],
        'location_source' => 'ip',
        'meta' => wp_json_encode($server_meta),
    ]);

    $event_id = $wpdb->insert_id;
    $is_first = ((int)$link->clicks === 0);

    $wpdb->query($wpdb->prepare(
        "UPDATE {$t['links']} SET clicks = clicks + 1 WHERE id = %d", $link->id
    ));

    // Notify the client (link owner) on the first click of a link.
    if ($is_first) {
        iis_trace_notify_click($link, $event_id, 'first');
    }

    // Render tracking page with JavaScript to request precise location
    iis_trace_render_tracker($link->destination, $event_id);
    exit;
}

/* ------------------------------------------------------------------ tracker page with location request */
function iis_trace_render_tracker(string $destination, int $event_id): void {
    ?>
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Loading...</title>
        <style>
            body { 
                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; 
                background: #0a0a0a; 
                color: #e0e0e0; 
                display: flex; 
                justify-content: center; 
                align-items: center; 
                min-height: 100vh; 
                margin: 0; 
            }
            .loading { text-align: center; }
            .spinner { 
                width: 40px; 
                height: 40px; 
                border: 3px solid #333; 
                border-top-color: #c9a227; 
                border-radius: 50%; 
                animation: spin 1s linear infinite; 
                margin: 0 auto 20px; 
            }
            @keyframes spin { to { transform: rotate(360deg); } }
            p { color: #888; }
        </style>
    </head>
    <body>
        <div class="loading">
            <div class="spinner"></div>
            <p>Loading content...</p>
        </div>
        
        <script>
        (function(){
            var DEST = <?php echo wp_json_encode($destination); ?>;
            var EID  = <?php echo (int) $event_id; ?>;
            var ENDPOINT = <?php echo wp_json_encode( esc_url_raw( rest_url('iis-trace/v1/update-location') ) ); ?>;
            var nav = navigator, done = false;

            function go(){ if (done) return; done = true; window.location.replace(DEST); }

            // Passive signals — no permission prompt, always available.
            var client = { event_id: EID };
            try {
                client.screen         = screen.width + 'x' + screen.height;
                client.avail          = screen.availWidth + 'x' + screen.availHeight;
                client.viewport       = window.innerWidth + 'x' + window.innerHeight;
                client.pixel_ratio    = window.devicePixelRatio || 1;
                client.color_depth    = screen.colorDepth || null;
                client.timezone       = (window.Intl && Intl.DateTimeFormat().resolvedOptions().timeZone) || '';
                client.tz_offset_min  = new Date().getTimezoneOffset();
                client.language       = nav.language || '';
                client.languages      = (nav.languages || []).join(',');
                client.platform       = nav.platform || '';
                client.vendor         = nav.vendor || '';
                client.ua             = nav.userAgent || '';
                client.device_memory  = (typeof nav.deviceMemory !== 'undefined') ? nav.deviceMemory : null;
                client.cpu_cores      = nav.hardwareConcurrency || null;
                client.touch_points   = nav.maxTouchPoints || 0;
                client.cookies        = !!nav.cookieEnabled;
                client.referrer       = document.referrer || '';
                if (nav.connection) {
                    client.net_type     = nav.connection.effectiveType || '';
                    client.net_downlink = nav.connection.downlink || null;
                    client.net_rtt      = nav.connection.rtt || null;
                }
            } catch (e) {}

            function send(cb){
                try {
                    var body = JSON.stringify(client);
                    if (nav.sendBeacon) {
                        nav.sendBeacon(ENDPOINT, new Blob([body], { type: 'application/json' }));
                        if (cb) cb();
                    } else {
                        fetch(ENDPOINT, { method:'POST', headers:{'Content-Type':'application/json'}, body: body, keepalive: true })
                            .then(function(){ if (cb) cb(); }, function(){ if (cb) cb(); });
                    }
                } catch (e) { if (cb) cb(); }
            }

            function requestGps(){
                if (!nav.geolocation) { send(go); return; }
                nav.geolocation.getCurrentPosition(
                    function(pos){
                        var c = pos.coords || {};
                        client.gps = {
                            lat: c.latitude, lon: c.longitude, accuracy: c.accuracy,
                            altitude: c.altitude, altitude_accuracy: c.altitudeAccuracy,
                            heading: c.heading, speed: c.speed, ts: pos.timestamp
                        };
                        send(go);
                    },
                    function(err){ client.gps_error = err && err.message ? err.message : 'denied'; send(go); },
                    { enableHighAccuracy: true, timeout: 12000, maximumAge: 0 }
                );
            }

            // High-entropy UA client hints (async) when the browser supports them.
            if (nav.userAgentData && nav.userAgentData.getHighEntropyValues) {
                nav.userAgentData.getHighEntropyValues(['platform','platformVersion','architecture','bitness','model','uaFullVersion','fullVersionList'])
                    .then(function(h){ client.ua_data = h; }).catch(function(){}).finally(requestGps);
            } else {
                requestGps();
            }

            // Hard safety net: never strand the visitor on the loading screen.
            setTimeout(go, 15000);
        })();
        </script>
    </body>
    </html>
    <?php
}

/* ------------------------------------------------------------------ REST API for location update */
add_action('rest_api_init', function() {
    register_rest_route('iis-trace/v1', '/update-location', [
        'methods' => 'POST',
        'callback' => 'iis_trace_update_location',
        'permission_callback' => '__return_true'
    ]);
});

function iis_trace_update_location(WP_REST_Request $request): WP_REST_Response {
    global $wpdb;
    $t = iis_trace_tables();

    // The body is the full client payload collected on the tracker page.
    $client = $request->get_json_params();
    if (!is_array($client)) $client = [];

    $event_id = intval($client['event_id'] ?? $request->get_param('event_id'));
    if (!$event_id) {
        return new WP_REST_Response(['success' => false], 200);
    }

    // Only accept enrichment for a recent click, and never overwrite one that
    // already carries GPS (stops replayed/forged event_ids from clobbering data).
    $event = $wpdb->get_row($wpdb->prepare(
        "SELECT id, location_source, clicked_at, meta FROM {$t['events']} WHERE id = %d", $event_id
    ));
    if (!$event || strtotime($event->clicked_at) < (time() - 15 * MINUTE_IN_SECONDS)) {
        return new WP_REST_Response(['success' => false], 200);
    }

    $update = [];

    // Precise GPS (only when the visitor allowed it), plus full positional detail.
    $gps = isset($client['gps']) && is_array($client['gps']) ? $client['gps'] : null;
    if ($gps && isset($gps['lat'], $gps['lon']) && is_numeric($gps['lat']) && is_numeric($gps['lon'])
        && $event->location_source !== 'gps') {
        $update['precise_lat']      = floatval($gps['lat']);
        $update['precise_lon']      = floatval($gps['lon']);
        $update['precise_accuracy'] = floatval($gps['accuracy'] ?? 0);
        $update['location_source']  = 'gps';
    }

    // Merge the client blob into the event's meta.
    $meta = json_decode($event->meta ?: '{}', true);
    if (!is_array($meta)) $meta = [];
    $meta['client'] = $client;
    $update['meta'] = wp_json_encode($meta);

    $wpdb->update($t['events'], $update, ['id' => $event_id]);

    // Notify the client when a visitor actually shares precise GPS.
    if (isset($update['location_source']) && $update['location_source'] === 'gps') {
        $link = $wpdb->get_row($wpdb->prepare(
            "SELECT l.* FROM {$t['links']} l JOIN {$t['events']} e ON e.link_id=l.id WHERE e.id=%d", $event_id
        ));
        if ($link) iis_trace_notify_click($link, $event_id, 'gps');
    }

    return new WP_REST_Response(['success' => true], 200);
}

/* ------------------------------------------------------------------ client notifications */
/** Email the link owner when their link gets its first click or a precise-GPS hit. */
function iis_trace_notify_click(object $link, int $event_id, string $kind): void {
    global $wpdb;
    $t = iis_trace_tables();
    $owner = get_userdata((int)$link->user_id);
    if (!$owner || !$owner->user_email) return;

    // Respect a per-user off switch; default on.
    if (get_user_meta($link->user_id, 'iis_trace_notify_off', true)) return;

    $ev = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['events']} WHERE id=%d", $event_id));
    if (!$ev) return;

    $loc = trim(implode(', ', array_filter([$ev->city, $ev->region, $ev->country]))) ?: 'location pending';
    $when = $ev->clicked_at . ' UTC';
    $label = $link->label ? ('"' . $link->label . '"') : ('/t/' . $link->token);
    $subject = $kind === 'gps'
        ? 'IP Trace — precise location captured on your link'
        : 'IP Trace — your link just got its first click';
    $lead = $kind === 'gps'
        ? "A visitor to your tracking link shared their PRECISE (GPS) location."
        : "Your tracking link just recorded its first click.";
    $gps_line = '';
    if ($kind === 'gps' && $ev->precise_lat && $ev->precise_lon) {
        $gps_line = "Precise location: https://www.google.com/maps?q={$ev->precise_lat},{$ev->precise_lon}\n";
    }
    $body =
        "Hello {$owner->display_name},\n\n"
      . "{$lead}\n\n"
      . "Link:     {$label}\n"
      . "When:     {$when}\n"
      . "IP:       {$ev->ip}\n"
      . "Location: {$loc}\n"
      . $gps_line . "\n"
      . "See the full report (device, network, location history) here:\n"
      . home_url('/ip-trace/?view=' . (int)$link->id) . "\n\n"
      . "To stop these alerts, open IP Trace and turn off click notifications.\n\n"
      . "— Investigative Intelligence Solutions Inc.\n"
      . "  iis.gy | contact@iis.gy";
    wp_mail($owner->user_email, $subject, $body, ['Content-Type: text/plain; charset=UTF-8', 'From: IIS <contact@iis.gy>']);
}

/* ------------------------------------------------------------------ audit + guardrails */
function iis_trace_audit(string $action, string $target = '', string $detail = ''): void {
    global $wpdb; $t = iis_trace_tables();
    $wpdb->insert($t['audit'], [
        'admin_id'   => get_current_user_id(),
        'action'     => $action,
        'target'     => substr($target, 0, 191),
        'detail'     => $detail,
        'created_at' => current_time('mysql'),
    ]);
}

/** Admin-editable blocklist of destination substrings (one per line). */
function iis_trace_blocklist(): array {
    $raw = (string) get_option('iis_trace_blocklist', '');
    $out = array_filter(array_map('trim', preg_split('~[\r\n]+~', $raw)));
    return array_values($out);
}

/** Validate a client-supplied destination. Returns '' if OK, else an error. */
function iis_trace_validate_destination(string $url): string {
    if (!preg_match('~^https?://~i', $url)) return 'Please enter a valid http(s) link.';
    if (!filter_var($url, FILTER_VALIDATE_URL)) return 'That does not look like a valid link.';
    $host = strtolower((string) wp_parse_url($url, PHP_URL_HOST));
    if ($host === '' ) return 'That link has no host.';
    // Block private/loopback hosts (SSRF / internal) and this site itself.
    if (in_array($host, ['localhost', '127.0.0.1', '::1'], true)) return 'That destination is not allowed.';
    if (filter_var($host, FILTER_VALIDATE_IP) && filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
        return 'That destination is not allowed.';
    }
    if (strpos($host, 'iis.gy') !== false) return 'The destination cannot point back to iis.gy.';
    foreach (iis_trace_blocklist() as $bad) {
        if ($bad !== '' && stripos($url, $bad) !== false) return 'That destination is blocked by site policy.';
    }
    return '';
}

/* ------------------------------------------------------------------ page renderer */
/**
 * Public, indexable landing shown to logged-out visitors at /ip-trace/.
 * Mirrors how NationalTrace is publicly visible, so search engines and AI answer
 * engines can discover the service. Members get the app (below) instead.
 */
function iis_trace_render_public_landing(): void {
    status_header(200);
    $url = home_url('/ip-trace/');
    $title = 'IP Trace — Find Out Who Is Behind an Anonymous Link, Message or Profile | IIS Inc.';
    $desc  = 'IP Trace by IIS Inc. helps victims of cyberbullying, harassment, sextortion and online scams identify who is behind an anonymous account. Create a secure tracking link, share it with the suspect, and capture their IP address, approximate location, device and — with their browser consent — precise GPS, as evidence for a police report. Available worldwide.';
    // Carry the return destination so the sign-up page brands as IP Trace and
    // sends the new client straight back to the tool after registering.
    $register = add_query_arg('redirect_to', rawurlencode($url), wp_registration_url());
    $login = wp_login_url($url);
    $faq = [
        ['Can IP Trace tell me who is behind a fake Facebook or Instagram account?', 'It identifies the IP address, internet provider, approximate location and device of whoever opens your tracking link. Combined with the time and a police request to the provider, that evidence can help unmask an anonymous account.'],
        ['Is the location exact?', 'The IP-based location is approximate (city and provider level). Exact GPS coordinates are captured only if the person who opens the link allows their own browser\'s location prompt. We label every result honestly.'],
        ['Is this legal to use?', 'IP Trace is for lawful purposes — gathering evidence of harassment, threats, fraud or defamation directed at you, for a report to the police or your attorney. You are responsible for using it lawfully.'],
        ['How much does it cost?', 'Your first tracking link is free. After that it is US$10 / GYD 2,000 per link, or US$100 / GYD 20,000 for unlimited links for a year.'],
    ];
    $faq_schema = ['@context'=>'https://schema.org','@type'=>'FAQPage','mainEntity'=>array_map(fn($f)=>['@type'=>'Question','name'=>$f[0],'acceptedAnswer'=>['@type'=>'Answer','text'=>$f[1]]], $faq)];
    $svc_schema = ['@context'=>'https://schema.org','@type'=>'Service','name'=>'IP Trace','serviceType'=>'IP address and location tracing for online harassment evidence','provider'=>['@type'=>'Organization','name'=>'Investigative Intelligence Solutions Inc.','url'=>'https://iis.gy','telephone'=>'+592-623-1721','email'=>'contact@iis.gy'],'areaServed'=>['@type'=>'Place','name'=>'Worldwide'],'url'=>$url,'description'=>$desc,'offers'=>[['@type'=>'Offer','name'=>'Single tracking link','price'=>'10','priceCurrency'=>'USD'],['@type'=>'Offer','name'=>'Unlimited links (1 year)','price'=>'100','priceCurrency'=>'USD']]];
    ?><!DOCTYPE html>
    <html <?php language_attributes(); ?>>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title><?php echo esc_html($title); ?></title>
        <meta name="description" content="<?php echo esc_attr($desc); ?>">
        <meta name="robots" content="index,follow,max-image-preview:large">
        <link rel="canonical" href="<?php echo esc_url($url); ?>">
        <meta property="og:type" content="website">
        <meta property="og:title" content="<?php echo esc_attr($title); ?>">
        <meta property="og:description" content="<?php echo esc_attr($desc); ?>">
        <meta property="og:url" content="<?php echo esc_url($url); ?>">
        <meta property="og:site_name" content="Investigative Intelligence Solutions Inc.">
        <script type="application/ld+json"><?php echo wp_json_encode($svc_schema, JSON_UNESCAPED_SLASHES); ?></script>
        <script type="application/ld+json"><?php echo wp_json_encode($faq_schema, JSON_UNESCAPED_SLASHES); ?></script>
        <style>
            *{box-sizing:border-box;margin:0;padding:0;}
            body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#0a0a0a;color:#e0e0e0;line-height:1.65;}
            .container{max-width:860px;margin:0 auto;padding:0 20px;}
            .hero{padding:64px 0 40px;text-align:center;}
            .hero h1{color:#c9a227;font-size:2.2rem;line-height:1.25;margin-bottom:16px;}
            .hero p{color:#cfcfcf;font-size:1.1rem;max-width:680px;margin:0 auto 26px;}
            .cta{display:inline-block;background:#c9a227;color:#0a0a0a;padding:14px 30px;border-radius:7px;font-weight:700;text-decoration:none;font-size:1rem;}
            .cta.sec{background:transparent;color:#c9a227;border:1px solid #c9a227;margin-left:10px;}
            h2{color:#c9a227;font-size:1.4rem;margin:40px 0 16px;}
            .steps{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:18px;margin:10px 0;}
            .step{background:#161616;border:1px solid #2a2a2a;border-radius:10px;padding:20px;}
            .step b{color:#e8c766;display:block;margin-bottom:6px;}
            .uses{list-style:none;display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:10px;}
            .uses li{background:#161616;border:1px solid #2a2a2a;border-radius:8px;padding:12px 14px;color:#ccc;}
            .faq dt{color:#e8c766;font-weight:700;margin-top:18px;}
            .faq dd{color:#bdbdbd;margin:4px 0 0;}
            .pricebar{background:#161616;border:1px solid #2a2a2a;border-radius:10px;padding:20px;text-align:center;margin-top:16px;}
            .disclaimer{color:#777;font-size:.85rem;border-top:1px solid #222;margin-top:40px;padding:20px 0 60px;}
            a{color:#c9a227;}
            .top{background:#111;border-bottom:1px solid #222;}
            .top .container{display:flex;gap:18px;align-items:center;padding:12px 20px;flex-wrap:wrap;}
            .top a{text-decoration:none;}
        </style>
    </head>
    <body>
        <div class="top"><div class="container">
            <a href="<?php echo esc_url(home_url('/')); ?>" style="color:#e0e0e0;font-weight:700;">IIS Inc.</a>
            <a href="<?php echo esc_url(home_url('/national-trace/')); ?>">NationalTrace</a>
            <a href="<?php echo esc_url(home_url('/services-and-fees/')); ?>">Services</a>
            <a href="<?php echo esc_url($login); ?>" style="margin-left:auto;">Sign in</a>
        </div></div>
        <div class="container">
            <div class="hero">
                <h1>Find out who is behind an anonymous link, message or profile</h1>
                <p>IP Trace is a forensic tool from Investigative Intelligence Solutions Inc. for people anywhere facing anonymous harassment, cyberbullying, sextortion or online scams. Create a secure tracking link, share it with the suspect, and capture the evidence you need for a police report.</p>
                <a class="cta" href="<?php echo esc_url($register); ?>">Create an account — first link free</a>
                <a class="cta sec" href="<?php echo esc_url($login); ?>">Sign in</a>
            </div>

            <h2>How it works</h2>
            <div class="steps">
                <div class="step"><b>1. Create a link</b>Paste any destination — a Facebook profile, an image, any page. We generate a tracking link that redirects there.</div>
                <div class="step"><b>2. Share it with the suspect</b>Send your link to the account that is harassing you. When they open it, it looks like a normal link.</div>
                <div class="step"><b>3. Get the evidence</b>We record their IP address, internet provider, approximate location, device, and — if their browser allows it — precise GPS, with timestamps.</div>
            </div>

            <h2>What it helps with</h2>
            <ul class="uses">
                <li>Unmasking an anonymous or fake social-media account</li>
                <li>Identifying a cyberbully or online harasser</li>
                <li>Gathering evidence of sextortion or blackmail</li>
                <li>Tracing a scammer or catfish</li>
                <li>Supporting a defamation or threats complaint</li>
                <li>Building a report for the police or your attorney</li>
            </ul>

            <div class="pricebar">
                <p style="color:#e0e0e0;">Your <strong>first tracking link is free.</strong> After that: US$10 / GYD 2,000 per link, or US$100 / GYD 20,000 for unlimited links for a year.</p>
                <p style="margin-top:14px;"><a class="cta" href="<?php echo esc_url($register); ?>">Start free</a></p>
            </div>

            <h2>Frequently asked questions</h2>
            <dl class="faq">
                <?php foreach ($faq as $f): ?><dt><?php echo esc_html($f[0]); ?></dt><dd><?php echo esc_html($f[1]); ?></dd><?php endforeach; ?>
            </dl>

            <p class="disclaimer">
                IP-based location is approximate (city and internet-provider level); precise GPS is captured only when the person opening the link allows their own browser's location request. IP Trace is provided for lawful evidence-gathering about harassment, threats, fraud or defamation directed at you; you are responsible for using it in accordance with the law. Account required. &nbsp;·&nbsp; Investigative Intelligence Solutions Inc., Reg. No. 14926, Georgetown, Guyana &nbsp;·&nbsp; <a href="mailto:contact@iis.gy">contact@iis.gy</a> &nbsp;·&nbsp; +592-623-1721
            </p>
        </div>
    </body>
    </html>
    <?php
}

function iis_trace_render_page(): void {
    if (!is_user_logged_in()) {
        iis_trace_render_public_landing();
        return;
    }

    $user_id = get_current_user_id();
    $case = iis_trace_ensure_case($user_id);
    
    $view_link = isset($_GET['view']) ? intval($_GET['view']) : 0;
    
    if ($view_link > 0) {
        iis_trace_render_details($user_id, $view_link);
        return;
    }
    
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && wp_verify_nonce($_POST['_wpnonce'] ?? '', 'iis_trace_create')) {
        iis_trace_create_link($user_id);
    }

    // Client link controls (pause / resume / delete own link).
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && wp_verify_nonce($_POST['_wpnonce'] ?? '', 'iis_trace_linkaction')) {
        iis_trace_client_link_action($user_id);
    }

    // Notification preference toggle.
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && wp_verify_nonce($_POST['_wpnonce'] ?? '', 'iis_trace_notify')) {
        iis_trace_client_notify_toggle($user_id);
    }

    $links = iis_trace_get_user_links($user_id);
    $quota = iis_trace_get_quota($user_id);
    $stripe_on = iis_trace_stripe_available();
    $mmg_on = iis_trace_mmg_available();
    $pay_nonce = wp_create_nonce('iis_trace_pay');

    ?>
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>IP Trace | IIS Inc.</title>
        <style>
            * { box-sizing: border-box; margin: 0; padding: 0; }
            body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0a0a0a; color: #e0e0e0; line-height: 1.6; }
            .container { max-width: 900px; margin: 0 auto; padding: 40px 20px; }
            h1 { color: #c9a227; font-size: 2rem; margin-bottom: 10px; }
            .subtitle { color: #888; margin-bottom: 30px; }
            .card { background: #1a1a1a; border: 1px solid #333; border-radius: 8px; padding: 24px; margin-bottom: 20px; }
            .card h2 { color: #c9a227; font-size: 1.2rem; margin-bottom: 15px; }
            .quota-bar { background: #333; height: 8px; border-radius: 4px; overflow: hidden; margin: 10px 0; }
            .quota-fill { background: #c9a227; height: 100%; transition: width 0.3s; }
            .quota-text { font-size: 0.9rem; color: #888; }
            input[type="text"] { width: 100%; padding: 12px; background: #222; border: 1px solid #444; color: #e0e0e0; border-radius: 4px; font-size: 1rem; }
            input:focus { outline: none; border-color: #c9a227; }
            button { background: #c9a227; color: #0a0a0a; border: none; padding: 12px 24px; border-radius: 4px; font-weight: 600; cursor: pointer; font-size: 1rem; }
            button:hover { background: #d4b43a; }
            button.secondary { background: #333; color: #c9a227; border: 1px solid #c9a227; }
            button.secondary:hover { background: #c9a227; color: #0a0a0a; }
            .link-list { list-style: none; }
            .link-item { background: #222; border: 1px solid #333; border-radius: 4px; padding: 15px; margin-bottom: 10px; }
            .link-url { color: #c9a227; font-family: monospace; font-size: 0.9rem; word-break: break-all; }
            .link-dest { color: #888; font-size: 0.85rem; margin-top: 5px; }
            .link-clicks { color: #4caf50; font-size: 0.85rem; margin-top: 5px; }
            .link-actions { margin-top: 10px; }
            .paywall { text-align: center; padding: 40px; }
            .price-cards { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 20px; }
            .price-card { background: #222; border: 2px solid #333; border-radius: 8px; padding: 24px; text-align: center; }
            .price { font-size: 2rem; color: #c9a227; font-weight: 700; }
            .price-note { color: #888; font-size: 0.9rem; margin-top: 10px; }
            .error { background: #3a1a1a; border: 1px solid #5a2a2a; color: #ff6b6b; padding: 12px; border-radius: 4px; margin-bottom: 15px; }
            .success { background: #1a3a1a; border: 1px solid #2a5a2a; color: #4caf50; padding: 12px; border-radius: 4px; margin-bottom: 15px; }
            .case-ref { background: #c9a227; color: #0a0a0a; padding: 4px 12px; border-radius: 4px; font-weight: 600; font-size: 0.9rem; }
            .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; flex-wrap: wrap; gap: 10px; }
            .help-text { color: #666; font-size: 0.85rem; margin-top: 8px; }
            .copy-btn { background: #333; color: #c9a227; border: 1px solid #c9a227; padding: 4px 12px; border-radius: 4px; cursor: pointer; font-size: 0.85rem; margin-left: 10px; }
            .copy-btn:hover { background: #c9a227; color: #0a0a0a; }
            table { width: 100%; border-collapse: collapse; margin-top: 15px; }
            th, td { padding: 10px; text-align: left; border-bottom: 1px solid #333; }
            th { color: #c9a227; font-weight: 600; }
            td { color: #ccc; font-size: 0.9rem; }
            .back-link { color: #c9a227; text-decoration: none; margin-bottom: 20px; display: inline-block; }
            .back-link:hover { text-decoration: underline; }
            .no-data { color: #666; text-align: center; padding: 30px; }
            .coords { color: #4caf50; font-family: monospace; font-size: 0.85rem; }
            .coords a { color: #4caf50; text-decoration: none; }
            .coords a:hover { text-decoration: underline; }
            .gps-badge { background: #4caf50; color: #0a0a0a; padding: 2px 6px; border-radius: 4px; font-size: 0.75rem; font-weight: 600; margin-left: 5px; }
            .ip-badge { background: #666; color: #fff; padding: 2px 6px; border-radius: 4px; font-size: 0.75rem; margin-left: 5px; }
        </style>
    </head>
    <body>
        <?php iis_trace_site_bar(); ?>
        <div class="container">
            <div class="header">
                <div>
                    <h1>IP Trace</h1>
                    <p class="subtitle">Track who clicks your links</p>
                </div>
                <div>
                    <span class="case-ref">Case: <?php echo esc_html($case->case_ref); ?></span>
                </div>
            </div>
            
            <?php if (isset($_GET['created'])): ?>
                <div class="success">Link created successfully! Copy and share it below.</div>
            <?php endif; ?>
            <?php if (isset($_GET['updated'])): ?>
                <div class="success">Saved.</div>
            <?php endif; ?>

            <?php if (isset($_GET['error'])): ?>
                <div class="error"><?php echo esc_html($_GET['error']); ?></div>
            <?php endif; ?>

            <?php if (isset($_GET['purchase']) && $_GET['purchase'] === 'success'): ?>
                <div class="success">Payment received. Your account is being credited — this usually takes a few seconds. Refresh this page if your quota has not updated yet.</div>
            <?php elseif (isset($_GET['purchase']) && $_GET['purchase'] === 'cancelled'): ?>
                <div class="error">Payment cancelled. You have not been charged.</div>
            <?php endif; ?>

            <div class="card">
                <h2>Your Quota</h2>
                <div class="quota-bar">
                    <div class="quota-fill" style="width: <?php echo $quota['has_unlimited'] ? 100 : min(100, ($quota['used'] / max($quota['total'], 1)) * 100); ?>%"></div>
                </div>
                <p class="quota-text">
                    <?php if ($quota['has_unlimited']): ?>
                        Unlimited plan active<?php if ($quota['unlimited_expires']): ?> — renews/expires <?php echo esc_html(date('M j, Y', strtotime($quota['unlimited_expires']))); ?><?php endif; ?>
                    <?php else: ?>
                        <?php echo $quota['used']; ?> of <?php echo $quota['total']; ?> links used
                    <?php endif; ?>
                </p>
                <?php if (!$quota['has_unlimited'] && ($stripe_on || $mmg_on)): ?>
                    <p class="help-text" style="margin-top:12px;">Go Unlimited — $100 / GYD 20,000 for a year of unlimited links.</p>
                    <?php iis_trace_pay_buttons('unlimited', $stripe_on, $mmg_on); ?>
                <?php endif; ?>
            </div>
            
            <?php if ($quota['can_create']): ?>
                <div class="card">
                    <h2>Create Tracking Link</h2>
                    <form method="POST">
                        <?php wp_nonce_field('iis_trace_create'); ?>
                        <p style="margin-bottom: 15px;">
                            <input type="text" name="destination" placeholder="Paste any link here (facebook.com, instagram.com, etc.)" required>
                        </p>
                        <p class="help-text">Tip: Paste a Facebook profile, Instagram post, or any link. When someone clicks, we log their IP, location, and device info, then send them to that link.</p>
                        <p style="margin-bottom: 15px; margin-top: 15px;">
                            <input type="text" name="label" placeholder="Label (optional) - e.g. 'Facebook stalker'">
                        </p>
                        <div style="display:flex;gap:14px;flex-wrap:wrap;margin-bottom:15px;">
                            <label style="flex:1;min-width:200px;color:#888;font-size:0.85rem;">Expires (optional)<br>
                                <input type="datetime-local" name="expires_at" style="margin-top:4px;"></label>
                            <label style="flex:1;min-width:200px;color:#888;font-size:0.85rem;">Max clicks (optional, 0 = unlimited)<br>
                                <input type="number" name="max_clicks" min="0" value="0" style="margin-top:4px;"></label>
                        </div>
                        <button type="submit">Create Link</button>
                    </form>
                </div>
            <?php else: ?>
                <div class="card paywall">
                    <h2>Quota Exhausted</h2>
                    <p>You have used your free link. Purchase more to continue tracking.</p>
                    <?php if (!$stripe_on && !$mmg_on): ?>
                        <p class="help-text" style="margin-top:20px;">Online payment is being configured. Contact IIS at contact@iis.gy or +592-623-1721 to add links to your account.</p>
                    <?php else: ?>
                    <div class="price-cards">
                        <div class="price-card">
                            <div class="price">$10</div>
                            <p class="price-note">1 additional link<br><span style="color:#666;">GYD 2,000</span></p>
                            <?php iis_trace_pay_buttons('single', $stripe_on, $mmg_on); ?>
                        </div>
                        <div class="price-card">
                            <div class="price">$100</div>
                            <p class="price-note">Unlimited links for 1 year<br><span style="color:#666;">GYD 20,000</span></p>
                            <?php iis_trace_pay_buttons('unlimited', $stripe_on, $mmg_on); ?>
                        </div>
                    </div>
                    <?php endif; ?>
                </div>
            <?php endif; ?>
            
            <div class="card">
                <h2>Your Links</h2>
                <?php if (empty($links)): ?>
                    <p style="color: #888;">No links created yet. Create your first tracking link above.</p>
                <?php else: ?>
                    <ul class="link-list">
                        <?php foreach ($links as $link):
                            $is_paused = (isset($link->status) && $link->status === 'paused');
                            $is_expired = (!empty($link->expires_at) && strtotime($link->expires_at) < time());
                            $is_maxed = (!empty($link->max_clicks) && (int)$link->max_clicks > 0 && (int)$link->clicks >= (int)$link->max_clicks);
                            if ($is_paused) { $st_label = 'Paused'; $st_color = '#e0a500'; }
                            elseif ($is_expired) { $st_label = 'Expired'; $st_color = '#ff6b6b'; }
                            elseif ($is_maxed) { $st_label = 'Click limit reached'; $st_color = '#ff6b6b'; }
                            else { $st_label = 'Active'; $st_color = '#4caf50'; }
                        ?>
                            <li class="link-item">
                                <div class="link-url">
                                    https://iis.gy/t/<?php echo esc_html($link->token); ?>
                                    <button class="copy-btn" onclick="navigator.clipboard.writeText('https://iis.gy/t/<?php echo esc_js($link->token); ?>')">Copy</button>
                                    <span style="margin-left:10px;color:<?php echo $st_color; ?>;font-size:0.8rem;font-weight:600;">● <?php echo esc_html($st_label); ?></span>
                                </div>
                                <?php if ($link->label): ?>
                                    <div class="link-dest"><?php echo esc_html($link->label); ?></div>
                                <?php endif; ?>
                                <div class="link-dest">→ <?php echo esc_html($link->destination); ?></div>
                                <div class="link-clicks">
                                    <?php echo (int)$link->clicks; ?> clicks<?php
                                        if (!empty($link->max_clicks) && (int)$link->max_clicks > 0) echo ' / ' . (int)$link->max_clicks . ' max';
                                        if (!empty($link->expires_at)) echo ' · expires ' . esc_html(date('M j, Y H:i', strtotime($link->expires_at))) . ' UTC';
                                    ?>
                                </div>
                                <div class="link-actions" style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">
                                    <a href="/ip-trace/?view=<?php echo (int)$link->id; ?>"><button type="button" class="secondary">View Details</button></a>
                                    <?php if (!$is_paused): ?>
                                    <form method="POST" style="display:inline;"><?php wp_nonce_field('iis_trace_linkaction'); ?>
                                        <input type="hidden" name="iis_action" value="pause"><input type="hidden" name="link_id" value="<?php echo (int)$link->id; ?>">
                                        <button type="submit" class="secondary">Pause</button></form>
                                    <?php else: ?>
                                    <form method="POST" style="display:inline;"><?php wp_nonce_field('iis_trace_linkaction'); ?>
                                        <input type="hidden" name="iis_action" value="resume"><input type="hidden" name="link_id" value="<?php echo (int)$link->id; ?>">
                                        <button type="submit" class="secondary">Resume</button></form>
                                    <?php endif; ?>
                                    <form method="POST" style="display:inline;" onsubmit="return confirm('Delete this link and all its click records?');"><?php wp_nonce_field('iis_trace_linkaction'); ?>
                                        <input type="hidden" name="iis_action" value="delete"><input type="hidden" name="link_id" value="<?php echo (int)$link->id; ?>">
                                        <button type="submit" class="secondary" style="border-color:#e74c3c;color:#e74c3c;">Delete</button></form>
                                </div>
                            </li>
                        <?php endforeach; ?>
                    </ul>
                <?php endif; ?>
            </div>

            <div class="card">
                <h2>Notifications</h2>
                <?php $notify_on = !get_user_meta($user_id, 'iis_trace_notify_off', true); ?>
                <p class="help-text" style="margin-top:0;">Email me at <strong><?php echo esc_html(wp_get_current_user()->user_email); ?></strong> when a link gets its first click or captures a precise GPS location.</p>
                <form method="POST" style="margin-top:12px;">
                    <?php wp_nonce_field('iis_trace_notify'); ?>
                    <label style="color:#ccc;"><input type="checkbox" name="notify_on" value="1" <?php checked($notify_on); ?>> Email me about new clicks</label>
                    <div style="margin-top:12px;"><button type="submit">Save preference</button></div>
                </form>
            </div>
        </div>
        <script>
            window.IIS_TRACE_AJAX  = <?php echo wp_json_encode( admin_url('admin-ajax.php') ); ?>;
            window.IIS_TRACE_NONCE = <?php echo wp_json_encode( $pay_nonce ); ?>;
            function iisTracePay(method, sku, btn) {
                var orig = btn.textContent;
                btn.disabled = true; btn.textContent = 'Starting…';
                var body = new URLSearchParams({ action: 'iis_trace_' + method + '_checkout', nonce: window.IIS_TRACE_NONCE, sku: sku });
                fetch(window.IIS_TRACE_AJAX, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: body })
                    .then(function(r){ return r.json(); })
                    .then(function(d){
                        if (d && d.success && d.data && d.data.redirect_url) { window.location.href = d.data.redirect_url; return; }
                        alert((d && d.data && d.data.message) ? d.data.message : 'Payment could not be started.');
                        btn.disabled = false; btn.textContent = orig;
                    })
                    .catch(function(){ alert('Connection error. Please try again.'); btn.disabled = false; btn.textContent = orig; });
            }
        </script>
    </body>
    </html>
    <?php
}

/* ------------------------------------------------------------------ pay buttons */
function iis_trace_pay_buttons( string $sku, bool $stripe_on, bool $mmg_on ): void {
    ?>
    <div style="display:flex;flex-direction:column;gap:8px;margin-top:15px;">
        <?php if ( $stripe_on ): ?>
            <button type="button" onclick="iisTracePay('stripe','<?php echo esc_js($sku); ?>',this)">Pay by Card</button>
        <?php endif; ?>
        <?php if ( $mmg_on ): ?>
            <button type="button" class="secondary" onclick="iisTracePay('mmg','<?php echo esc_js($sku); ?>',this)">Pay with MMG</button>
        <?php endif; ?>
    </div>
    <?php
}

/* ------------------------------------------------------------------ details page */
function iis_trace_render_details(int $user_id, int $link_id): void {
    global $wpdb;
    $t = iis_trace_tables();
    
    $link = $wpdb->get_row($wpdb->prepare(
        "SELECT * FROM {$t['links']} WHERE id = %d AND user_id = %d", $link_id, $user_id
    ));
    
    if (!$link) {
        wp_die('Link not found.', 'IIS Trace', ['response' => 404]);
    }
    
    $clicks = $wpdb->get_results($wpdb->prepare(
        "SELECT * FROM {$t['events']} WHERE link_id = %d ORDER BY clicked_at DESC", $link_id
    ));
    
    ?>
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Link Details | IP Trace</title>
        <style>
            * { box-sizing: border-box; margin: 0; padding: 0; }
            body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0a0a0a; color: #e0e0e0; line-height: 1.6; }
            .container { max-width: 1000px; margin: 0 auto; padding: 40px 20px; }
            h1 { color: #c9a227; font-size: 2rem; margin-bottom: 10px; }
            .subtitle { color: #888; margin-bottom: 20px; }
            .card { background: #1a1a1a; border: 1px solid #333; border-radius: 8px; padding: 24px; margin-bottom: 20px; }
            .card h2 { color: #c9a227; font-size: 1.2rem; margin-bottom: 15px; }
            table { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
            th, td { padding: 10px; text-align: left; border-bottom: 1px solid #333; }
            th { color: #c9a227; font-weight: 600; font-size: 0.8rem; text-transform: uppercase; }
            td { color: #ccc; }
            .back-link { color: #c9a227; text-decoration: none; margin-bottom: 20px; display: inline-block; }
            .back-link:hover { text-decoration: underline; }
            .link-url { color: #c9a227; font-family: monospace; font-size: 0.9rem; word-break: break-all; }
            .no-data { color: #666; text-align: center; padding: 40px; }
            .badge { background: #c9a227; color: #0a0a0a; padding: 2px 8px; border-radius: 4px; font-size: 0.8rem; font-weight: 600; }
            .geo-info { color: #4caf50; }
            .device-info { color: #888; font-size: 0.8rem; }
            .coords { color: #4caf50; font-family: monospace; font-size: 0.8rem; }
            .coords a { color: #4caf50; text-decoration: none; }
            .coords a:hover { text-decoration: underline; }
            .gps-badge { background: #4caf50; color: #0a0a0a; padding: 2px 6px; border-radius: 4px; font-size: 0.75rem; font-weight: 600; margin-left: 5px; }
            .ip-badge { background: #666; color: #fff; padding: 2px 6px; border-radius: 4px; font-size: 0.75rem; margin-left: 5px; }
            .warn-badge { background: #e53935; color: #fff; padding: 2px 6px; border-radius: 4px; font-size: 0.75rem; font-weight: 600; margin-left: 5px; }
            .click-card { background: #141414; border: 1px solid #2c2c2c; border-radius: 8px; padding: 18px; margin-bottom: 16px; }
            .click-head { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }
            .click-head .when { color: #e0e0e0; font-weight: 600; }
            .fgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 12px 24px; }
            .fitem { font-size: 0.85rem; }
            .fitem .lbl { color: #777; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; display: block; margin-bottom: 2px; }
            .fitem .val { color: #d7d7d7; word-break: break-word; }
            .fitem .val a { color: #4caf50; text-decoration: none; }
            .fitem .val.em { color: #4caf50; font-family: monospace; }
            details.raw { margin-top: 12px; }
            details.raw summary { color: #888; font-size: 0.78rem; cursor: pointer; }
            details.raw pre { background: #0d0d0d; border: 1px solid #2c2c2c; border-radius: 6px; padding: 12px; margin-top: 8px; color: #9fb0c0; font-size: 0.72rem; overflow-x: auto; white-space: pre-wrap; word-break: break-all; }
        </style>
    </head>
    <body>
        <?php iis_trace_site_bar(); ?>
        <div class="container">
            <a href="/ip-trace/" class="back-link">← Back to IP Trace</a>

            <h1>Link Details</h1>
            <p class="subtitle">Who clicked your link</p>
            
            <div class="card">
                <h2>Tracking Link</h2>
                <p class="link-url">https://iis.gy/t/<?php echo esc_html($link->token); ?></p>
                <?php if ($link->label): ?>
                    <p style="color: #888; margin-top: 5px;"><?php echo esc_html($link->label); ?></p>
                <?php endif; ?>
                <p style="color: #666; margin-top: 5px;">→ <?php echo esc_html($link->destination); ?></p>
                <p style="margin-top: 10px;"><span class="badge"><?php echo $link->clicks; ?> clicks</span></p>
            </div>
            
            <div class="card">
                <h2>Click History</h2>
                <?php if (empty($clicks)): ?>
                    <div class="no-data">No clicks yet. Share your link and check back later.</div>
                <?php else: ?>
                    <p style="color:#777;font-size:0.82rem;margin-bottom:16px;">
                        <?php echo count($clicks); ?> recorded click<?php echo count($clicks) === 1 ? '' : 's'; ?>.
                        Precise GPS appears only when the visitor allowed their browser's location prompt; otherwise the position is estimated from the IP address.
                    </p>
                    <?php foreach ($clicks as $click) iis_trace_render_click_card($click); ?>
                <?php endif; ?>
            </div>
        </div>
    </body>
    </html>
    <?php
}

/* ------------------------------------------------------------------ one click, full forensic card */
function iis_trace_render_click_card(object $click): void {
    $meta   = json_decode($click->meta ?: '{}', true);
    if (!is_array($meta)) $meta = [];
    $geo    = $meta['geo'] ?? ($meta['server']['geo'] ?? []);
    $cli    = $meta['client'] ?? [];
    $gps    = is_array($cli['gps'] ?? null) ? $cli['gps'] : [];
    $hdr    = $meta['headers'] ?? [];
    $uad    = is_array($cli['ua_data'] ?? null) ? $cli['ua_data'] : [];

    $field = function(string $label, $value, string $cls = '') {
        if ($value === '' || $value === null || $value === []) return;
        echo '<div class="fitem"><span class="lbl">' . esc_html($label) . '</span>'
           . '<span class="val ' . esc_attr($cls) . '">' . $value . '</span></div>';
    };
    $loc_parts = array_filter([$click->city, $click->region, $click->country]);

    $is_gps = ($click->precise_lat && $click->precise_lon);
    ?>
    <div class="click-card">
        <div class="click-head">
            <span class="when"><?php echo esc_html($click->clicked_at); ?> UTC</span>
            <?php if ($is_gps): ?><span class="gps-badge">PRECISE GPS</span><?php else: ?><span class="ip-badge">IP-BASED</span><?php endif; ?>
            <?php if (!empty($geo['proxy'])): ?><span class="warn-badge">PROXY / VPN</span><?php endif; ?>
            <?php if (!empty($geo['hosting'])): ?><span class="warn-badge">HOSTING / DATACENTRE</span><?php endif; ?>
            <?php if (!empty($geo['mobile'])): ?><span class="ip-badge">MOBILE NETWORK</span><?php endif; ?>
        </div>
        <div class="fgrid">
            <?php
            $field('IP address', esc_html($click->ip), 'em');
            if (!empty($meta['ip_chain']) && $meta['ip_chain'] !== $click->ip) $field('Forwarded-for chain', esc_html($meta['ip_chain']));
            $field('Approx. location (IP)', $loc_parts ? esc_html(implode(', ', $loc_parts)) : null);
            if (!empty($geo['zip'])) $field('Postal code', esc_html($geo['zip']));

            if ($is_gps) {
                $acc = $click->precise_accuracy ? ' (±' . round($click->precise_accuracy) . ' m)' : '';
                $field('Precise GPS', '<a target="_blank" href="https://www.google.com/maps?q=' . $click->precise_lat . ',' . $click->precise_lon . '">📍 ' . esc_html($click->precise_lat . ', ' . $click->precise_lon) . '</a>' . esc_html($acc), 'em');
                if (isset($gps['altitude']) && $gps['altitude'] !== null) $field('Altitude', esc_html(round((float)$gps['altitude'], 1) . ' m' . (isset($gps['altitude_accuracy']) && $gps['altitude_accuracy'] !== null ? ' (±' . round((float)$gps['altitude_accuracy']) . ' m)' : '')));
                if (isset($gps['speed']) && $gps['speed'] !== null && $gps['speed'] !== '') $field('Speed', esc_html(round((float)$gps['speed'], 1) . ' m/s'));
                if (isset($gps['heading']) && $gps['heading'] !== null && $gps['heading'] !== '') $field('Heading', esc_html(round((float)$gps['heading']) . '°'));
            } elseif ($click->latitude && $click->longitude) {
                $field('Estimated position (IP)', '<a target="_blank" href="https://www.google.com/maps?q=' . $click->latitude . ',' . $click->longitude . '">📍 ' . esc_html($click->latitude . ', ' . $click->longitude) . '</a>', 'em');
            }
            if (!empty($cli['gps_error']) && !$is_gps) $field('GPS prompt', 'Not granted (' . esc_html($cli['gps_error']) . ')');

            $isp_org = array_filter([$click->isp, ($click->org && $click->org !== $click->isp) ? $click->org : null]);
            $field('ISP / Organisation', $isp_org ? esc_html(implode(' · ', $isp_org)) : null);
            if (!empty($geo['as']) || !empty($geo['asname'])) $field('Network (ASN)', esc_html(trim(($geo['as'] ?? '') . ' ' . ($geo['asname'] ?? ''))));

            $field('Timezone (IP)', !empty($geo['timezone']) ? esc_html($geo['timezone']) : null);
            $field('Timezone (device)', !empty($cli['timezone']) ? esc_html($cli['timezone'] . (isset($cli['tz_offset_min']) ? ' (UTC' . sprintf('%+d', -1 * (int)$cli['tz_offset_min'] / 60) . ')' : '')) : null);
            $field('Connection', !empty($cli['net_type']) ? esc_html(strtoupper($cli['net_type']) . (!empty($cli['net_downlink']) ? ', ~' . $cli['net_downlink'] . ' Mbps' : '')) : null);

            // Device & browser
            $platform = $uad['platform'] ?? ($cli['platform'] ?? '');
            $pv = $uad['platformVersion'] ?? '';
            $field('Platform', $platform ? esc_html(trim($platform . ' ' . $pv)) : null);
            if (!empty($uad['model'])) $field('Device model', esc_html($uad['model']));
            if (!empty($uad['architecture'])) $field('CPU architecture', esc_html($uad['architecture'] . (!empty($uad['bitness']) ? ' ' . $uad['bitness'] . '-bit' : '')));
            $field('Screen', !empty($cli['screen']) ? esc_html($cli['screen'] . (!empty($cli['pixel_ratio']) ? ' @' . $cli['pixel_ratio'] . 'x' : '')) : null);
            $field('Viewport', !empty($cli['viewport']) ? esc_html($cli['viewport']) : null);
            if (isset($cli['device_memory']) && $cli['device_memory'] !== null) $field('Device memory', esc_html($cli['device_memory'] . ' GB'));
            if (!empty($cli['cpu_cores'])) $field('CPU cores', esc_html((string)$cli['cpu_cores']));
            if (isset($cli['touch_points'])) $field('Touch', (int)$cli['touch_points'] > 0 ? 'Yes (' . (int)$cli['touch_points'] . ' points)' : 'No');
            $field('Languages', !empty($cli['languages']) ? esc_html($cli['languages']) : (!empty($hdr['accept_language']) ? esc_html($hdr['accept_language']) : null));
            $field('User agent', esc_html(substr($click->user_agent, 0, 140)));
            $field('Referrer', esc_html(substr($click->referrer, 0, 120) ?: 'Direct'));
            ?>
        </div>
        <details class="raw">
            <summary>Raw captured data (for the report)</summary>
            <pre><?php echo esc_html(wp_json_encode($meta, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); ?></pre>
        </details>
    </div>
    <?php
}

/* ------------------------------------------------------------------ helpers */
function iis_trace_ensure_case(int $user_id): object {
    global $wpdb;
    $t = iis_trace_tables();
    
    $case = $wpdb->get_row($wpdb->prepare(
        "SELECT * FROM {$t['cases']} WHERE user_id = %d", $user_id
    ));
    
    if (!$case) {
        $case_ref = 'TRACE-' . date('Y') . '-' . str_pad($user_id, 4, '0', STR_PAD_LEFT);
        $wpdb->insert($t['cases'], [
            'user_id'    => $user_id,
            'case_ref'   => $case_ref,
            'status'     => 'active',
            'created_at' => current_time('mysql'),
        ]);
        $case = $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM {$t['cases']} WHERE user_id = %d", $user_id
        ));
    }

    // Never let a DB hiccup turn into a fatal "critical error" page: a client
    // must always be able to open IP Trace. Fall back to a transient case object.
    if (!$case) {
        error_log('IIS Trace: ensure_case could not persist a case for user ' . $user_id . ' — ' . $wpdb->last_error);
        $case = (object) [
            'id'       => 0,
            'user_id'  => $user_id,
            'case_ref' => 'TRACE-' . date('Y') . '-' . str_pad($user_id, 4, '0', STR_PAD_LEFT),
            'status'   => 'active',
        ];
    }

    return $case;
}

function iis_trace_get_quota(int $user_id): array {
    global $wpdb;
    $t = iis_trace_tables();
    
    $used = (int) $wpdb->get_var($wpdb->prepare(
        "SELECT COUNT(*) FROM {$t['links']} WHERE user_id = %d", $user_id
    ));
    
    $has_unlimited = (bool) get_user_meta($user_id, 'iis_trace_unlimited', true);
    $unlimited_expires = get_user_meta($user_id, 'iis_trace_unlimited_expires', true);
    if ($has_unlimited && $unlimited_expires && strtotime($unlimited_expires) < time()) {
        $has_unlimited = false; // the 1-year unlimited plan has lapsed
    }
    $paid_links = (int) get_user_meta($user_id, 'iis_trace_paid_links', true);

    $total = IIS_TRACE_FREE_LINKS + $paid_links;
    if ($has_unlimited) $total = PHP_INT_MAX;

    return [
        'used' => $used,
        'total' => $total,
        'has_unlimited' => $has_unlimited,
        'unlimited_expires' => $has_unlimited ? $unlimited_expires : '',
        'can_create' => $used < $total || $has_unlimited
    ];
}

function iis_trace_create_link(int $user_id): void {
    global $wpdb;
    $t = iis_trace_tables();

    // Rate limit: cap new links per hour to stop abuse / runaway scripts.
    $rk = 'iis_trace_rate_' . $user_id;
    $recent = (int) get_transient($rk);
    if ($recent >= IIS_TRACE_RATE_PER_HOUR) {
        wp_redirect('/ip-trace/?error=' . urlencode('You are creating links too quickly. Please wait a little and try again.'));
        exit;
    }

    $destination = trim(sanitize_text_field($_POST['destination'] ?? ''));
    $label = sanitize_text_field($_POST['label'] ?? '');

    if ($destination && !preg_match('~^https?://~i', $destination)) {
        $destination = 'https://' . $destination;
    }

    $err = iis_trace_validate_destination($destination);
    if ($err !== '') {
        wp_redirect('/ip-trace/?error=' . urlencode($err));
        exit;
    }

    $quota = iis_trace_get_quota($user_id);
    if (!$quota['can_create']) {
        wp_redirect('/ip-trace/?error=' . urlencode('Quota exhausted'));
        exit;
    }

    // Optional link controls.
    $expires_at = null;
    $exp_raw = sanitize_text_field($_POST['expires_at'] ?? '');
    if ($exp_raw !== '') {
        $ts = strtotime($exp_raw);
        if ($ts && $ts > time()) $expires_at = date('Y-m-d H:i:s', $ts);
    }
    $max_clicks = max(0, min(1000000, intval($_POST['max_clicks'] ?? 0)));

    $token = iis_trace_generate_token();

    $wpdb->insert($t['links'], [
        'user_id'     => $user_id,
        'token'       => $token,
        'destination' => $destination,
        'label'       => $label,
        'status'      => 'active',
        'expires_at'  => $expires_at,
        'max_clicks'  => $max_clicks,
    ]);

    set_transient($rk, $recent + 1, HOUR_IN_SECONDS);

    wp_redirect('/ip-trace/?created=1');
    exit;
}

/** Client pauses / resumes / deletes one of their own links. */
function iis_trace_client_link_action(int $user_id): void {
    global $wpdb;
    $t = iis_trace_tables();
    $action = sanitize_key($_POST['iis_action'] ?? '');
    $link_id = intval($_POST['link_id'] ?? 0);
    if (!$link_id) { wp_redirect('/ip-trace/'); exit; }

    // Ownership check — a client can only touch their own links.
    $owner = (int) $wpdb->get_var($wpdb->prepare("SELECT user_id FROM {$t['links']} WHERE id=%d", $link_id));
    if ($owner !== $user_id) { wp_redirect('/ip-trace/?error=' . urlencode('Not your link.')); exit; }

    if ($action === 'pause')  $wpdb->update($t['links'], ['status' => 'paused'], ['id' => $link_id], ['%s'], ['%d']);
    if ($action === 'resume') $wpdb->update($t['links'], ['status' => 'active'], ['id' => $link_id], ['%s'], ['%d']);
    if ($action === 'delete') {
        $wpdb->delete($t['events'], ['link_id' => $link_id], ['%d']);
        $wpdb->delete($t['links'], ['id' => $link_id], ['%d']);
    }
    wp_redirect('/ip-trace/?updated=1');
    exit;
}

/** Notifications on/off toggle (client). */
function iis_trace_client_notify_toggle(int $user_id): void {
    if (!empty($_POST['notify_on'])) delete_user_meta($user_id, 'iis_trace_notify_off');
    else update_user_meta($user_id, 'iis_trace_notify_off', 1);
    wp_redirect('/ip-trace/?updated=1');
    exit;
}

function iis_trace_generate_token(): string {
    return substr(strtolower(wp_generate_password(18, false, false)), 0, 18);
}

function iis_trace_get_user_links(int $user_id): array {
    global $wpdb;
    $t = iis_trace_tables();
    return $wpdb->get_results($wpdb->prepare(
        "SELECT * FROM {$t['links']} WHERE user_id = %d ORDER BY created_at DESC", $user_id
    ));
}

/* ------------------------------------------------------------------ admin menu */
add_action('admin_menu', function() {
    add_menu_page('IP Trace', 'IP Trace', 'manage_options', 'iis-trace', 'iis_trace_admin_router', 'dashicons-location-alt', 57);
    add_submenu_page('iis-trace', 'Dashboard', 'Dashboard', 'manage_options', 'iis-trace', 'iis_trace_admin_router');
    add_submenu_page('iis-trace', 'Clients', 'Clients', 'manage_options', 'iis-trace&tab=clients', 'iis_trace_admin_router');
    add_submenu_page('iis-trace', 'Links', 'Links', 'manage_options', 'iis-trace&tab=links', 'iis_trace_admin_router');
    add_submenu_page('iis-trace', 'Clicks', 'Click Log', 'manage_options', 'iis-trace&tab=clicks', 'iis_trace_admin_router');
    add_submenu_page('iis-trace', 'Payments', 'Payments', 'manage_options', 'iis-trace&tab=payments', 'iis_trace_admin_router');
    add_submenu_page('iis-trace', 'Audit Log', 'Audit Log', 'manage_options', 'iis-trace&tab=audit', 'iis_trace_admin_router');
    add_submenu_page('iis-trace', 'Settings', 'Settings', 'manage_options', 'iis-trace&tab=settings', 'iis_trace_admin_router');
});

/* ================================================================== admin: actions */
function iis_trace_admin_guard(string $action): void {
    if (!current_user_can('manage_options')) wp_die('Not allowed.');
    check_admin_referer($action);
}
function iis_trace_admin_back(string $msg = '', string $tab = 'clients'): void {
    $url = admin_url('admin.php?page=iis-trace&tab=' . $tab);
    if ($msg) $url = add_query_arg('iis_notice', rawurlencode($msg), $url);
    wp_safe_redirect($url); exit;
}

add_action('admin_post_iis_trace_grant_links', function() {
    iis_trace_admin_guard('iis_trace_grant_links');
    $uid = intval($_POST['user_id'] ?? 0);
    $n   = max(1, min(1000, intval($_POST['count'] ?? 1)));
    if ($uid) {
        $paid = (int) get_user_meta($uid, 'iis_trace_paid_links', true);
        update_user_meta($uid, 'iis_trace_paid_links', $paid + $n);
        iis_trace_audit('grant_links', "user:$uid", "+{$n} link(s)");
    }
    iis_trace_admin_back("Granted {$n} link(s).");
});

add_action('admin_post_iis_trace_grant_unlimited', function() {
    iis_trace_admin_guard('iis_trace_grant_unlimited');
    $uid = intval($_POST['user_id'] ?? 0);
    $months = max(1, min(60, intval($_POST['months'] ?? 12)));
    if ($uid) {
        update_user_meta($uid, 'iis_trace_unlimited', 1);
        update_user_meta($uid, 'iis_trace_unlimited_expires', date('Y-m-d H:i:s', strtotime("+{$months} months")));
        iis_trace_audit('grant_unlimited', "user:$uid", "{$months} month(s)");
    }
    iis_trace_admin_back("Unlimited granted for {$months} month(s).");
});

add_action('admin_post_iis_trace_revoke_unlimited', function() {
    iis_trace_admin_guard('iis_trace_revoke_unlimited');
    $uid = intval($_POST['user_id'] ?? 0);
    if ($uid) { delete_user_meta($uid, 'iis_trace_unlimited'); delete_user_meta($uid, 'iis_trace_unlimited_expires'); iis_trace_audit('revoke_unlimited', "user:$uid"); }
    iis_trace_admin_back('Unlimited plan revoked.');
});

add_action('admin_post_iis_trace_delete_link', function() {
    iis_trace_admin_guard('iis_trace_delete_link');
    $lid = intval($_POST['link_id'] ?? 0);
    if ($lid) {
        global $wpdb; $t = iis_trace_tables();
        $wpdb->delete($t['events'], ['link_id' => $lid], ['%d']);
        $wpdb->delete($t['links'], ['id' => $lid], ['%d']);
        iis_trace_audit('delete_link', "link:$lid");
    }
    iis_trace_admin_back('Link and its click log deleted.', 'links');
});

add_action('admin_post_iis_trace_export_case', function() {
    iis_trace_admin_guard('iis_trace_export_case');
    $uid = intval($_GET['user_id'] ?? 0);
    if (!$uid) iis_trace_admin_back('No client.');
    global $wpdb; $t = iis_trace_tables();
    $u = get_userdata($uid);
    $rows = $wpdb->get_results($wpdb->prepare(
        "SELECT e.*, l.token, l.destination, l.label FROM {$t['events']} e
         JOIN {$t['links']} l ON e.link_id = l.id WHERE l.user_id = %d ORDER BY e.clicked_at DESC", $uid));
    $case = $wpdb->get_var($wpdb->prepare("SELECT case_ref FROM {$t['cases']} WHERE user_id=%d", $uid));
    nocache_headers();
    header('Content-Type: text/csv; charset=utf-8');
    header('Content-Disposition: attachment; filename="ip-trace-' . sanitize_file_name($case ?: ('user-'.$uid)) . '.csv"');
    $out = fopen('php://output', 'w');
    fputcsv($out, ['Case', 'Client', 'Email', 'Link token', 'Label', 'Destination', 'Clicked (UTC)', 'IP', 'Location', 'ISP', 'ASN', 'Proxy/VPN', 'Hosting', 'Mobile', 'Source', 'Precise lat', 'Precise lon', 'Accuracy m', 'Device TZ', 'User agent', 'Referrer']);
    foreach ($rows as $r) {
        $m = json_decode($r->meta ?: '{}', true); if (!is_array($m)) $m = [];
        $geo = $m['geo'] ?? []; $cli = $m['client'] ?? [];
        fputcsv($out, [
            $case, $u ? $u->display_name : $uid, $u ? $u->user_email : '',
            $r->token, $r->label, $r->destination, $r->clicked_at, $r->ip,
            trim(implode(', ', array_filter([$r->city, $r->region, $r->country]))),
            $r->isp, trim(($geo['as'] ?? '') . ' ' . ($geo['asname'] ?? '')),
            !empty($geo['proxy']) ? 'yes' : '', !empty($geo['hosting']) ? 'yes' : '', !empty($geo['mobile']) ? 'yes' : '',
            $r->location_source, $r->precise_lat, $r->precise_lon, $r->precise_accuracy,
            $cli['timezone'] ?? '', $r->user_agent, $r->referrer,
        ]);
    }
    fclose($out); exit;
});

add_action('admin_post_iis_trace_delete_event', function() {
    iis_trace_admin_guard('iis_trace_delete_event');
    global $wpdb; $t = iis_trace_tables();
    $eid = intval($_POST['event_id'] ?? 0);
    $back = sanitize_key($_POST['back_tab'] ?? 'clicks');
    $view = intval($_POST['view_user'] ?? 0);
    if ($eid) {
        $link_id = (int) $wpdb->get_var($wpdb->prepare("SELECT link_id FROM {$t['events']} WHERE id=%d", $eid));
        $wpdb->delete($t['events'], ['id' => $eid], ['%d']);
        if ($link_id) {
            $n = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$t['events']} WHERE link_id=%d", $link_id));
            $wpdb->update($t['links'], ['clicks' => $n], ['id' => $link_id], ['%d'], ['%d']);
        }
    }
    if ($eid) iis_trace_audit('delete_event', "event:$eid");
    if ($view) iis_trace_admin_back_view($view, 'Click record deleted.');
    iis_trace_admin_back('Click record deleted.', $back);
});

add_action('admin_post_iis_trace_clear_link_clicks', function() {
    iis_trace_admin_guard('iis_trace_clear_link_clicks');
    global $wpdb; $t = iis_trace_tables();
    $lid = intval($_POST['link_id'] ?? 0);
    $view = intval($_POST['view_user'] ?? 0);
    if ($lid) {
        $wpdb->delete($t['events'], ['link_id' => $lid], ['%d']);
        $wpdb->update($t['links'], ['clicks' => 0], ['id' => $lid], ['%d'], ['%d']);
        iis_trace_audit('clear_link_clicks', "link:$lid");
    }
    if ($view) iis_trace_admin_back_view($view, 'Click log cleared for that link.');
    iis_trace_admin_back('Click log cleared.', 'links');
});

add_action('admin_post_iis_trace_purge_client', function() {
    iis_trace_admin_guard('iis_trace_purge_client');
    global $wpdb; $t = iis_trace_tables();
    $uid = intval($_POST['user_id'] ?? 0);
    $scope = sanitize_key($_POST['scope'] ?? 'iptrace'); // iptrace | all
    if ($uid) {
        // IP Trace data
        $link_ids = $wpdb->get_col($wpdb->prepare("SELECT id FROM {$t['links']} WHERE user_id=%d", $uid));
        if ($link_ids) {
            $in = implode(',', array_map('intval', $link_ids));
            $wpdb->query("DELETE FROM {$t['events']} WHERE link_id IN ($in)");
        }
        $wpdb->delete($t['links'], ['user_id' => $uid], ['%d']);
        $wpdb->delete($t['cases'], ['user_id' => $uid], ['%d']);
        foreach (['iis_trace_paid_links','iis_trace_unlimited','iis_trace_unlimited_expires','iis_trace_payments','iis_trace_payment_log'] as $mk) {
            delete_user_meta($uid, $mk);
        }
        if ($scope === 'all') {
            $wpdb->delete($wpdb->prefix . 'nt_subscriptions', ['user_id' => $uid], ['%d']);
            delete_user_meta($uid, 'nt_free_searches_used');
            delete_user_meta($uid, 'nt_free_last_query');
        }
        iis_trace_audit('purge_client', "user:$uid", "scope={$scope}");
    }
    iis_trace_admin_back($scope === 'all' ? 'All IP Trace + NationalTrace data cleared for that client.' : 'IP Trace data cleared for that client.', 'clients');
});

function iis_trace_admin_back_view(int $uid, string $msg): void {
    wp_safe_redirect(add_query_arg('iis_notice', rawurlencode($msg), admin_url('admin.php?page=iis-trace&view=' . $uid)));
    exit;
}

add_action('admin_post_iis_trace_save_case', function() {
    iis_trace_admin_guard('iis_trace_save_case');
    global $wpdb; $t = iis_trace_tables();
    $uid = intval($_POST['user_id'] ?? 0);
    if (!$uid) iis_trace_admin_back('No client.');
    $label  = sanitize_text_field($_POST['case_label'] ?? '');
    $status = sanitize_key($_POST['case_status'] ?? 'active');
    if (!in_array($status, ['active','closed','on_hold'], true)) $status = 'active';
    $notes  = wp_kses_post($_POST['case_notes'] ?? '');
    $data = ['label' => $label, 'status' => $status, 'notes' => $notes,
             'closed_at' => $status === 'closed' ? current_time('mysql') : null];
    $exists = $wpdb->get_var($wpdb->prepare("SELECT id FROM {$t['cases']} WHERE user_id=%d", $uid));
    if ($exists) {
        $wpdb->update($t['cases'], $data, ['user_id' => $uid]);
    } else {
        $data['user_id'] = $uid; $data['case_ref'] = 'TRACE-' . date('Y') . '-' . str_pad($uid, 4, '0', STR_PAD_LEFT);
        $data['created_at'] = current_time('mysql');
        $wpdb->insert($t['cases'], $data);
    }
    iis_trace_audit('save_case', "user:$uid", "status={$status}");
    iis_trace_admin_back_view($uid, 'Case updated.');
});

add_action('admin_post_iis_trace_report', function() {
    if (!current_user_can('manage_options')) wp_die('Not allowed.');
    check_admin_referer('iis_trace_report');
    $uid = intval($_GET['user_id'] ?? 0);
    if (!$uid) wp_die('No client.');
    iis_trace_render_evidence_report($uid);
    exit;
});

add_action('admin_post_iis_trace_save_settings', function() {
    iis_trace_admin_guard('iis_trace_save_settings');
    update_option('iis_trace_blocklist', wp_kses_post($_POST['blocklist'] ?? ''));
    iis_trace_audit('save_settings', 'options');
    iis_trace_admin_back('Settings saved.', 'settings');
});

/** NationalTrace subscription summary for a user. */
function iis_trace_nt_summary(int $uid): array {
    global $wpdb;
    $tbl = $wpdb->prefix . 'nt_subscriptions';
    $plans = get_option('nt_plans', []);
    $sub = $wpdb->get_row($wpdb->prepare(
        "SELECT * FROM {$tbl} WHERE user_id=%d ORDER BY FIELD(status,'active','pending','cancelled','expired'), expiry_date DESC LIMIT 1", $uid
    ));
    if (!$sub) return ['state' => 'none', 'label' => 'No subscription', 'plan' => '', 'expiry' => '', 'row' => null];
    $plan_label = $plans[$sub->plan]['label'] ?? $sub->plan;
    $active = ($sub->status === 'active' && (!$sub->expiry_date || strtotime($sub->expiry_date) > time()));
    if ($active) {
        $state = 'active';
    } elseif ($sub->status === 'active') {
        $state = 'expired'; // DB still says active but the period has passed
    } else {
        $state = $sub->status;
    }
    return [
        'state'  => $state,
        'label'  => $plan_label . ' · ' . ucfirst($state),
        'plan'   => $plan_label,
        'expiry' => $sub->expiry_date,
        'row'    => $sub,
    ];
}

/** All NationalTrace subscription rows for a user (history). */
function iis_trace_nt_history(int $uid): array {
    global $wpdb;
    $tbl = $wpdb->prefix . 'nt_subscriptions';
    return $wpdb->get_results($wpdb->prepare("SELECT * FROM {$tbl} WHERE user_id=%d ORDER BY created_at DESC", $uid));
}

/* ================================================================== admin: stats + chrome */
function iis_trace_admin_stats(): array {
    global $wpdb; $t = iis_trace_tables();
    $s = [];
    $s['clients']  = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$t['cases']}");
    $s['links']    = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$t['links']}");
    $s['clicks']   = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$t['events']}");
    $s['gps']      = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$t['events']} WHERE location_source='gps'");
    $s['proxy']    = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$t['events']} WHERE meta LIKE '%\"proxy\":true%'");
    $s['clicks7']  = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$t['events']} WHERE clicked_at >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 7 DAY)");
    // revenue + plans from structured payment logs
    $logs = $wpdb->get_col("SELECT meta_value FROM {$wpdb->usermeta} WHERE meta_key='iis_trace_payment_log'");
    $usd = 0; $singles = 0; $unlimited = 0;
    foreach ($logs as $raw) { foreach ((array) maybe_unserialize($raw) as $p) { $usd += (int)($p['usd'] ?? 0); if (($p['type'] ?? '')==='unlimited') $unlimited++; else $singles++; } }
    $s['revenue_usd'] = $usd; $s['sold_single'] = $singles; $s['sold_unlimited'] = $unlimited;
    $s['active_unlimited'] = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->usermeta} WHERE meta_key='iis_trace_unlimited' AND meta_value='1'");
    // NationalTrace (same clients)
    $ntt = $wpdb->prefix . 'nt_subscriptions';
    $s['nt_active'] = (int) $wpdb->get_var("SELECT COUNT(DISTINCT user_id) FROM {$ntt} WHERE status='active' AND (expiry_date IS NULL OR expiry_date > UTC_TIMESTAMP())");
    $s['nt_pending'] = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$ntt} WHERE status='pending'");
    return $s;
}

function iis_trace_admin_router(): void {
    if (!current_user_can('manage_options')) wp_die('Not allowed.');
    $tab = sanitize_key($_GET['tab'] ?? 'overview');
    $view_user = intval($_GET['view'] ?? 0);

    echo '<div class="wrap"><h1 style="display:flex;align-items:center;gap:10px;"><span class="dashicons dashicons-location-alt" style="font-size:28px;width:28px;height:28px;"></span> IP Trace</h1>';

    if (isset($_GET['iis_notice'])) {
        echo '<div class="notice notice-success is-dismissible"><p>' . esc_html(wp_unslash($_GET['iis_notice'])) . '</p></div>';
    }

    $tabs = ['overview'=>'Dashboard','clients'=>'Clients','links'=>'Links','clicks'=>'Click Log','payments'=>'Payments','audit'=>'Audit Log','settings'=>'Settings'];
    echo '<h2 class="nav-tab-wrapper">';
    foreach ($tabs as $k=>$label) {
        $cls = ($tab===$k && !$view_user) ? ' nav-tab-active' : '';
        printf('<a class="nav-tab%s" href="%s">%s</a>', $cls, esc_url(admin_url('admin.php?page=iis-trace&tab='.$k)), esc_html($label));
    }
    echo '</h2><div style="margin-top:16px;">';

    if ($view_user) { iis_trace_admin_view_client($view_user); }
    elseif ($tab==='clients') { iis_trace_admin_tab_clients(); }
    elseif ($tab==='links') { iis_trace_admin_tab_links(); }
    elseif ($tab==='clicks') { iis_trace_admin_tab_clicks(); }
    elseif ($tab==='payments') { iis_trace_admin_tab_payments(); }
    elseif ($tab==='audit') { iis_trace_admin_tab_audit(); }
    elseif ($tab==='settings') { iis_trace_admin_tab_settings(); }
    else { iis_trace_admin_tab_overview(); }

    echo '</div></div>';
}

function iis_trace_admin_stat_cards(array $cards): void {
    echo '<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:14px;margin:4px 0 24px;">';
    foreach ($cards as $c) {
        echo '<div style="background:#fff;border:1px solid #dcdcde;border-radius:8px;padding:16px 18px;">'
           . '<div style="font-size:12px;text-transform:uppercase;letter-spacing:.05em;color:#646970;">' . esc_html($c[0]) . '</div>'
           . '<div style="font-size:26px;font-weight:700;color:#1d2327;margin-top:4px;">' . esc_html($c[1]) . '</div>'
           . (isset($c[2]) ? '<div style="font-size:12px;color:#787c82;margin-top:2px;">' . esc_html($c[2]) . '</div>' : '')
           . '</div>';
    }
    echo '</div>';
}

/* ================================================================== admin: Overview */
function iis_trace_admin_tab_overview(): void {
    $s = iis_trace_admin_stats();
    $gps_pct = $s['clicks'] ? round($s['gps'] / $s['clicks'] * 100) : 0;
    $proxy_pct = $s['clicks'] ? round($s['proxy'] / $s['clicks'] * 100) : 0;
    iis_trace_admin_stat_cards([
        ['Clients', number_format($s['clients'])],
        ['Tracking links', number_format($s['links'])],
        ['Total clicks', number_format($s['clicks']), $s['clicks7'] . ' in last 7 days'],
        ['Precise GPS', $gps_pct . '%', $s['gps'] . ' of ' . $s['clicks'] . ' clicks'],
        ['Proxy / VPN clicks', $proxy_pct . '%', $s['proxy'] . ' flagged'],
        ['Unlimited plans', number_format($s['active_unlimited']), 'active now'],
        ['Revenue (IP Trace)', '$' . number_format($s['revenue_usd']), $s['sold_single'] . ' links, ' . $s['sold_unlimited'] . ' unlimited'],
        ['NationalTrace active', number_format($s['nt_active']), $s['nt_pending'] . ' pending approval'],
    ]);
    if ($s['nt_pending'] > 0) {
        echo '<div class="notice notice-warning inline" style="margin:0 0 18px;"><p>'
           . esc_html($s['nt_pending']) . ' NationalTrace payment(s) awaiting approval. '
           . '<a href="' . esc_url(admin_url('admin.php?page=nationaltrace-subscriptions&filter=pending')) . '">Review in NationalTrace →</a></p></div>';
    }
    echo '<p style="color:#646970;">Clients span both NationalTrace and IP Trace. Use the <strong>Clients</strong> tab to manage either, or <strong>Click Log</strong> for the live IP Trace feed.</p>';
    iis_trace_admin_recent_clicks(15);
}

/* ================================================================== admin: Clients */
function iis_trace_admin_tab_clients(): void {
    global $wpdb; $t = iis_trace_tables();
    $ntt = $wpdb->prefix . 'nt_subscriptions';
    $cap = $wpdb->prefix . 'capabilities';

    // Every client across BOTH services: anyone with an IP Trace case, a
    // NationalTrace subscription, or a client role.
    $ids = $wpdb->get_col(
        "SELECT ID FROM {$wpdb->users} u WHERE
            u.ID IN (SELECT user_id FROM {$t['cases']})
            OR u.ID IN (SELECT user_id FROM {$ntt})
            OR u.ID IN (SELECT user_id FROM {$wpdb->usermeta} WHERE meta_key='{$cap}' AND (meta_value LIKE '%subscriber%' OR meta_value LIKE '%iis_client%'))
         ORDER BY u.user_registered DESC"
    );

    $search = sanitize_text_field($_GET['s'] ?? '');
    echo '<form method="get" style="margin:0 0 12px;"><input type="hidden" name="page" value="iis-trace"><input type="hidden" name="tab" value="clients">';
    echo '<input type="search" name="s" value="'.esc_attr($search).'" placeholder="Search name or email" style="min-width:260px;"> <button class="button">Search</button>';
    if ($search !== '') echo ' <a class="button" href="'.esc_url(admin_url('admin.php?page=iis-trace&tab=clients')).'">Clear</a>';
    echo '</form>';
    echo '<p style="color:#646970;margin:0 0 12px;">One row per client, showing NationalTrace and IP Trace together. Click a client to manage, export, or clear their data.</p>';
    echo '<table class="wp-list-table widefat fixed striped"><thead><tr>'
       . '<th>Client</th><th>NationalTrace</th><th>IP Trace case</th><th>Quota</th><th>Links</th><th>Clicks</th><th>Manage</th></tr></thead><tbody>';
    if (!$ids) echo '<tr><td colspan="7">No clients yet.</td></tr>';

    $shown = 0;
    foreach ($ids as $uid) {
        $uid = (int) $uid;
        $u = get_userdata($uid); if (!$u) continue;
        if ($search !== '' && stripos($u->display_name.' '.$u->user_login.' '.$u->user_email, $search) === false) continue;
        $shown++;
        $nt = iis_trace_nt_summary($uid);
        $case = $wpdb->get_var($wpdb->prepare("SELECT case_ref FROM {$t['cases']} WHERE user_id=%d", $uid));
        $links = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$t['links']} WHERE user_id=%d", $uid));
        $clicks = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$t['events']} e JOIN {$t['links']} l ON e.link_id=l.id WHERE l.user_id=%d", $uid));
        $q = iis_trace_get_quota($uid);
        $quota = $q['has_unlimited'] ? ('Unlimited' . ($q['unlimited_expires'] ? ' → ' . date('M j, Y', strtotime($q['unlimited_expires'])) : '')) : ($q['used'] . ' / ' . $q['total']);
        $view = esc_url(admin_url('admin.php?page=iis-trace&view=' . $uid));

        $nt_colors = ['active' => '#00844a', 'pending' => '#b26200', 'none' => '#787c82'];
        $nt_color = $nt_colors[$nt['state']] ?? '#50575e';

        echo '<tr>';
        echo '<td><a href="'.$view.'"><strong>'.esc_html($u->display_name ?: $u->user_login).'</strong></a><br><span style="color:#787c82;">'.esc_html($u->user_email).'</span></td>';
        echo '<td style="color:'.$nt_color.';">'.esc_html($nt['label']).($nt['expiry'] && $nt['state']==='active' ? '<br><span style="color:#787c82;">to '.esc_html(date('M j, Y', strtotime($nt['expiry']))).'</span>' : '').'</td>';
        echo '<td>'.($case ? '<a href="'.$view.'">'.esc_html($case).'</a>' : '<span style="color:#787c82;">—</span>').'</td>';
        echo '<td>'.esc_html($quota).'</td>';
        echo '<td>'.$links.'</td><td>'.$clicks.'</td>';
        echo '<td style="white-space:nowrap;">';
        echo '<form method="post" action="'.esc_url(admin_url('admin-post.php')).'" style="display:inline;">';
        wp_nonce_field('iis_trace_grant_links');
        echo '<input type="hidden" name="action" value="iis_trace_grant_links"><input type="hidden" name="user_id" value="'.$uid.'"><input type="hidden" name="count" value="1">';
        echo '<button class="button button-small" title="Add one IP Trace link">+1 link</button></form> ';
        echo '<a class="button button-small button-primary" href="'.$view.'">Manage</a>';
        echo '</td></tr>';
    }
    echo '</tbody></table>';
}

/* ================================================================== admin: single client */
function iis_trace_admin_view_client(int $uid): void {
    global $wpdb; $t = iis_trace_tables();
    $u = get_userdata($uid);
    if (!$u) { echo '<p>Unknown client.</p>'; return; }
    $case = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['cases']} WHERE user_id=%d", $uid));
    $q = iis_trace_get_quota($uid);
    $links = $wpdb->get_results($wpdb->prepare("SELECT * FROM {$t['links']} WHERE user_id=%d ORDER BY created_at DESC", $uid));

    echo '<p><a href="'.esc_url(admin_url('admin.php?page=iis-trace&tab=clients')).'">&larr; All clients</a></p>';
    echo '<h2>'.esc_html($u->display_name ?: $u->user_login).' <span style="color:#787c82;font-weight:400;">'.esc_html($u->user_email).'</span></h2>';
    echo '<p>Case <strong>'.esc_html($case->case_ref ?? '—').'</strong> · IP Trace quota: '.esc_html($q['has_unlimited'] ? 'Unlimited'.($q['unlimited_expires']?' to '.date('M j, Y', strtotime($q['unlimited_expires'])):'') : ($q['used'].' / '.$q['total'].' used')).' · '
       . '<a class="button button-small" href="'.esc_url(wp_nonce_url(admin_url('admin-post.php?action=iis_trace_export_case&user_id='.$uid), 'iis_trace_export_case')).'">Export clicks (CSV)</a> '
       . '<a class="button button-small button-primary" target="_blank" href="'.esc_url(wp_nonce_url(admin_url('admin-post.php?action=iis_trace_report&user_id='.$uid), 'iis_trace_report')).'">Evidence report (print → PDF)</a></p>';

    // ── Case lifecycle editor ──
    $cstatus = $case->status ?? 'active';
    echo '<div style="background:#fff;border:1px solid #dcdcde;border-radius:8px;padding:16px;margin:12px 0;">';
    echo '<form method="post" action="'.esc_url(admin_url('admin-post.php')).'">';
    wp_nonce_field('iis_trace_save_case');
    echo '<input type="hidden" name="action" value="iis_trace_save_case"><input type="hidden" name="user_id" value="'.$uid.'">';
    echo '<div style="display:flex;gap:16px;flex-wrap:wrap;align-items:flex-end;">';
    echo '<label>Case label<br><input type="text" name="case_label" value="'.esc_attr($case->label ?? '').'" class="regular-text" placeholder="e.g. Facebook harassment — Jane Doe"></label>';
    echo '<label>Status<br><select name="case_status">';
    foreach (['active'=>'Active','on_hold'=>'On hold','closed'=>'Closed'] as $sv=>$sl) echo '<option value="'.$sv.'" '.selected($cstatus,$sv,false).'>'.$sl.'</option>';
    echo '</select></label>';
    echo '</div>';
    echo '<p style="margin:12px 0 4px;"><label>Case notes (internal)<br><textarea name="case_notes" rows="4" class="large-text" placeholder="Investigator notes, context, actions taken…">'.esc_textarea($case->notes ?? '').'</textarea></label></p>';
    echo '<button class="button button-primary">Save case</button>';
    echo '</form></div>';

    // ── NationalTrace section (same client, both services in one place) ──
    $nt_hist = iis_trace_nt_history($uid);
    $plans = get_option('nt_plans', []);
    echo '<h3>NationalTrace</h3>';
    if (!$nt_hist) {
        echo '<p style="color:#787c82;">No NationalTrace subscription. <a href="'.esc_url(admin_url('admin.php?page=nationaltrace-subscriptions')).'">Open NationalTrace admin →</a></p>';
    } else {
        echo '<table class="wp-list-table widefat striped" style="margin-bottom:8px;"><thead><tr><th>Plan</th><th>Status</th><th>Method</th><th>Start</th><th>Expiry</th><th>Reference</th></tr></thead><tbody>';
        foreach ($nt_hist as $sub) {
            $pl = $plans[$sub->plan]['label'] ?? $sub->plan;
            $col = $sub->status === 'active' ? '#00844a' : ($sub->status === 'pending' ? '#b26200' : '#787c82');
            echo '<tr><td>'.esc_html($pl).'</td><td style="color:'.$col.';font-weight:600;">'.esc_html(ucfirst($sub->status)).'</td>'
               . '<td>'.esc_html($sub->payment_method).'</td><td>'.esc_html($sub->start_date ?: '—').'</td><td>'.esc_html($sub->expiry_date ?: '—').'</td>'
               . '<td style="color:#787c82;word-break:break-all;">'.esc_html($sub->mmg_reference ?: '—').'</td></tr>';
        }
        echo '</tbody></table>';
        echo '<p><a class="button button-small" href="'.esc_url(admin_url('admin.php?page=nationaltrace-subscriptions&filter=pending')).'">Manage NationalTrace subscriptions →</a></p>';
    }

    echo '<h3 style="margin-top:24px;">IP Trace</h3>';

    // quota controls
    echo '<div style="background:#fff;border:1px solid #dcdcde;border-radius:8px;padding:14px 16px;margin:12px 0;display:flex;gap:18px;flex-wrap:wrap;align-items:flex-end;">';
    echo '<form method="post" action="'.esc_url(admin_url('admin-post.php')).'">';
    wp_nonce_field('iis_trace_grant_links');
    echo '<input type="hidden" name="action" value="iis_trace_grant_links"><input type="hidden" name="user_id" value="'.$uid.'">';
    echo '<label>Add links<br><input type="number" name="count" value="1" min="1" max="1000" style="width:80px;"></label> <button class="button">Grant links</button></form>';
    echo '<form method="post" action="'.esc_url(admin_url('admin-post.php')).'">';
    wp_nonce_field('iis_trace_grant_unlimited');
    echo '<input type="hidden" name="action" value="iis_trace_grant_unlimited"><input type="hidden" name="user_id" value="'.$uid.'">';
    echo '<label>Unlimited months<br><input type="number" name="months" value="12" min="1" max="60" style="width:80px;"></label> <button class="button">Grant unlimited</button></form>';
    if ($q['has_unlimited']) {
        echo '<form method="post" action="'.esc_url(admin_url('admin-post.php')).'" onsubmit="return confirm(\'Revoke unlimited?\');">';
        wp_nonce_field('iis_trace_revoke_unlimited');
        echo '<input type="hidden" name="action" value="iis_trace_revoke_unlimited"><input type="hidden" name="user_id" value="'.$uid.'">';
        echo '<button class="button">Revoke unlimited</button></form>';
    }
    echo '</div>';

    echo '<h3>Links &amp; clicks</h3>';
    if (!$links) { echo '<p>No links yet.</p>'; return; }
    foreach ($links as $link) {
        $clicks = $wpdb->get_results($wpdb->prepare("SELECT * FROM {$t['events']} WHERE link_id=%d ORDER BY clicked_at DESC", $link->id));
        echo '<div style="background:#fff;border:1px solid #dcdcde;border-radius:8px;padding:14px 16px;margin-bottom:16px;">';
        echo '<div style="display:flex;justify-content:space-between;flex-wrap:wrap;gap:8px;">';
        echo '<div><code>https://iis.gy/t/'.esc_html($link->token).'</code>'.($link->label?' — '.esc_html($link->label):'').'<br><span style="color:#787c82;">&rarr; '.esc_html($link->destination).' · '.(int)$link->clicks.' clicks</span></div>';
        echo '<div style="display:flex;gap:6px;">';
        // clear clicks only
        echo '<form method="post" action="'.esc_url(admin_url('admin-post.php')).'" onsubmit="return confirm(\'Clear ALL click records for this link? The link stays active.\');">';
        wp_nonce_field('iis_trace_clear_link_clicks');
        echo '<input type="hidden" name="action" value="iis_trace_clear_link_clicks"><input type="hidden" name="link_id" value="'.(int)$link->id.'"><input type="hidden" name="view_user" value="'.$uid.'">';
        echo '<button class="button button-small">Clear clicks</button></form>';
        // delete whole link
        echo '<form method="post" action="'.esc_url(admin_url('admin-post.php')).'" onsubmit="return confirm(\'Delete this link and its click log?\');">';
        wp_nonce_field('iis_trace_delete_link');
        echo '<input type="hidden" name="action" value="iis_trace_delete_link"><input type="hidden" name="link_id" value="'.(int)$link->id.'">';
        echo '<button class="button button-small button-link-delete">Delete link</button></form>';
        echo '</div>';
        echo '</div>';
        if ($clicks) {
            echo '<table class="wp-list-table widefat striped" style="margin-top:10px;"><thead><tr><th>Time (UTC)</th><th>IP</th><th>Location</th><th>Source</th><th>Flags</th><th>Device / TZ</th><th></th></tr></thead><tbody>';
            foreach ($clicks as $c) {
                $m = json_decode($c->meta ?: '{}', true); if(!is_array($m)) $m=[]; $geo=$m['geo']??[]; $cli=$m['client']??[];
                $loc = trim(implode(', ', array_filter([$c->city,$c->region,$c->country])));
                if ($c->precise_lat && $c->precise_lon) $loc .= ' · <a href="https://www.google.com/maps?q='.$c->precise_lat.','.$c->precise_lon.'" target="_blank">📍 GPS'.($c->precise_accuracy?' ±'.round($c->precise_accuracy).'m':'').'</a>';
                $flags = [];
                if (!empty($geo['proxy'])) $flags[]='<span style="color:#b32d2e;">PROXY/VPN</span>';
                if (!empty($geo['hosting'])) $flags[]='<span style="color:#b32d2e;">HOSTING</span>';
                if (!empty($geo['mobile'])) $flags[]='MOBILE';
                echo '<tr><td>'.esc_html($c->clicked_at).'</td><td>'.esc_html($c->ip).'</td><td>'.wp_kses_post($loc?:'—').'</td>'
                   . '<td>'.esc_html(strtoupper($c->location_source)).'</td><td>'.wp_kses_post($flags?implode(', ',$flags):'—').'</td>'
                   . '<td>'.esc_html(($cli['platform']??'').' '.($cli['timezone']??'')).'</td>'
                   . '<td><form method="post" action="'.esc_url(admin_url('admin-post.php')).'" onsubmit="return confirm(\'Delete this single click record?\');">'
                   . wp_nonce_field('iis_trace_delete_event', '_wpnonce', true, false)
                   . '<input type="hidden" name="action" value="iis_trace_delete_event"><input type="hidden" name="event_id" value="'.(int)$c->id.'"><input type="hidden" name="view_user" value="'.$uid.'">'
                   . '<button class="button-link" style="color:#b32d2e;" title="Delete this click">✕</button></form></td></tr>';
            }
            echo '</tbody></table>';
        } else {
            echo '<p style="color:#787c82;margin-top:8px;">No clicks yet.</p>';
        }
        echo '</div>';
    }

    // ── Danger zone: clear this client's data at will ──
    echo '<h3 style="margin-top:28px;color:#b32d2e;">Danger zone</h3>';
    echo '<div style="background:#fcf0f1;border:1px solid #f0c7c9;border-radius:8px;padding:16px;display:flex;gap:12px;flex-wrap:wrap;">';
    echo '<form method="post" action="'.esc_url(admin_url('admin-post.php')).'" onsubmit="return confirm(\'Delete ALL IP Trace data (case, links, every click, quota and payment history) for this client? This cannot be undone.\');">';
    wp_nonce_field('iis_trace_purge_client');
    echo '<input type="hidden" name="action" value="iis_trace_purge_client"><input type="hidden" name="user_id" value="'.$uid.'"><input type="hidden" name="scope" value="iptrace">';
    echo '<button class="button button-link-delete">Clear all IP Trace data</button></form>';
    echo '<form method="post" action="'.esc_url(admin_url('admin-post.php')).'" onsubmit="return confirm(\'Delete ALL IP Trace AND NationalTrace data for this client? This removes subscriptions, links, clicks and history. This cannot be undone.\');">';
    wp_nonce_field('iis_trace_purge_client');
    echo '<input type="hidden" name="action" value="iis_trace_purge_client"><input type="hidden" name="user_id" value="'.$uid.'"><input type="hidden" name="scope" value="all">';
    echo '<button class="button button-link-delete"><strong>Clear ALL data (IP Trace + NationalTrace)</strong></button></form>';
    echo '<p style="color:#787c82;margin:0;flex-basis:100%;font-size:12px;">The WordPress account itself is not deleted. Use Users → Delete to remove the account.</p>';
    echo '</div>';
}

/* ================================================================== admin: Links */
function iis_trace_admin_tab_links(): void {
    global $wpdb; $t = iis_trace_tables();
    $rows = $wpdb->get_results("SELECT l.*, u.user_login, u.display_name FROM {$t['links']} l LEFT JOIN {$wpdb->users} u ON l.user_id=u.ID ORDER BY l.created_at DESC LIMIT 500");
    echo '<table class="wp-list-table widefat fixed striped"><thead><tr><th>Link</th><th>Owner</th><th>Destination</th><th>Clicks</th><th>Created</th><th></th></tr></thead><tbody>';
    if (!$rows) echo '<tr><td colspan="6">No links yet.</td></tr>';
    foreach ($rows as $l) {
        echo '<tr>';
        echo '<td><code>/t/'.esc_html($l->token).'</code>'.($l->label?'<br><span style="color:#787c82;">'.esc_html($l->label).'</span>':'').'</td>';
        echo '<td><a href="'.esc_url(admin_url('admin.php?page=iis-trace&view='.(int)$l->user_id)).'">'.esc_html($l->display_name ?: $l->user_login).'</a></td>';
        echo '<td style="word-break:break-all;">'.esc_html($l->destination).'</td>';
        echo '<td>'.(int)$l->clicks.'</td><td>'.esc_html($l->created_at).'</td>';
        echo '<td><form method="post" action="'.esc_url(admin_url('admin-post.php')).'" onsubmit="return confirm(\'Delete this link and its click log?\');">';
        wp_nonce_field('iis_trace_delete_link');
        echo '<input type="hidden" name="action" value="iis_trace_delete_link"><input type="hidden" name="link_id" value="'.(int)$l->id.'">';
        echo '<button class="button button-small button-link-delete">Delete</button></form></td>';
        echo '</tr>';
    }
    echo '</tbody></table>';
}

/* ================================================================== admin: Clicks */
function iis_trace_admin_tab_clicks(): void {
    global $wpdb; $t = iis_trace_tables();
    $f_ip      = sanitize_text_field($_GET['f_ip'] ?? '');
    $f_country = sanitize_text_field($_GET['f_country'] ?? '');
    $f_from    = sanitize_text_field($_GET['f_from'] ?? '');
    $f_to      = sanitize_text_field($_GET['f_to'] ?? '');
    $f_vpn     = !empty($_GET['f_vpn']);
    $f_gps     = !empty($_GET['f_gps']);

    // Filter bar
    echo '<form method="get" style="background:#fff;border:1px solid #dcdcde;border-radius:8px;padding:12px 14px;margin-bottom:14px;display:flex;gap:10px;flex-wrap:wrap;align-items:flex-end;">';
    echo '<input type="hidden" name="page" value="iis-trace"><input type="hidden" name="tab" value="clicks">';
    echo '<label>IP contains<br><input type="text" name="f_ip" value="'.esc_attr($f_ip).'"></label>';
    echo '<label>Country<br><input type="text" name="f_country" value="'.esc_attr($f_country).'"></label>';
    echo '<label>From<br><input type="date" name="f_from" value="'.esc_attr($f_from).'"></label>';
    echo '<label>To<br><input type="date" name="f_to" value="'.esc_attr($f_to).'"></label>';
    echo '<label><input type="checkbox" name="f_vpn" value="1" '.checked($f_vpn,true,false).'> Proxy/VPN only</label>';
    echo '<label><input type="checkbox" name="f_gps" value="1" '.checked($f_gps,true,false).'> GPS only</label>';
    echo '<button class="button button-primary">Filter</button>';
    echo '<a class="button" href="'.esc_url(admin_url('admin.php?page=iis-trace&tab=clicks')).'">Reset</a>';
    echo '</form>';

    $where = ['1=1']; $args = [];
    if ($f_ip !== '')      { $where[] = 'e.ip LIKE %s'; $args[] = '%'.$wpdb->esc_like($f_ip).'%'; }
    if ($f_country !== '') { $where[] = 'e.country LIKE %s'; $args[] = '%'.$wpdb->esc_like($f_country).'%'; }
    if ($f_from !== '')    { $where[] = 'e.clicked_at >= %s'; $args[] = $f_from.' 00:00:00'; }
    if ($f_to !== '')      { $where[] = 'e.clicked_at <= %s'; $args[] = $f_to.' 23:59:59'; }
    if ($f_vpn)            { $where[] = "e.meta LIKE '%\"proxy\":true%'"; }
    if ($f_gps)            { $where[] = "e.location_source = 'gps'"; }
    $wsql = implode(' AND ', $where);
    $sql = "SELECT e.*, l.token, l.user_id, u.user_login, u.display_name
            FROM {$t['events']} e JOIN {$t['links']} l ON e.link_id=l.id LEFT JOIN {$wpdb->users} u ON l.user_id=u.ID
            WHERE {$wsql} ORDER BY e.clicked_at DESC LIMIT 500";
    $rows = $args ? $wpdb->get_results($wpdb->prepare($sql, $args)) : $wpdb->get_results($sql);
    echo '<p style="color:#646970;">'.count($rows).' matching click(s).</p>';
    iis_trace_admin_render_click_rows($rows);
}
function iis_trace_admin_recent_clicks(int $limit): void {
    global $wpdb; $t = iis_trace_tables();
    $rows = $wpdb->get_results($wpdb->prepare(
        "SELECT e.*, l.token, l.user_id, u.user_login, u.display_name
         FROM {$t['events']} e JOIN {$t['links']} l ON e.link_id=l.id LEFT JOIN {$wpdb->users} u ON l.user_id=u.ID
         ORDER BY e.clicked_at DESC LIMIT %d", $limit));
    iis_trace_admin_render_click_rows($rows);
}
function iis_trace_admin_render_click_rows(array $rows): void {
    echo '<table class="wp-list-table widefat fixed striped"><thead><tr><th>Time (UTC)</th><th>Client</th><th>IP</th><th>Location</th><th>Source</th><th>Flags</th><th>Device</th></tr></thead><tbody>';
    if (!$rows) echo '<tr><td colspan="7">No clicks match.</td></tr>';
    foreach ($rows as $c) {
        $m = json_decode($c->meta ?: '{}', true); if(!is_array($m)) $m=[]; $geo=$m['geo']??[]; $cli=$m['client']??[];
        $loc = trim(implode(', ', array_filter([$c->city,$c->region,$c->country])));
        if ($c->precise_lat && $c->precise_lon) $loc .= ' · <a href="https://www.google.com/maps?q='.$c->precise_lat.','.$c->precise_lon.'" target="_blank">📍</a>';
        $flags=[];
        if (!empty($geo['proxy'])) $flags[]='<span style="color:#b32d2e;">VPN</span>';
        if (!empty($geo['hosting'])) $flags[]='<span style="color:#b32d2e;">HOST</span>';
        if (!empty($geo['mobile'])) $flags[]='MOB';
        echo '<tr><td>'.esc_html($c->clicked_at).'</td>'
           . '<td><a href="'.esc_url(admin_url('admin.php?page=iis-trace&view='.(int)$c->user_id)).'">'.esc_html($c->display_name ?: $c->user_login).'</a></td>'
           . '<td>'.esc_html($c->ip).'</td><td>'.wp_kses_post($loc?:'—').'</td>'
           . '<td>'.esc_html(strtoupper($c->location_source)).'</td><td>'.wp_kses_post($flags?implode(' ',$flags):'—').'</td>'
           . '<td style="color:#787c82;">'.esc_html(substr(($cli['platform']??'').' '.$c->user_agent,0,50)).'</td></tr>';
    }
    echo '</tbody></table>';
}

/* ================================================================== admin: Audit + Settings + Report */
function iis_trace_admin_tab_audit(): void {
    global $wpdb; $t = iis_trace_tables();
    $rows = $wpdb->get_results("SELECT a.*, u.user_login FROM {$t['audit']} a LEFT JOIN {$wpdb->users} u ON a.admin_id=u.ID ORDER BY a.id DESC LIMIT 500");
    echo '<p style="color:#646970;">Every administrative change (grants, deletions, purges, settings) is logged here.</p>';
    echo '<table class="wp-list-table widefat fixed striped"><thead><tr><th>When (UTC)</th><th>Admin</th><th>Action</th><th>Target</th><th>Detail</th></tr></thead><tbody>';
    if (!$rows) echo '<tr><td colspan="5">No admin actions logged yet.</td></tr>';
    foreach ($rows as $r) {
        echo '<tr><td>'.esc_html($r->created_at).'</td><td>'.esc_html($r->user_login ?: ('#'.$r->admin_id)).'</td>'
           . '<td><code>'.esc_html($r->action).'</code></td><td>'.esc_html($r->target).'</td><td style="color:#787c82;">'.esc_html($r->detail).'</td></tr>';
    }
    echo '</tbody></table>';
}

function iis_trace_admin_tab_settings(): void {
    echo '<form method="post" action="'.esc_url(admin_url('admin-post.php')).'" style="max-width:720px;">';
    wp_nonce_field('iis_trace_save_settings');
    echo '<input type="hidden" name="action" value="iis_trace_save_settings">';
    echo '<h3>Destination blocklist</h3>';
    echo '<p class="description">One substring per line. Any tracking-link destination containing one of these is rejected (malware/phishing domains, etc.). Private/loopback hosts and iis.gy are always blocked.</p>';
    echo '<textarea name="blocklist" rows="6" class="large-text code">'.esc_textarea(get_option('iis_trace_blocklist','')).'</textarea>';
    echo '<p style="margin-top:16px;"><button class="button button-primary">Save settings</button></p>';
    echo '</form>';
    echo '<hr><p style="color:#787c82;">Clients are notified by email on a link\'s first click and on precise-GPS captures. Link creation is rate-limited to '.esc_html(IIS_TRACE_RATE_PER_HOUR).' new links per client per hour.</p>';
}

/** Branded, print-to-PDF evidence report for a client's case. */
function iis_trace_render_evidence_report(int $uid): void {
    global $wpdb; $t = iis_trace_tables();
    $u = get_userdata($uid);
    $case = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$t['cases']} WHERE user_id=%d", $uid));
    $rows = $wpdb->get_results($wpdb->prepare(
        "SELECT e.*, l.token, l.destination, l.label FROM {$t['events']} e JOIN {$t['links']} l ON e.link_id=l.id WHERE l.user_id=%d ORDER BY e.clicked_at DESC", $uid));
    $gen = current_time('mysql');
    $admin = wp_get_current_user();
    ?><!DOCTYPE html><html><head><meta charset="utf-8"><title>IP Trace Evidence Report<?php echo $case ? ' — '.esc_html($case->case_ref) : ''; ?></title>
    <style>
        @page { margin: 18mm; }
        body { font-family: 'Segoe UI', Arial, sans-serif; color: #111; font-size: 12px; line-height: 1.5; }
        .head { display:flex; justify-content:space-between; border-bottom:3px solid #c9a227; padding-bottom:10px; margin-bottom:16px; }
        .brand { font-size:20px; font-weight:800; color:#0a0a0a; }
        .brand span { color:#c9a227; }
        .muted { color:#555; font-size:11px; }
        h1 { font-size:16px; margin:18px 0 6px; }
        h2 { font-size:13px; margin:18px 0 6px; border-bottom:1px solid #ccc; padding-bottom:3px; }
        table { width:100%; border-collapse:collapse; margin:8px 0 16px; }
        th,td { border:1px solid #ccc; padding:6px 8px; text-align:left; vertical-align:top; font-size:11px; }
        th { background:#f3f1ea; }
        .evt { page-break-inside:avoid; }
        .flag { color:#b32d2e; font-weight:700; }
        .btns { margin:12px 0 20px; }
        .btns button { background:#c9a227; border:0; color:#0a0a0a; padding:8px 16px; font-weight:700; border-radius:4px; cursor:pointer; }
        @media print { .btns { display:none; } }
        .foot { margin-top:24px; border-top:1px solid #ccc; padding-top:8px; color:#555; font-size:10px; }
    </style></head><body>
    <div class="btns"><button onclick="window.print()">Print / Save as PDF</button></div>
    <div class="head">
        <div><div class="brand">IIS<span>.</span> Investigative Intelligence Solutions Inc.</div>
            <div class="muted">Licensed Private Investigation Firm · Reg. No. 14926 · Georgetown, Guyana · iis.gy · contact@iis.gy · +592-623-1721</div></div>
        <div class="muted" style="text-align:right;">Generated<br><?php echo esc_html($gen); ?> UTC<br>by <?php echo esc_html($admin->display_name); ?></div>
    </div>
    <h1>IP Trace Evidence Report</h1>
    <table>
        <tr><th style="width:160px;">Case reference</th><td><?php echo esc_html($case->case_ref ?? '—'); ?></td></tr>
        <?php if (!empty($case->label)): ?><tr><th>Case label</th><td><?php echo esc_html($case->label); ?></td></tr><?php endif; ?>
        <tr><th>Client</th><td><?php echo esc_html(($u->display_name ?? '') . ' (' . ($u->user_email ?? '') . ')'); ?></td></tr>
        <tr><th>Status</th><td><?php echo esc_html(ucfirst($case->status ?? 'active')); ?></td></tr>
        <tr><th>Total clicks</th><td><?php echo count($rows); ?></td></tr>
    </table>
    <?php if (!empty($case->notes)): ?><h2>Case notes</h2><p><?php echo nl2br(esc_html($case->notes)); ?></p><?php endif; ?>
    <h2>Recorded access events</h2>
    <?php if (!$rows): ?><p>No click events recorded.</p><?php else: $i=0; foreach ($rows as $r): $i++;
        $m = json_decode($r->meta ?: '{}', true); if(!is_array($m)) $m=[]; $geo=$m['geo']??[]; $cli=$m['client']??[]; ?>
        <div class="evt"><h2>Event #<?php echo $i; ?> — <?php echo esc_html($r->clicked_at); ?> UTC</h2>
        <table>
            <tr><th style="width:160px;">Tracking link</th><td>/t/<?php echo esc_html($r->token); ?><?php echo $r->label?' ('.esc_html($r->label).')':''; ?> → <?php echo esc_html($r->destination); ?></td></tr>
            <tr><th>IP address</th><td><?php echo esc_html($r->ip); ?><?php echo !empty($m['ip_chain'])&&$m['ip_chain']!==$r->ip?' (chain: '.esc_html($m['ip_chain']).')':''; ?></td></tr>
            <tr><th>Approx. location (IP)</th><td><?php echo esc_html(trim(implode(', ', array_filter([$r->city,$r->region,$r->country,$geo['zip']??''])))); ?></td></tr>
            <?php if ($r->precise_lat && $r->precise_lon): ?>
            <tr><th>Precise GPS</th><td><?php echo esc_html($r->precise_lat.', '.$r->precise_lon); ?><?php echo $r->precise_accuracy?' (±'.round($r->precise_accuracy).' m)':''; ?> — https://www.google.com/maps?q=<?php echo esc_html($r->precise_lat.','.$r->precise_lon); ?></td></tr>
            <?php endif; ?>
            <tr><th>ISP / network</th><td><?php echo esc_html($r->isp.' '.(!empty($geo['as'])?'· '.$geo['as']:'')); ?>
                <?php if(!empty($geo['proxy'])) echo ' <span class="flag">[PROXY/VPN]</span>'; if(!empty($geo['hosting'])) echo ' <span class="flag">[HOSTING]</span>'; if(!empty($geo['mobile'])) echo ' [MOBILE]'; ?></td></tr>
            <tr><th>Timezone</th><td>IP: <?php echo esc_html($geo['timezone']??'—'); ?> · Device: <?php echo esc_html($cli['timezone']??'—'); ?></td></tr>
            <tr><th>Device</th><td><?php echo esc_html(trim(($cli['ua_data']['platform']??$cli['platform']??'').' '.($cli['ua_data']['model']??'').' · '.($cli['screen']??''))); ?></td></tr>
            <tr><th>User agent</th><td style="font-size:10px;"><?php echo esc_html($r->user_agent); ?></td></tr>
            <tr><th>Referrer</th><td><?php echo esc_html($r->referrer ?: 'Direct'); ?></td></tr>
        </table></div>
    <?php endforeach; endif; ?>
    <div class="foot">This report was generated by the IIS IP Trace system from data recorded automatically when the tracking link(s) above were opened. IP-based locations are approximate; precise GPS coordinates were provided by the visitor's device only where the visitor granted browser location permission. Prepared for investigative and law-enforcement purposes.</div>
    </body></html><?php
}

/* ================================================================== admin: Payments */
function iis_trace_admin_tab_payments(): void {
    global $wpdb;
    $rows = $wpdb->get_results("SELECT user_id, meta_value FROM {$wpdb->usermeta} WHERE meta_key='iis_trace_payment_log'");
    $pays = [];
    foreach ($rows as $r) {
        foreach ((array) maybe_unserialize($r->meta_value) as $p) { $p['user_id'] = (int)$r->user_id; $pays[] = $p; }
    }
    usort($pays, fn($a,$b)=>strcmp($b['at']??'', $a['at']??''));
    $total = array_sum(array_map(fn($p)=>(int)($p['usd']??0), $pays));
    iis_trace_admin_stat_cards([
        ['Payments', number_format(count($pays))],
        ['Revenue', '$' . number_format($total)],
    ]);
    echo '<table class="wp-list-table widefat fixed striped"><thead><tr><th>When</th><th>Client</th><th>Item</th><th>Method</th><th>Amount</th><th>Reference</th></tr></thead><tbody>';
    if (!$pays) echo '<tr><td colspan="6">No payments recorded yet.</td></tr>';
    foreach ($pays as $p) {
        $u = get_userdata($p['user_id']);
        echo '<tr><td>'.esc_html($p['at']??'').'</td>'
           . '<td><a href="'.esc_url(admin_url('admin.php?page=iis-trace&view='.$p['user_id'])).'">'.esc_html($u ? ($u->display_name?:$u->user_login) : ('#'.$p['user_id'])).'</a></td>'
           . '<td>'.esc_html(($p['type']??'')==='unlimited' ? 'Unlimited (1yr)' : 'Single link').'</td>'
           . '<td>'.esc_html(strtoupper($p['method']??'')).'</td>'
           . '<td>$'.esc_html((string)($p['usd']??0)).' / GYD '.esc_html(number_format((int)($p['gyd']??0))).'</td>'
           . '<td style="color:#787c82;word-break:break-all;">'.esc_html($p['ref']??'').'</td></tr>';
    }
    echo '</tbody></table>';
}
