<?php
/**
 * Shop2LLM Activation Doctor
 *
 * WP-CLI diagnostic that prints the activation funnel for the current site,
 * identifies the bottleneck step, and suggests concrete fixes.
 *
 * Usage (from WordPress root):
 *   wp eval-file wp-content/plugins/shop-to-llm/tools/activation-doctor.php
 *
 * No PII is ever printed. The Site Hash is SHA-256 of the hostname and is
 * safe to share with support@shop2llm.com.
 *
 * @package Shop2LLM
 * @since 1.3.2
 */

// phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped -- CLI diagnostic, output to stdout, not HTML.
// phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_init,WordPress.WP.AlternativeFunctions.curl_curl_setopt_array,WordPress.WP.AlternativeFunctions.curl_curl_exec,WordPress.WP.AlternativeFunctions.curl_curl_getinfo,WordPress.WP.AlternativeFunctions.curl_curl_error,WordPress.WP.AlternativeFunctions.curl_curl_close
// cURL is used instead of wp_remote_get() because WordPress's WP_Http applies
// http_request_args filters that rewrite the request host to site_url's host:port.
// Inside Docker containers (where Apache listens on :80 but site_url may be
// localhost:8081), wp_remote_get() fails. cURL bypasses the filters and reaches
// the local server directly. See shop2llm_doctor_http_get() docblock for details.

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

/**
 * Render a human-readable duration between two unix timestamps.
 */
function shop2llm_doctor_format_gap(int $fromTs, int $toTs): string
{
    $delta = $toTs - $fromTs;
    if ($delta < 0) {
        return 'negative (-' . gmdate('H:i:s', abs($delta)) . ')';
    }
    if ($delta < 60) {
        return $delta . 's';
    }
    if ($delta < 3600) {
        return floor($delta / 60) . 'm ' . ($delta % 60) . 's';
    }
    if ($delta < 86400) {
        return floor($delta / 3600) . 'h ' . floor(($delta % 3600) / 60) . 'm';
    }
    return floor($delta / 86400) . 'd ' . floor(($delta % 86400) / 3600) . 'h';
}

/**
 * Print a single funnel step row.
 */
function shop2llm_doctor_print_step(string $label, ?int $ts, ?int $prevTs, string $threshold, string $fixHint): void
{
    if ($ts === null || $ts === 0) {
        printf("  %-22s  ✗ NOT REACHED                          —\n", $label);
        printf("      ↳ Suggested fix: %s\n", $fixHint);
        return;
    }
    $date = gmdate('Y-m-d H:i:s', $ts);
    $gapStr = $prevTs ? shop2llm_doctor_format_gap($prevTs, $ts) : '—';
    printf("  %-22s  ✓ %s  gap=%-12s\n", $label, $date, $gapStr);
}

/**
 * Bare-bones HTTP GET via PHP cURL.
 *
 * Why not wp_remote_get(): WordPress's WP_Http applies filters (http_request_url,
 * http_request_args) that may rewrite the host to site_url's host:port. In a
 * Docker container where site_url is localhost:8081 (host-side port mapping),
 * wp_remote_get("http://127.0.0.1/...") gets rewritten to localhost:8081 and
 * fails inside the container (where Apache listens on :80). cURL bypasses all
 * of that and hits the URL we give it.
 *
 * Why a custom Host header: WordPress's redirect_canonical() compares the
 * request's Host header to site_url's host. When they differ, it 301-redirects
 * to the canonical site_url — which inside the container points at the
 * host-side port (8081) that is unreachable from inside the container. By
 * sending the site_url host as the Host header, we tell WordPress "this *is*
 * the canonical request" and skip the redirect.
 *
 * @return array{code: int, body: string, error: string}
 */
function shop2llm_doctor_http_get(string $url, string $hostHeader = ''): array
{
    if (!function_exists('curl_init')) {
        return ['code' => 0, 'body' => '', 'error' => 'PHP cURL extension not loaded'];
    }
    $ch = curl_init($url);
    $opts = [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 5,
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_SSL_VERIFYHOST => 0,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_MAXREDIRS      => 3,
        CURLOPT_USERAGENT      => 'Shop2LLM-Activation-Doctor/1.0',
    ];
    if ($hostHeader !== '') {
        $opts[CURLOPT_HTTPHEADER] = ['Host: ' . $hostHeader];
    }
    curl_setopt_array($ch, $opts);
    $body  = (string) curl_exec($ch);
    $code  = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $error = curl_error($ch);
    curl_close($ch);
    if ($error && $code === 0) {
        return ['code' => 0, 'body' => '', 'error' => $error];
    }
    return ['code' => $code, 'body' => $body, 'error' => ''];
}

// ─── Collect data ────────────────────────────────────────────────────

$summary = get_option('shop2llm_behavior_summary', []);
if (!is_array($summary)) {
    $summary = [];
}

$activated           = (int) ($summary['activated'] ?? 0);
$welcomeShown        = (int) ($summary['welcome_shown'] ?? 0);
$welcomeCompleted    = (int) ($summary['welcome_completed'] ?? 0);
$welcomeSkippedStep  = $summary['welcome_skipped_step'] ?? null;
$urlCopied           = (int) ($summary['url_copied'] ?? 0);
$firstSearch         = (int) ($summary['first_search'] ?? 0);
$firstSearchOk       = !empty($summary['first_search_ok']);
$dashboardVisits     = (int) ($summary['dashboard_visits'] ?? 0);

// first_crawler is stored as a separate option (set when a real crawler hits the site)
$firstCrawlerTime    = (int) get_option('shop_to_llm_first_crawler_time', 0);
$firstCrawlerPlatform = (string) get_option('shop_to_llm_first_crawler_platform', '');

// Plugin environment
$pluginVersion   = defined('SHOP_TO_LLM_VERSION') ? SHOP_TO_LLM_VERSION : get_file_data(__DIR__ . '/../shop-to-llm.php', ['Version'])[0];
$wpVersion       = get_bloginfo('version');
$phpVersion      = PHP_VERSION;
$hasWoo          = class_exists('WooCommerce') ? 'yes (' . WC()->version . ')' : 'no';
$apiKey          = get_option('shop_to_llm_api_key', '') ? 'set' : 'MISSING';
$analyticsOn     = get_option('shop_to_llm_analytics', 'no') === 'yes' ? 'on' : 'off';
$seoAuditOn      = get_option('shop_to_llm_seo_audit', 'yes') === 'yes' ? 'on' : 'off';

// Site hash (same algorithm as the heartbeat payload)
$siteUrl  = get_site_url();
$host     = wp_parse_url($siteUrl, PHP_URL_HOST) ?: $siteUrl;
$siteHash = hash('sha256', $host);

// Container/Docker fallback: when running inside WP-CLI eval-file, the site URL
// stored in the database may point at host-side port mapping (e.g. localhost:8081)
// that is unreachable from inside the container (where Apache listens on :80).
// Rewrite to http://127.0.0.1 so wp_remote_get can actually reach the local server.
// On a real production server, site_url returns a publicly reachable URL and this
// block is a no-op.
$internalUrl = $siteUrl;
$siteHost    = $host; // Host header value to send with cURL requests.
if (defined('WP_CLI') && WP_CLI) {
    $port = (int) wp_parse_url($siteUrl, PHP_URL_PORT);
    if ($port > 0 && $port !== 80) {
        $path        = wp_parse_url($siteUrl, PHP_URL_PATH) ?: '';
        $internalUrl = 'http://127.0.0.1' . $path;
        // Preserve the original host:port for the Host header so WordPress's
        // redirect_canonical() sees a matching host and doesn't 301-redirect.
        $siteHost    = $host . ':' . $port;
    }
}

// Content counts
$postCount    = (int) (wp_count_posts('post')->publish ?? 0);
$productCount = 0;
if (class_exists('WooCommerce')) {
    $productCount = (int) (wp_count_posts('product')->publish ?? 0);
}

// ─── Print header ────────────────────────────────────────────────────

echo "\n";
echo "══════════════════════════════════════════════════════════════════════\n";
echo " Shop2LLM Activation Doctor  v1.0   (plugin v{$pluginVersion})\n";
echo "══════════════════════════════════════════════════════════════════════\n";
echo " Site URL          : {$siteUrl}  (do not share full URL publicly — share Site Hash below)\n";
echo " Site Hash         : {$siteHash}  (safe to share with support@shop2llm.com)\n";
echo " WP / PHP          : {$wpVersion} / {$phpVersion}\n";
echo " WooCommerce       : {$hasWoo}\n";
echo " Content (post+product): " . ($postCount + $productCount) . " ({$postCount} posts, {$productCount} products)\n";
echo " API key           : {$apiKey}\n";
echo " Analytics opt-in  : {$analyticsOn}\n";
echo " SEO audit         : {$seoAuditOn}\n";
echo " Dashboard visits  : {$dashboardVisits}\n";
if ($welcomeSkippedStep !== null) {
    echo " Welcome modal     : SKIPPED at step {$welcomeSkippedStep}\n";
}
echo "──────────────────────────────────────────────────────────────────────\n";
echo " Activation Funnel\n";
echo "──────────────────────────────────────────────────────────────────────\n";

shop2llm_doctor_print_step(
    'activated',
    $activated ?: null,
    null,
    '—',
    'Activate the plugin from Plugins → Installed Plugins.'
);

shop2llm_doctor_print_step(
    'welcome_shown',
    $welcomeShown ?: null,
    $activated ?: null,
    '5 min',
    'Open any wp-admin page; the Welcome Modal should auto-show. If a redirect plugin hijacks admin, deactivate it temporarily.'
);

shop2llm_doctor_print_step(
    'welcome_completed',
    $welcomeCompleted ?: null,
    $welcomeShown ?: null,
    '1 day',
    'Finish the 3-step Welcome Modal, or click Skip. If you skipped, re-open via Shop2LLM → Settings → Onboarding → Restart Onboarding.'
);

shop2llm_doctor_print_step(
    'url_copied',
    $urlCopied ?: null,
    $welcomeCompleted ?: $welcomeShown ?: null,
    '1 day',
    'Open Shop2LLM Dashboard → click "Copy AI Connection URL". The URL is yoursite.com/ai/v1.'
);

shop2llm_doctor_print_step(
    'first_search',
    $firstSearch ?: null,
    $urlCopied ?: null,
    '3 days',
    'Paste the URL into ChatGPT Desktop, then ask it to search your site. Or use the Hero Test Search in the dashboard (5 free/day, no API key needed).'
);

if ($firstSearch) {
    printf("      ↳ first_search result: %s\n", $firstSearchOk ? '✓ returned results' : '✗ returned empty (check content count or AI crawler access)');
}

shop2llm_doctor_print_step(
    'first_crawler_done',
    $firstCrawlerTime ?: null,
    $firstSearch ?: null,
    '7 days',
    'GPTBot/ClaudeBot haven\'t crawled yet. Check: (1) robots.txt has "Allow: /" for GPTBot/ClaudeBot; (2) Cloudflare Security → Bots → "AI Scrapers and Crawlers" is OFF; (3) site is on HTTPS with valid cert.'
);

if ($firstCrawlerTime && $firstCrawlerPlatform) {
    printf("      ↳ first crawler: %s at %s\n", $firstCrawlerPlatform, gmdate('Y-m-d H:i:s', $firstCrawlerTime));
}

// ─── Bottleneck analysis ─────────────────────────────────────────────

echo "──────────────────────────────────────────────────────────────────────\n";
echo " Bottleneck Analysis\n";
echo "──────────────────────────────────────────────────────────────────────\n";

$steps = [
    ['activated → welcome_shown', $activated, $welcomeShown, 300, 'Admin redirect hijack. Check for "redirection", "better-wp-security", or any plugin that hooks admin_init with wp_redirect.'],
    ['welcome_shown → welcome_completed', $welcomeShown, $welcomeCompleted, 86400, 'User abandoned the modal. Re-trigger via Shop2LLM → Settings → Onboarding → Restart Onboarding.'],
    ['welcome_completed → url_copied', $welcomeCompleted, $urlCopied, 86400, 'The "Copy" button is below the fold on small screens. Scroll the dashboard or visit Settings → Shop2LLM → Connection.'],
    ['url_copied → first_search', $urlCopied, $firstSearch, 259200, 'User hasn\'t pasted the URL into ChatGPT. Alternative: use Hero Test Search in the dashboard (no setup needed).'],
    ['first_search → first_crawler_done', $firstSearch, $firstCrawlerTime, 604800, 'AI bots can\'t reach the site. See robots.txt + Cloudflare checks below.'],
];

$worstStep = null;
$worstGap  = 0;
foreach ($steps as $step) {
    [$label, $from, $to, $threshold, $fix] = $step;
    if (!$from) {
        continue;
    }
    if (!$to) {
        printf("  ⚠ %-38s  PENDING (threshold %s)\n", $label, shop2llm_doctor_format_gap(0, $threshold));
        printf("      ↳ Fix: %s\n", $fix);
        continue;
    }
    $gap = $to - $from;
    $over = $gap > $threshold;
    if ($over && $gap > $worstGap) {
        $worstGap  = $gap;
        $worstStep = $label;
    }
    printf(
        "  %s %-34s  gap=%-12s  threshold=%s%s\n",
        $over ? '⚠' : '✓',
        $label,
        shop2llm_doctor_format_gap($from, $to),
        shop2llm_doctor_format_gap(0, $threshold),
        $over ? '  OVER' : ''
    );
    if ($over) {
        printf("      ↳ Fix: %s\n", $fix);
    }
}

if ($worstStep) {
    echo "\n  ➤ Primary bottleneck: {$worstStep}\n";
} elseif ($firstCrawlerTime) {
    echo "\n  ➤ Funnel complete. Site is fully onboarded.\n";
} else {
    echo "\n  ➤ Funnel incomplete — see PENDING steps above.\n";
}

// ─── Connectivity checks ─────────────────────────────────────────────

echo "──────────────────────────────────────────────────────────────────────\n";
echo " Connectivity Checks\n";
echo "──────────────────────────────────────────────────────────────────────\n";

// robots.txt
$robotsUrl = trailingslashit($internalUrl) . 'robots.txt';
$robotsResp = shop2llm_doctor_http_get($robotsUrl, $siteHost);
if ($robotsResp['error'] !== '' && $robotsResp['code'] === 0) {
    printf("  robots.txt        ✗ unreachable: %s\n", $robotsResp['error']);
} else {
    $body = $robotsResp['body'];
    $code = $robotsResp['code'];
    $hasGpt = stripos($body, 'GPTBot') !== false;
    $hasClaude = stripos($body, 'ClaudeBot') !== false;
    $hasPerplexity = stripos($body, 'PerplexityBot') !== false;
    printf(
        "  robots.txt        %s  HTTP %d  GPTBot=%s ClaudeBot=%s PerplexityBot=%s\n",
        $code === 200 ? '✓' : '⚠',
        $code,
        $hasGpt ? '✓' : '✗',
        $hasClaude ? '✓' : '✗',
        $hasPerplexity ? '✓' : '✗'
    );
    if (!$hasGpt || !$hasClaude) {
        echo "      ↳ Fix: Settings → Shop2LLM → ensure plugin is active. Cloudflare Managed robots.txt will override the local file — disable it in Cloudflare → Rules → Configuration Rules.\n";
    }
}

// llms.txt
// Note: shop-to-llm registers /llms.txt/ (with trailing slash) as a rewrite rule,
// so we request it with the trailing slash to avoid a 301 redirect.
$llmsUrl = trailingslashit($internalUrl) . 'llms.txt/';
$llmsResp = shop2llm_doctor_http_get($llmsUrl, $siteHost);
if ($llmsResp['error'] !== '' && $llmsResp['code'] === 0) {
    printf("  llms.txt          ✗ unreachable: %s\n", $llmsResp['error']);
} else {
    $code = $llmsResp['code'];
    $body = $llmsResp['body'];
    $lines = $body ? count(explode("\n", trim($body))) : 0;
    printf(
        "  llms.txt          %s  HTTP %d  %d lines\n",
        $code === 200 ? '✓' : '✗',
        $code,
        $lines
    );
    if ($code !== 200) {
        echo "      ↳ Fix: Permalinks need flushing. Run: wp rewrite flush. Also check that no security plugin blocks /llms.txt.\n";
    }
}

// MCP discovery endpoint
$mcpPath = '/wp-json/shop2llm/v1/.well-known/mcp';
$mcpUrl = trailingslashit($internalUrl) . ltrim($mcpPath, '/');
$mcpResp = shop2llm_doctor_http_get($mcpUrl, $siteHost);
if ($mcpResp['error'] !== '' && $mcpResp['code'] === 0) {
    printf("  MCP discovery     ✗ unreachable: %s\n", $mcpResp['error']);
} else {
    $code = $mcpResp['code'];
    printf(
        "  MCP discovery     %s  HTTP %d  %s\n",
        $code === 200 ? '✓' : '✗',
        $code,
        $mcpUrl
    );
    if ($code !== 200) {
        echo "      ↳ Fix: WP REST API may be blocked by a security plugin. Add /wp-json/shop2llm/v1/ to the allowlist.\n";
    }
}

// ai/v1 permalink (AI connection URL)
$aiUrl = trailingslashit($internalUrl) . 'ai/v1';
$aiResp = shop2llm_doctor_http_get($aiUrl, $siteHost);
if ($aiResp['error'] !== '' && $aiResp['code'] === 0) {
    printf("  ai/v1             ✗ unreachable: %s\n", $aiResp['error']);
} else {
    $code = $aiResp['code'];
    printf(
        "  ai/v1             %s  HTTP %d  %s\n",
        in_array($code, [200, 401, 403], true) ? '✓' : '✗',
        $code,
        $aiUrl
    );
    if (!in_array($code, [200, 401, 403], true)) {
        echo "      ↳ Fix: Permalinks not flushed. Run: wp rewrite flush.\n";
    }
}

// ─── Summary & next action ───────────────────────────────────────────

echo "──────────────────────────────────────────────────────────────────────\n";
echo " Summary & Next Action\n";
echo "──────────────────────────────────────────────────────────────────────\n";

if (!$activated) {
    echo "  ➤ The plugin has never been activated on this site.\n";
} elseif (!$firstCrawlerTime) {
    echo "  ➤ Activation in progress. Focus on getting the first AI crawler visit:\n";
    echo "     1. Copy the AI Connection URL from the dashboard\n";
    echo "     2. Paste into ChatGPT Desktop and ask it to search your site\n";
    echo "     3. Wait 24–72h for GPTBot/ClaudeBot to crawl\n";
    echo "     4. If on Cloudflare, disable Security → Bots → AI Scrapers block\n";
} elseif (!$firstSearchOk) {
    echo "  ➤ Crawler is visiting but first search returned empty. Likely causes:\n";
    echo "     • No published posts/products on this site (currently " . ($postCount + $productCount) . " total)\n";
    echo "     • All content is password-protected or in draft status\n";
    echo "     • MCP search endpoint returns 200 but empty body — re-save permalinks\n";
} else {
    echo "  ➤ Funnel complete. Site is fully onboarded for AI discovery.\n";
    if (!$analyticsOn) {
        echo "  ➤ Tip: Enable Settings → Shop2LLM → Analytics to track AI-driven traffic.\n";
    }
}

echo "\n";
echo " Site Hash (safe to share): {$siteHash}\n";
echo "══════════════════════════════════════════════════════════════════════\n\n";
