<?php
/**
 * AI Monitor - Customer API Endpoint
 * Session-authenticated AJAX endpoint for the customer AI monitoring dashboard.
 * Handles issues, reviews, notifications, and subscription data.
 */

header('Content-Type: application/json');

// Session authentication - auto-prepend.php handles this
if (!defined('AUTH_USER')) {
    http_response_code(401);
    echo json_encode(['success' => false, 'error' => 'Authentication required']);
    exit;
}

$username = AUTH_USER;
$is_root = (AUTH_USER === 'root');

// Database connection
require_once('/docker/whp/web/libs/mysqlmgmt.php');
require_once('/docker/whp/web/libs/ai-monitor-explain.php');
$mysql = new mysqlmgmt();
$db = $mysql->getMySQLConnection();
if (!$db) {
    http_response_code(500);
    echo json_encode(['success' => false, 'error' => 'Database connection failed']);
    exit;
}
$db->exec("USE whp");

// Parse request
$method = $_SERVER['REQUEST_METHOD'];
$action = $_GET['action'] ?? '';

// For POST requests, parse JSON body
$input = [];
if ($method === 'POST') {
    $raw = file_get_contents('php://input');
    $input = json_decode($raw, true);
    if (!is_array($input)) {
        $input = $_POST;
    }
    // Also check for action in body if not in GET
    if (empty($action) && !empty($input['action'])) {
        $action = $input['action'];
    }
}

// CSRF validation is handled by auto-prepend.php which checks the
// X-CSRF-Token header on all authenticated POST requests. No additional
// validation is needed here.

/**
 * Verify that a subscription belongs to the current user.
 * Returns the subscription row or sends error and exits.
 */
function verifySubscriptionOwnership($db, $subscriptionId, $username) {
    $stmt = $db->prepare("SELECT * FROM whp.ai_monitor_subscriptions WHERE id = ? AND username = ?");
    $stmt->execute([intval($subscriptionId), $username]);
    $sub = $stmt->fetch(PDO::FETCH_ASSOC);
    if (!$sub) {
        http_response_code(403);
        echo json_encode(['success' => false, 'error' => 'Subscription not found or access denied']);
        exit;
    }
    return $sub;
}

/**
 * Verify that an issue belongs to the current user.
 * Returns the issue row or sends error and exits.
 */
function verifyIssueOwnership($db, $issueId, $username) {
    // Root can modify any issue; others must own it.
    if ($username === 'root') {
        $stmt = $db->prepare("SELECT * FROM whp.ai_monitor_issues WHERE id = ?");
        $stmt->execute([intval($issueId)]);
    } else {
        $stmt = $db->prepare("SELECT * FROM whp.ai_monitor_issues WHERE id = ? AND username = ?");
        $stmt->execute([intval($issueId), $username]);
    }
    $issue = $stmt->fetch(PDO::FETCH_ASSOC);
    if (!$issue) {
        http_response_code(403);
        echo json_encode(['success' => false, 'error' => 'Issue not found or access denied']);
        exit;
    }
    return $issue;
}

/**
 * Verify that a review belongs to the current user.
 * Returns the review row or sends error and exits.
 */
function verifyReviewOwnership($db, $reviewId, $username) {
    // Root can access any review; others must own it.
    if ($username === 'root') {
        $stmt = $db->prepare("SELECT * FROM whp.ai_monitor_log_reviews WHERE id = ?");
        $stmt->execute([intval($reviewId)]);
    } else {
        $stmt = $db->prepare("SELECT * FROM whp.ai_monitor_log_reviews WHERE id = ? AND username = ?");
        $stmt->execute([intval($reviewId), $username]);
    }
    $review = $stmt->fetch(PDO::FETCH_ASSOC);
    if (!$review) {
        http_response_code(403);
        echo json_encode(['success' => false, 'error' => 'Review not found or access denied']);
        exit;
    }
    return $review;
}

// Route to action handler
try {
    switch ($action) {

        // ============================
        // GET: subscriptions
        // ============================
        case 'subscriptions':
            if ($method !== 'GET') {
                http_response_code(405);
                echo json_encode(['success' => false, 'error' => 'Method not allowed']);
                break;
            }

            if ($is_root) {
                $stmt = $db->prepare("
                    SELECT sub.id, sub.site_id, sub.username, sub.notification_email,
                           sub.daily_review_limit, sub.status, sub.activated_at,
                           s.site_name,
                           COALESCE(sd.fqdn, d.domain_name) AS primary_domain,
                           (SELECT COUNT(*) FROM whp.ai_monitor_issues i WHERE i.subscription_id = sub.id AND i.status = 'open') AS open_issues,
                           (SELECT COUNT(*) FROM whp.ai_monitor_issues i WHERE i.subscription_id = sub.id AND i.status = 'open' AND i.severity = 'critical') AS critical_count,
                           (SELECT COUNT(*) FROM whp.ai_monitor_issues i WHERE i.subscription_id = sub.id AND i.status = 'open' AND i.severity = 'warning') AS warning_count,
                           (SELECT COUNT(*) FROM whp.ai_monitor_issues i WHERE i.subscription_id = sub.id AND i.status = 'open' AND i.severity = 'info') AS info_count
                    FROM whp.ai_monitor_subscriptions sub
                    JOIN whp.sites s ON sub.site_id = s.id
                    JOIN whp.domains d ON s.primary_domain_id = d.id
                    LEFT JOIN whp.site_domains sd ON s.id = sd.site_id AND sd.is_primary = 1
                    WHERE sub.status = 'active'
                    ORDER BY s.site_name ASC
                ");
                $stmt->execute();
            } else {
                $stmt = $db->prepare("
                    SELECT sub.id, sub.site_id, sub.username, sub.notification_email,
                           sub.daily_review_limit, sub.status, sub.activated_at,
                           s.site_name,
                           COALESCE(sd.fqdn, d.domain_name) AS primary_domain,
                           (SELECT COUNT(*) FROM whp.ai_monitor_issues i WHERE i.subscription_id = sub.id AND i.status = 'open') AS open_issues,
                           (SELECT COUNT(*) FROM whp.ai_monitor_issues i WHERE i.subscription_id = sub.id AND i.status = 'open' AND i.severity = 'critical') AS critical_count,
                           (SELECT COUNT(*) FROM whp.ai_monitor_issues i WHERE i.subscription_id = sub.id AND i.status = 'open' AND i.severity = 'warning') AS warning_count,
                           (SELECT COUNT(*) FROM whp.ai_monitor_issues i WHERE i.subscription_id = sub.id AND i.status = 'open' AND i.severity = 'info') AS info_count
                    FROM whp.ai_monitor_subscriptions sub
                    JOIN whp.sites s ON sub.site_id = s.id
                    JOIN whp.domains d ON s.primary_domain_id = d.id
                    LEFT JOIN whp.site_domains sd ON s.id = sd.site_id AND sd.is_primary = 1
                    WHERE sub.username = ? AND sub.status = 'active'
                    ORDER BY s.site_name ASC
                ");
                $stmt->execute([$username]);
            }
            $subscriptions = $stmt->fetchAll(PDO::FETCH_ASSOC);

            echo json_encode(['success' => true, 'data' => ['subscriptions' => $subscriptions]]);
            break;

        // ============================
        // GET: issues (list or single)
        // ============================
        case 'issues':
            if ($method !== 'GET') {
                http_response_code(405);
                echo json_encode(['success' => false, 'error' => 'Method not allowed']);
                break;
            }

            // Single issue detail
            if (!empty($_GET['issue_id'])) {
                $issueId = intval($_GET['issue_id']);
                if ($is_root) {
                    $stmt = $db->prepare("
                        SELECT i.*, s.site_name,
                               COALESCE(sd.fqdn, d.domain_name) AS primary_domain
                        FROM whp.ai_monitor_issues i
                        JOIN whp.sites s ON i.site_id = s.id
                        JOIN whp.domains d ON s.primary_domain_id = d.id
                        LEFT JOIN whp.site_domains sd ON s.id = sd.site_id AND sd.is_primary = 1
                        WHERE i.id = ?
                    ");
                    $stmt->execute([$issueId]);
                } else {
                    $stmt = $db->prepare("
                        SELECT i.*, s.site_name,
                               COALESCE(sd.fqdn, d.domain_name) AS primary_domain
                        FROM whp.ai_monitor_issues i
                        JOIN whp.sites s ON i.site_id = s.id
                        JOIN whp.domains d ON s.primary_domain_id = d.id
                        LEFT JOIN whp.site_domains sd ON s.id = sd.site_id AND sd.is_primary = 1
                        WHERE i.id = ? AND i.username = ?
                    ");
                    $stmt->execute([$issueId, $username]);
                }
                $issue = $stmt->fetch(PDO::FETCH_ASSOC);

                if (!$issue) {
                    http_response_code(404);
                    echo json_encode(['success' => false, 'error' => 'Issue not found']);
                    break;
                }

                echo json_encode(['success' => true, 'data' => ['issue' => $issue]]);
                break;
            }

            // Paginated list
            $page = max(1, intval($_GET['page'] ?? 1));
            $perPage = max(1, min(100, intval($_GET['per_page'] ?? 20)));
            $offset = ($page - 1) * $perPage;

            // Build WHERE conditions
            if ($is_root) {
                $conditions = [];
                $params = [];
            } else {
                $conditions = ['i.username = ?'];
                $params = [$username];
            }

            // Filter: site_id
            if (!empty($_GET['site_id'])) {
                $conditions[] = 'i.site_id = ?';
                $params[] = intval($_GET['site_id']);
            }

            // Filter: severity
            if (!empty($_GET['severity']) && in_array($_GET['severity'], ['critical', 'warning', 'info'])) {
                $conditions[] = 'i.severity = ?';
                $params[] = $_GET['severity'];
            }

            // Filter: status
            $statusFilter = $_GET['status'] ?? 'open';
            // Back-compat aliases for pre-refactor UI
            if ($statusFilter === 'unread') $statusFilter = 'open';
            if ($statusFilter === 'resolved') $statusFilter = 'fixed';
            if (in_array($statusFilter, ['open', 'fixed', 'ignored'], true)) {
                $conditions[] = 'i.status = ?';
                $params[] = $statusFilter;
            }
            // 'all' = no additional filter

            $where = !empty($conditions) ? implode(' AND ', $conditions) : '1=1';

            // Count total
            $countSql = "SELECT COUNT(*) FROM whp.ai_monitor_issues i WHERE $where";
            $countStmt = $db->prepare($countSql);
            $countStmt->execute($params);
            $total = intval($countStmt->fetchColumn());

            $totalPages = max(1, ceil($total / $perPage));

            // Fetch issues
            $sql = "
                SELECT i.id, i.subscription_id, i.site_id, i.container_name,
                       i.severity, i.category, i.title, i.occurrence_count,
                       i.status, i.auto_resolved_at, i.first_seen, i.last_seen,
                       s.site_name,
                       COALESCE(sd.fqdn, d.domain_name) AS primary_domain
                FROM whp.ai_monitor_issues i
                JOIN whp.sites s ON i.site_id = s.id
                JOIN whp.domains d ON s.primary_domain_id = d.id
                LEFT JOIN whp.site_domains sd ON s.id = sd.site_id AND sd.is_primary = 1
                WHERE $where
                ORDER BY
                    CASE i.severity WHEN 'critical' THEN 1 WHEN 'warning' THEN 2 ELSE 3 END,
                    i.last_seen DESC
                LIMIT ? OFFSET ?
            ";
            $fetchParams = array_merge($params, [$perPage, $offset]);
            $stmt = $db->prepare($sql);
            $stmt->execute($fetchParams);
            $issues = $stmt->fetchAll(PDO::FETCH_ASSOC);

            echo json_encode([
                'success' => true,
                'data' => [
                    'issues' => $issues,
                    'total' => $total,
                    'page' => $page,
                    'per_page' => $perPage,
                    'total_pages' => $totalPages
                ]
            ]);
            break;

        // ============================
        // POST: mark_resolved
        // ============================
        case 'mark_resolved':  // backward-compat alias
        case 'mark_fixed':
            if ($method !== 'POST') {
                http_response_code(405);
                echo json_encode(['success' => false, 'error' => 'Method not allowed']);
                break;
            }

            $issueId = intval($input['issue_id'] ?? 0);
            if (!$issueId) {
                echo json_encode(['success' => false, 'error' => 'Missing issue_id']);
                break;
            }

            verifyIssueOwnership($db, $issueId, $username);

            $stmt = $db->prepare("
                UPDATE whp.ai_monitor_issues
                SET status = 'fixed', status_changed_by = ?, status_changed_at = NOW()
                WHERE id = ?
            ");
            $stmt->execute([$username, $issueId]);

            echo json_encode(['success' => true, 'data' => ['issue_id' => $issueId]]);
            break;

        // ============================
        // POST: mark_ignored
        // ============================
        case 'mark_ignored':
            if ($method !== 'POST') {
                http_response_code(405);
                echo json_encode(['success' => false, 'error' => 'Method not allowed']);
                break;
            }

            $issueId = intval($input['issue_id'] ?? 0);
            $suppress = !empty($input['suppress']);
            $reason = trim((string)($input['reason'] ?? ''));
            if (!$issueId) {
                echo json_encode(['success' => false, 'error' => 'Missing issue_id']);
                break;
            }

            verifyIssueOwnership($db, $issueId, $username);

            $db->beginTransaction();
            try {
                $stmt = $db->prepare("
                    UPDATE whp.ai_monitor_issues
                    SET status = 'ignored', status_changed_by = ?, status_changed_at = NOW()
                    WHERE id = ?
                ");
                $stmt->execute([$username, $issueId]);

                if ($suppress) {
                    $s = $db->prepare("SELECT signature, site_id FROM whp.ai_monitor_issues WHERE id = ?");
                    $s->execute([$issueId]);
                    $row = $s->fetch(PDO::FETCH_ASSOC);
                    if ($row) {
                        $ins = $db->prepare("
                            INSERT IGNORE INTO whp.ai_monitor_suppressions
                                (site_id, signature, scope, suppressed_by_user, reason)
                            VALUES (?, ?, ?, ?, ?)
                        ");
                        $ins->execute([
                            $row['site_id'],
                            $row['signature'],
                            $row['site_id'] ? 'site' : 'server',
                            $username,
                            $reason !== '' ? $reason : null,
                        ]);
                    }
                }
                $db->commit();
                echo json_encode(['success' => true, 'data' => ['issue_id' => $issueId]]);
            } catch (Throwable $e) {
                $db->rollBack();
                error_log('mark_ignored: ' . $e->getMessage());
                http_response_code(500);
                echo json_encode(['success' => false, 'error' => 'Server error']);
            }
            break;

        // ============================
        // POST: bulk_update (mass mark_fixed / mark_ignored / delete)
        // ============================
        // Customer-side bulk action handler. We don't reuse verifyIssueOwnership
        // because per-row exits would abort the whole batch on the first
        // not-owned id; instead the WHERE clause itself enforces the
        // ownership rule (root has no extra filter, anyone else gets
        // username = ?). Affected-row count tells the UI how many rows
        // really got updated.
        case 'bulk_mark_fixed':
        case 'bulk_mark_ignored':
        case 'bulk_delete':
            if ($method !== 'POST') {
                http_response_code(405);
                echo json_encode(['success' => false, 'error' => 'Method not allowed']);
                break;
            }
            $rawIds = $input['issue_ids'] ?? [];
            $issueIds = array_values(array_filter(array_map('intval', is_array($rawIds) ? $rawIds : []), fn($n) => $n > 0));
            if (!$issueIds) {
                echo json_encode(['success' => false, 'error' => 'No issues selected']);
                break;
            }
            $placeholders = implode(',', array_fill(0, count($issueIds), '?'));
            $ownerFilter  = $is_root ? '' : ' AND username = ?';
            $args         = $issueIds;
            if (!$is_root) $args[] = $username;

            try {
                $db->beginTransaction();
                if ($action === 'bulk_delete') {
                    // Drop suppressions tied to these issues' signatures, then delete.
                    $sigStmt = $db->prepare("SELECT DISTINCT signature FROM whp.ai_monitor_issues WHERE id IN ({$placeholders}){$ownerFilter}");
                    $sigStmt->execute($args);
                    $signatures = $sigStmt->fetchAll(PDO::FETCH_COLUMN);
                    $stmt = $db->prepare("DELETE FROM whp.ai_monitor_issues WHERE id IN ({$placeholders}){$ownerFilter}");
                    $stmt->execute($args);
                    $affected = $stmt->rowCount();
                } elseif ($action === 'bulk_mark_fixed') {
                    $sql  = "UPDATE whp.ai_monitor_issues
                             SET status = 'fixed', status_changed_by = ?, status_changed_at = NOW()
                             WHERE id IN ({$placeholders}) AND status != 'fixed'{$ownerFilter}";
                    $stmt = $db->prepare($sql);
                    $stmt->execute(array_merge([$username], $args));
                    $affected = $stmt->rowCount();
                } else { // bulk_mark_ignored — also adds suppressions so the same
                         // signature doesn't immediately re-open
                    $stmt = $db->prepare("UPDATE whp.ai_monitor_issues
                        SET status = 'ignored', status_changed_by = ?, status_changed_at = NOW()
                        WHERE id IN ({$placeholders}) AND status != 'ignored'{$ownerFilter}");
                    $stmt->execute(array_merge([$username], $args));
                    $affected = $stmt->rowCount();

                    $supSelect = $db->prepare("SELECT id, site_id, signature FROM whp.ai_monitor_issues
                        WHERE id IN ({$placeholders}){$ownerFilter}");
                    $supSelect->execute($args);
                    $rows = $supSelect->fetchAll(PDO::FETCH_ASSOC);
                    if ($rows) {
                        $ins = $db->prepare("INSERT IGNORE INTO whp.ai_monitor_suppressions
                            (site_id, signature, scope, suppressed_by_user, reason)
                            VALUES (?, ?, ?, ?, NULL)");
                        foreach ($rows as $row) {
                            $ins->execute([
                                $row['site_id'],
                                $row['signature'],
                                $row['site_id'] ? 'site' : 'server',
                                $username,
                            ]);
                        }
                    }
                }
                $db->commit();
                echo json_encode(['success' => true, 'data' => ['affected' => $affected, 'requested' => count($issueIds)]]);
            } catch (Throwable $e) {
                $db->rollBack();
                error_log("{$action}: " . $e->getMessage());
                http_response_code(500);
                echo json_encode(['success' => false, 'error' => 'Server error']);
            }
            break;

        // ============================
        // GET: list_suppressions
        // ============================
        case 'list_suppressions':
            if ($method !== 'GET') {
                http_response_code(405);
                echo json_encode(['success' => false, 'error' => 'Method not allowed']);
                break;
            }
            $titleJoin = "
                LEFT JOIN (
                    SELECT i1.signature, i1.title, i1.category, i1.severity
                    FROM whp.ai_monitor_issues i1
                    JOIN (
                        SELECT signature, MAX(id) AS max_id
                        FROM whp.ai_monitor_issues GROUP BY signature
                    ) latest_ids ON latest_ids.signature = i1.signature AND latest_ids.max_id = i1.id
                ) latest ON latest.signature = sup.signature
            ";
            if ($is_root) {
                $sql = "
                    SELECT sup.*, d.domain_name AS primary_domain, s.site_name, s.username,
                           latest.title AS issue_title,
                           latest.category AS issue_category,
                           latest.severity AS issue_severity
                    FROM whp.ai_monitor_suppressions sup
                    LEFT JOIN whp.sites s ON s.id = sup.site_id
                    LEFT JOIN whp.domains d ON d.id = s.primary_domain_id
                    {$titleJoin}
                    ORDER BY sup.suppressed_at DESC
                ";
                $stmt = $db->prepare($sql);
                $stmt->execute();
            } else {
                $sql = "
                    SELECT sup.*, d.domain_name AS primary_domain, s.site_name, s.username,
                           latest.title AS issue_title,
                           latest.category AS issue_category,
                           latest.severity AS issue_severity
                    FROM whp.ai_monitor_suppressions sup
                    JOIN whp.sites s ON s.id = sup.site_id
                    JOIN whp.domains d ON d.id = s.primary_domain_id
                    {$titleJoin}
                    WHERE s.username = ?
                    ORDER BY sup.suppressed_at DESC
                ";
                $stmt = $db->prepare($sql);
                $stmt->execute([$username]);
            }
            $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
            echo json_encode(['success' => true, 'data' => ['suppressions' => $rows]]);
            break;

        // ============================
        // POST: un_suppress
        // ============================
        case 'un_suppress':
            if ($method !== 'POST') {
                http_response_code(405);
                echo json_encode(['success' => false, 'error' => 'Method not allowed']);
                break;
            }
            $suppId = intval($input['suppression_id'] ?? 0);
            if (!$suppId) {
                echo json_encode(['success' => false, 'error' => 'Missing suppression_id']);
                break;
            }
            // Ownership check: site owner can un-suppress their own; root can un-suppress anything.
            if ($is_root) {
                $stmt = $db->prepare("DELETE FROM whp.ai_monitor_suppressions WHERE id = ?");
                $stmt->execute([$suppId]);
            } else {
                $stmt = $db->prepare("
                    DELETE sup FROM whp.ai_monitor_suppressions sup
                    JOIN whp.sites s ON s.id = sup.site_id
                    WHERE sup.id = ? AND s.username = ?
                ");
                $stmt->execute([$suppId, $username]);
            }
            $affected = $stmt->rowCount();
            if ($affected === 0) {
                http_response_code(403);
                echo json_encode(['success' => false, 'error' => 'Not found or forbidden']);
                break;
            }
            echo json_encode(['success' => true, 'data' => ['suppression_id' => $suppId]]);
            break;

        // ============================
        // POST: request_review
        // ============================
        case 'request_review':
            if ($method !== 'POST') {
                http_response_code(405);
                echo json_encode(['success' => false, 'error' => 'Method not allowed']);
                break;
            }

            $siteId = intval($input['site_id'] ?? 0);
            $requestType = $input['request_type'] ?? 'full';

            if (!$siteId) {
                echo json_encode(['success' => false, 'error' => 'Missing site_id']);
                break;
            }

            // Validate request_type
            $validTypes = ['full', 'error_only', 'performance'];
            if (!in_array($requestType, $validTypes)) {
                $requestType = 'full';
            }

            // Find subscription for this site
            if ($is_root) {
                // Root can review any site - find subscription regardless of owner
                $stmt = $db->prepare("
                    SELECT * FROM whp.ai_monitor_subscriptions
                    WHERE site_id = ? AND status = 'active'
                ");
                $stmt->execute([$siteId]);
            } else {
                $stmt = $db->prepare("
                    SELECT * FROM whp.ai_monitor_subscriptions
                    WHERE site_id = ? AND username = ? AND status = 'active'
                ");
                $stmt->execute([$siteId, $username]);
            }
            $sub = $stmt->fetch(PDO::FETCH_ASSOC);

            if (!$sub) {
                echo json_encode(['success' => false, 'error' => 'No active subscription for this site']);
                break;
            }

            // Daily quota (root exempt). Atomic counter: the UPDATE either
            // increments (if same day and under limit) OR rolls over to 1
            // (if a new day started). If neither condition holds, rowCount
            // is 0 and we reject. CURDATE() reflects server local time.
            $dailyLimit = intval($sub['daily_review_limit']);
            if (!$is_root) {
                $quotaStmt = $db->prepare("
                    UPDATE whp.ai_monitor_subscriptions
                    SET daily_reviews_used = CASE
                            WHEN usage_day = CURDATE() THEN daily_reviews_used + 1
                            ELSE 1
                        END,
                        usage_day = CURDATE()
                    WHERE id = ?
                      AND (usage_day < CURDATE() OR usage_day IS NULL
                           OR daily_reviews_used < daily_review_limit)
                ");
                $quotaStmt->execute([$sub['id']]);
                if ($quotaStmt->rowCount() === 0) {
                    // Fetch current count for the error message.
                    $countStmt = $db->prepare("
                        SELECT daily_reviews_used FROM whp.ai_monitor_subscriptions WHERE id = ?
                    ");
                    $countStmt->execute([$sub['id']]);
                    $todayCount = intval($countStmt->fetchColumn());
                    echo json_encode([
                        'success' => false,
                        'error' => 'Daily review limit reached (' . $dailyLimit . ' per day). Resets at midnight.',
                        'data' => [
                            'today_count' => $todayCount,
                            'daily_limit' => $dailyLimit
                        ]
                    ]);
                    break;
                }
            }

            // Check for existing pending/processing review for this site.
            $stmt = $db->prepare("
                SELECT id FROM whp.ai_monitor_log_reviews
                WHERE site_id = ? AND status IN ('pending', 'processing')
                LIMIT 1
            ");
            $stmt->execute([$siteId]);
            $existing = $stmt->fetch(PDO::FETCH_ASSOC);

            if ($existing) {
                echo json_encode([
                    'success' => false,
                    'error' => 'A review is already in progress for this site.',
                    'data' => ['review_id' => $existing['id']]
                ]);
                break;
            }

            // Insert pending review (root reviews use root as username, not the site owner)
            $reviewUsername = $is_root ? 'root' : $username;
            $stmt = $db->prepare("
                INSERT INTO whp.ai_monitor_log_reviews
                    (subscription_id, site_id, username, status, request_type, requested_at)
                VALUES (?, ?, ?, 'pending', ?, NOW())
            ");
            $stmt->execute([$sub['id'], $siteId, $reviewUsername, $requestType]);
            $reviewId = $db->lastInsertId();

            echo json_encode([
                'success' => true,
                'data' => [
                    'review_id' => intval($reviewId),
                    'status' => 'pending',
                    'remaining_today' => $is_root ? -1 : ($dailyLimit - $todayCount - 1)
                ]
            ]);
            break;

        // ============================
        // GET: review_status
        // ============================
        case 'review_status':
            if ($method !== 'GET') {
                http_response_code(405);
                echo json_encode(['success' => false, 'error' => 'Method not allowed']);
                break;
            }

            $reviewId = intval($_GET['id'] ?? 0);
            if (!$reviewId) {
                echo json_encode(['success' => false, 'error' => 'Missing review id']);
                break;
            }

            if ($is_root) {
                $stmt = $db->prepare("
                    SELECT r.*, s.site_name,
                           COALESCE(sd.fqdn, d.domain_name) AS primary_domain
                    FROM whp.ai_monitor_log_reviews r
                    JOIN whp.sites s ON r.site_id = s.id
                    JOIN whp.domains d ON s.primary_domain_id = d.id
                    LEFT JOIN whp.site_domains sd ON s.id = sd.site_id AND sd.is_primary = 1
                    WHERE r.id = ?
                ");
                $stmt->execute([$reviewId]);
            } else {
                $stmt = $db->prepare("
                    SELECT r.*, s.site_name,
                           COALESCE(sd.fqdn, d.domain_name) AS primary_domain
                    FROM whp.ai_monitor_log_reviews r
                    JOIN whp.sites s ON r.site_id = s.id
                    JOIN whp.domains d ON s.primary_domain_id = d.id
                    LEFT JOIN whp.site_domains sd ON s.id = sd.site_id AND sd.is_primary = 1
                    WHERE r.id = ? AND r.username = ?
                ");
                $stmt->execute([$reviewId, $username]);
            }
            $review = $stmt->fetch(PDO::FETCH_ASSOC);

            if (!$review) {
                http_response_code(404);
                echo json_encode(['success' => false, 'error' => 'Review not found']);
                break;
            }

            // Parse result_details JSON if stored as string
            if (!empty($review['result_details']) && is_string($review['result_details'])) {
                $decoded = json_decode($review['result_details'], true);
                if ($decoded !== null) {
                    $review['result_details'] = $decoded;
                }
            }

            // Hide technical details from non-root users
            if (!$is_root) {
                unset($review['ai_model_used']);
                unset($review['ai_tokens_used']);
            }

            echo json_encode(['success' => true, 'data' => ['review' => $review]]);
            break;

        // ============================
        // GET: review_history
        // ============================
        case 'review_history':
            if ($method !== 'GET') {
                http_response_code(405);
                echo json_encode(['success' => false, 'error' => 'Method not allowed']);
                break;
            }

            if ($is_root) {
                $conditions = [];
                $params = [];
            } else {
                $conditions = ['r.username = ?'];
                $params = [$username];
            }

            if (!empty($_GET['site_id'])) {
                $conditions[] = 'r.site_id = ?';
                $params[] = intval($_GET['site_id']);
            }

            // Hide noise from the 4 AM scheduled scan: by default, show only
            // (a) user-requested reviews, (b) reviews that produced at least
            // one finding, and (c) anything still in flight or failed.
            // ?include_clean=1 disables this filter and shows every review.
            if (empty($_GET['include_clean'])) {
                $conditions[] = "(
                    r.username != 'system'
                    OR JSON_LENGTH(r.result_details, '$.findings') > 0
                    OR r.status IN ('pending','processing','failed')
                )";
            }

            $where = !empty($conditions) ? implode(' AND ', $conditions) : '1=1';

            $selectFields = 'r.id, r.subscription_id, r.site_id, r.username, r.status, r.request_type,
                       r.result_summary, r.requested_at, r.completed_at';
            if ($is_root) {
                $selectFields .= ', r.ai_model_used, r.ai_tokens_used';
            }

            $stmt = $db->prepare("
                SELECT $selectFields,
                       s.site_name,
                       COALESCE(sd.fqdn, d.domain_name) AS primary_domain
                FROM whp.ai_monitor_log_reviews r
                JOIN whp.sites s ON r.site_id = s.id
                JOIN whp.domains d ON s.primary_domain_id = d.id
                LEFT JOIN whp.site_domains sd ON s.id = sd.site_id AND sd.is_primary = 1
                WHERE $where
                ORDER BY r.requested_at DESC
                LIMIT 50
            ");
            $stmt->execute($params);
            $reviews = $stmt->fetchAll(PDO::FETCH_ASSOC);

            echo json_encode(['success' => true, 'data' => ['reviews' => $reviews]]);
            break;

        // ============================
        // POST: update_notification_email
        // ============================
        case 'update_notification_email':
            if ($method !== 'POST') {
                http_response_code(405);
                echo json_encode(['success' => false, 'error' => 'Method not allowed']);
                break;
            }

            $subscriptionId = intval($input['subscription_id'] ?? 0);
            $email = trim($input['notification_email'] ?? '');

            if (!$subscriptionId) {
                echo json_encode(['success' => false, 'error' => 'Missing subscription_id']);
                break;
            }

            // Verify ownership
            verifySubscriptionOwnership($db, $subscriptionId, $username);

            // Validate email (allow empty to disable)
            if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
                echo json_encode(['success' => false, 'error' => 'Invalid email address']);
                break;
            }

            $stmt = $db->prepare("
                UPDATE whp.ai_monitor_subscriptions
                SET notification_email = ?
                WHERE id = ? AND username = ?
            ");
            $stmt->execute([$email ?: null, $subscriptionId, $username]);

            echo json_encode([
                'success' => true,
                'data' => [
                    'subscription_id' => $subscriptionId,
                    'notification_email' => $email
                ]
            ]);
            break;

        // ============================
        // POST: delete_review
        // ============================
        case 'delete_review':
            if ($method !== 'POST') {
                http_response_code(405);
                echo json_encode(['success' => false, 'error' => 'Method not allowed']);
                break;
            }

            $reviewId = intval($input['review_id'] ?? 0);
            if (!$reviewId) {
                echo json_encode(['success' => false, 'error' => 'Missing review_id']);
                break;
            }

            // Fetch the review
            $stmt = $db->prepare("SELECT id, username, status FROM whp.ai_monitor_log_reviews WHERE id = ?");
            $stmt->execute([$reviewId]);
            $review = $stmt->fetch(PDO::FETCH_ASSOC);

            if (!$review) {
                http_response_code(404);
                echo json_encode(['success' => false, 'error' => 'Review not found']);
                break;
            }

            // Only allow deleting completed or failed reviews (not in-progress)
            if (!in_array($review['status'], ['completed', 'failed'])) {
                echo json_encode(['success' => false, 'error' => 'Cannot delete a review that is still in progress']);
                break;
            }

            // Root can delete any review; non-root can only delete their own
            if (!$is_root && $review['username'] !== $username) {
                http_response_code(403);
                echo json_encode(['success' => false, 'error' => 'Access denied']);
                break;
            }

            // Hard delete — daily quota is now tracked via a counter on
            // the subscription row (see migration 004), so removing a
            // historical review no longer resets the user's quota.
            $stmt = $db->prepare("DELETE FROM whp.ai_monitor_log_reviews WHERE id = ?");
            $stmt->execute([$reviewId]);

            echo json_encode(['success' => true, 'data' => ['deleted' => $reviewId]]);
            break;

        // ============================
        // POST: explain_issue
        // Produces a plain-English explanation of the issue on demand.
        // Cached on the issue row so repeated clicks don't re-call AI.
        // ============================
        case 'explain_issue':
            if ($method !== 'POST') {
                http_response_code(405);
                echo json_encode(['success' => false, 'error' => 'Method not allowed']);
                break;
            }
            $issueId = intval($input['issue_id'] ?? 0);
            $regenerate = !empty($input['regenerate']);
            if (!$issueId) {
                echo json_encode(['success' => false, 'error' => 'Missing issue_id']);
                break;
            }
            $issue = verifyIssueOwnership($db, $issueId, $username);

            // Cache hit — return the stored explanation unless regenerate=true.
            // Cache hits do NOT consume quota (no new AI call).
            if (!$regenerate && !empty($issue['user_explanation'])) {
                echo json_encode(['success' => true, 'data' => [
                    'issue_id'    => $issueId,
                    'explanation' => $issue['user_explanation'],
                    'cached'      => true,
                    'generated_at' => $issue['user_explanation_at'] ?? null,
                    'model'       => $issue['user_explanation_model'] ?? null,
                ]]);
                break;
            }

            // Daily quota (root exempt). Charges the issue's own
            // subscription. Atomic counter with midnight reset — same
            // pattern as request_review above.
            if (!$is_root) {
                $subId = (int)($issue['subscription_id'] ?? 0);
                if ($subId <= 0) {
                    http_response_code(403);
                    echo json_encode(['success' => false,
                                       'error' => 'Explain requires an active subscription for this site.']);
                    break;
                }
                $quotaStmt = $db->prepare("
                    UPDATE whp.ai_monitor_subscriptions
                    SET daily_explains_used = CASE
                            WHEN usage_day = CURDATE() THEN daily_explains_used + 1
                            ELSE 1
                        END,
                        usage_day = CURDATE()
                    WHERE id = ?
                      AND daily_explain_limit > 0
                      AND (usage_day < CURDATE() OR usage_day IS NULL
                           OR daily_explains_used < daily_explain_limit)
                ");
                $quotaStmt->execute([$subId]);
                if ($quotaStmt->rowCount() === 0) {
                    // Either the plan disables explains (limit=0) or we're over.
                    $statusStmt = $db->prepare("
                        SELECT daily_explain_limit, daily_explains_used
                        FROM whp.ai_monitor_subscriptions WHERE id = ?
                    ");
                    $statusStmt->execute([$subId]);
                    $q = $statusStmt->fetch(PDO::FETCH_ASSOC) ?: [];
                    $limit = (int)($q['daily_explain_limit'] ?? 0);
                    $used  = (int)($q['daily_explains_used'] ?? 0);
                    if ($limit <= 0) {
                        http_response_code(403);
                        echo json_encode(['success' => false,
                                           'error' => 'Explain feature is not available on your plan.']);
                    } else {
                        http_response_code(429);
                        echo json_encode(['success' => false,
                                           'error' => "Daily explain limit reached ({$used}/{$limit}). Resets at midnight.",
                                           'quota' => ['used' => $used, 'limit' => $limit]]);
                    }
                    break;
                }
            }

            $explain = ai_monitor_explain_issue($issue);
            if (!$explain['success']) {
                http_response_code(503);
                echo json_encode(['success' => false, 'error' => $explain['error']]);
                break;
            }

            $db->prepare("
                UPDATE whp.ai_monitor_issues
                SET user_explanation = ?, user_explanation_at = NOW(), user_explanation_model = ?
                WHERE id = ?
            ")->execute([$explain['explanation'], $explain['model'], $issueId]);

            echo json_encode(['success' => true, 'data' => [
                'issue_id'    => $issueId,
                'explanation' => $explain['explanation'],
                'cached'      => false,
                'model'       => $explain['model'],
            ]]);
            break;

        // ============================
        // Default: unknown action
        // ============================
        default:
            http_response_code(400);
            echo json_encode(['success' => false, 'error' => 'Unknown action: ' . ($action ?: '(empty)')]);
            break;
    }
} catch (PDOException $e) {
    error_log('AI Monitor Customer API PDO error: ' . $e->getMessage());
    http_response_code(500);
    echo json_encode(['success' => false, 'error' => 'Database error']);
} catch (Exception $e) {
    error_log('AI Monitor Customer API error: ' . $e->getMessage());
    http_response_code(500);
    echo json_encode(['success' => false, 'error' => 'Internal error']);
}
