<?php
require_once __DIR__ . '/../Core.php';
require_once __DIR__ . '/Setting.php';

class Campaign extends Model
{
    public function all(): array
    {
        $sql = 'SELECT c.*, t.name AS template_name,
            (SELECT COUNT(*) FROM campaign_recipients cr WHERE cr.campaign_id = c.id) total_recipients,
            (SELECT COUNT(*) FROM campaign_recipients cr WHERE cr.campaign_id = c.id AND cr.status = "sent") sent_recipients,
            (SELECT COUNT(*) FROM campaign_recipients cr WHERE cr.campaign_id = c.id AND cr.status = "failed") failed_recipients,
            (SELECT COUNT(*) FROM campaign_recipients cr WHERE cr.campaign_id = c.id AND cr.status = "queued") queued_recipients,
            (SELECT COUNT(*) FROM message_events me INNER JOIN messages m ON m.id = me.message_id WHERE m.campaign_id = c.id AND me.event_type = "open") open_count,
            (SELECT COUNT(DISTINCT me.message_id) FROM message_events me INNER JOIN messages m ON m.id = me.message_id WHERE m.campaign_id = c.id AND me.event_type = "open") unique_open_count,
            (SELECT COUNT(*) FROM message_events me INNER JOIN messages m ON m.id = me.message_id WHERE m.campaign_id = c.id AND me.event_type = "click") click_count,
            (SELECT COUNT(DISTINCT CONCAT(me.message_id, "|", COALESCE(me.event_value, ""))) FROM message_events me INNER JOIN messages m ON m.id = me.message_id WHERE m.campaign_id = c.id AND me.event_type = "click") unique_click_count,
            (SELECT COUNT(*) FROM messages m WHERE m.campaign_id = c.id AND m.status = "bounced") bounced_count,
            (SELECT COUNT(*) FROM messages m WHERE m.campaign_id = c.id AND m.status = "delivered") delivered_count
            FROM campaigns c LEFT JOIN templates t ON t.id = c.template_id LEFT JOIN contact_lists cl ON cl.id = c.contact_list_id ORDER BY c.created_at DESC';
        return $this->applyControlStates($this->db->query($sql)->fetchAll());
    }

    public function create(array $data): int
    {
        $stmt = $this->db->prepare('INSERT INTO campaigns (name, channel, template_id, status, recipient_filter, scheduled_at, created_by, subject_override, body_override, segment_name, segment_id, contact_list_id, recurring_parent_id, next_occurrence_at, last_run_at, approval_status, approved_by, approved_at, ab_enabled, variant_a_subject, variant_a_body, variant_b_subject, variant_b_body, recurring_enabled, recurring_rule, send_time_optimization, builder_json) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
        $stmt->execute([
            $data['name'],
            $data['channel'],
            $data['template_id'] ?? null,
            $data['status'] ?? 'draft',
            $data['recipient_filter'] ?? null,
            $data['scheduled_at'] ?? null,
            $data['created_by'] ?? null,
            $data['subject_override'] ?? null,
            $data['body_override'] ?? null,
            $data['segment_name'] ?? null,
            $data['segment_id'] ?? null,
            $data['contact_list_id'] ?? null,
            $data['recurring_parent_id'] ?? null,
            $data['next_occurrence_at'] ?? null,
            $data['last_run_at'] ?? null,
            $data['approval_status'] ?? 'not_required',
            $data['approved_by'] ?? null,
            $data['approved_at'] ?? null,
            !empty($data['ab_enabled']) ? 1 : 0,
            $data['variant_a_subject'] ?? null,
            $data['variant_a_body'] ?? null,
            $data['variant_b_subject'] ?? null,
            $data['variant_b_body'] ?? null,
            !empty($data['recurring_enabled']) ? 1 : 0,
            $data['recurring_rule'] ?? null,
            !empty($data['send_time_optimization']) ? 1 : 0,
            $data['builder_json'] ?? null,
        ]);
        return (int)$this->db->lastInsertId();
    }

    public function find(int $id): ?array
    {
        $stmt = $this->db->prepare('SELECT c.*, t.name AS template_name, t.subject AS template_subject, t.body AS template_body, t.media_path AS template_media_path, s.name AS saved_segment_name, cl.name AS contact_list_name FROM campaigns c LEFT JOIN templates t ON t.id = c.template_id LEFT JOIN segments s ON s.id = c.segment_id LEFT JOIN contact_lists cl ON cl.id = c.contact_list_id WHERE c.id = ? LIMIT 1');
        $stmt->execute([$id]);
        $campaign = $stmt->fetch() ?: null;
        return $campaign ? $this->appendControlState($campaign) : null;
    }

    public function due(): array
    {
        $sql = "SELECT * FROM campaigns WHERE status IN ('queued','processing') AND approval_status IN ('approved','not_required') AND (scheduled_at IS NULL OR scheduled_at <= NOW()) ORDER BY created_at ASC";
        $campaigns = $this->applyControlStates($this->db->query($sql)->fetchAll());
        return array_values(array_filter($campaigns, static fn(array $campaign): bool => ($campaign['operator_state'] ?? 'running') === 'running'));
    }

    public function setStatus(int $id, string $status): void
    {
        $stmt = $this->db->prepare('UPDATE campaigns SET status = ?, updated_at = NOW() WHERE id = ?');
        $stmt->execute([$status, $id]);
    }

    public function markRunCompleted(int $id, ?string $ranAt = null): void
    {
        $stmt = $this->db->prepare('UPDATE campaigns SET last_run_at = COALESCE(?, NOW()), updated_at = NOW() WHERE id = ?');
        $stmt->execute([$ranAt, $id]);
    }

    public function updateNextOccurrence(int $id, ?string $nextAt): void
    {
        $stmt = $this->db->prepare('UPDATE campaigns SET next_occurrence_at = ?, updated_at = NOW() WHERE id = ?');
        $stmt->execute([$nextAt, $id]);
    }

    public function approve(int $id, int $userId): void
    {
        $stmt = $this->db->prepare('UPDATE campaigns SET approval_status = "approved", approved_by = ?, approved_at = NOW(), status = CASE WHEN status = "draft" THEN "queued" ELSE status END, updated_at = NOW() WHERE id = ?');
        $stmt->execute([$userId, $id]);
    }

    public function seedRecipients(int $campaignId, array $contacts, string $channel): void
    {
        $stmt = $this->db->prepare('INSERT INTO campaign_recipients (campaign_id, contact_id, address, status) VALUES (?, ?, ?, "queued")');
        foreach ($contacts as $contact) {
            $address = $channel === 'sms' ? ($contact['phone'] ?? null) : ($contact['email'] ?? null);
            if (!$address) {
                continue;
            }
            $stmt->execute([$campaignId, $contact['id'], $address]);
        }
    }

    public function seedRecipientsFromCampaign(int $newCampaignId, int $sourceCampaignId): void
    {
        $stmt = $this->db->prepare('INSERT INTO campaign_recipients (campaign_id, contact_id, address, status) SELECT ?, contact_id, address, "queued" FROM campaign_recipients WHERE campaign_id = ? GROUP BY contact_id, address');
        $stmt->execute([$newCampaignId, $sourceCampaignId]);
    }

    public function queuedRecipients(int $campaignId, int $limit = 250): array
    {
        $stmt = $this->db->prepare('SELECT cr.*, c.first_name, c.last_name, c.email, c.phone, c.company, c.tags, c.timezone FROM campaign_recipients cr INNER JOIN contacts c ON c.id = cr.contact_id WHERE cr.campaign_id = ? AND cr.status = "queued" ORDER BY cr.id ASC LIMIT ?');
        $stmt->bindValue(1, $campaignId, PDO::PARAM_INT);
        $stmt->bindValue(2, $limit, PDO::PARAM_INT);
        $stmt->execute();
        return $stmt->fetchAll();
    }

    public function hasQueuedRecipients(int $campaignId): bool
    {
        $stmt = $this->db->prepare('SELECT COUNT(*) FROM campaign_recipients WHERE campaign_id = ? AND status = "queued"');
        $stmt->execute([$campaignId]);
        return (int)$stmt->fetchColumn() > 0;
    }

    public function requeueFailedRecipients(int $campaignId): int
    {
        $stmt = $this->db->prepare('UPDATE campaign_recipients SET status = "queued", error_message = NULL, message_id = NULL, processed_at = NULL WHERE campaign_id = ? AND status = "failed"');
        $stmt->execute([$campaignId]);
        return $stmt->rowCount();
    }

    public function deleteCascade(int $campaignId): void
    {
        $this->setControlState($campaignId, 'stopped');

        $messageStmt = $this->db->prepare('SELECT id FROM messages WHERE campaign_id = ?');
        $messageStmt->execute([$campaignId]);
        $messageIds = array_map('intval', array_column($messageStmt->fetchAll(), 'id'));
        if ($messageIds) {
            $placeholders = implode(',', array_fill(0, count($messageIds), '?'));
            $deleteEvents = $this->db->prepare("DELETE FROM message_events WHERE message_id IN ($placeholders)");
            $deleteEvents->execute($messageIds);
        }

        $queueJobIds = [];
        $queueStmt = $this->db->prepare('SELECT id, payload_json FROM queue_jobs WHERE job_type = "campaign_process"');
        $queueStmt->execute();
        foreach ($queueStmt->fetchAll() as $job) {
            $payload = json_decode((string)($job['payload_json'] ?? ''), true) ?: [];
            if ((int)($payload['campaign_id'] ?? 0) === $campaignId) {
                $queueJobIds[] = (int)$job['id'];
            }
        }
        if ($queueJobIds) {
            $placeholders = implode(',', array_fill(0, count($queueJobIds), '?'));
            $deleteJobs = $this->db->prepare("DELETE FROM queue_jobs WHERE id IN ($placeholders)");
            $deleteJobs->execute($queueJobIds);
        }

        $deadLetterIds = [];
        $deadLetterStmt = $this->db->prepare('SELECT id, payload_json FROM queue_dead_letters WHERE job_type = "campaign_process"');
        $deadLetterStmt->execute();
        foreach ($deadLetterStmt->fetchAll() as $letter) {
            $payload = json_decode((string)($letter['payload_json'] ?? ''), true) ?: [];
            if ((int)($payload['campaign_id'] ?? 0) === $campaignId) {
                $deadLetterIds[] = (int)$letter['id'];
            }
        }
        if ($deadLetterIds) {
            $placeholders = implode(',', array_fill(0, count($deadLetterIds), '?'));
            $deleteDeadLetters = $this->db->prepare("DELETE FROM queue_dead_letters WHERE id IN ($placeholders)");
            $deleteDeadLetters->execute($deadLetterIds);
        }

        $deleteRecipients = $this->db->prepare('DELETE FROM campaign_recipients WHERE campaign_id = ?');
        $deleteRecipients->execute([$campaignId]);

        $deleteMessages = $this->db->prepare('DELETE FROM messages WHERE campaign_id = ?');
        $deleteMessages->execute([$campaignId]);

        $deleteState = $this->db->prepare('DELETE FROM settings WHERE setting_key = ?');
        $deleteState->execute([$this->controlKey($campaignId)]);

        $deleteCampaign = $this->db->prepare('DELETE FROM campaigns WHERE id = ?');
        $deleteCampaign->execute([$campaignId]);
    }

    public function markRecipient(int $id, string $status, ?string $errorMessage = null, ?int $messageId = null): void
    {
        $stmt = $this->db->prepare('UPDATE campaign_recipients SET status = ?, error_message = ?, message_id = ?, processed_at = NOW() WHERE id = ?');
        $stmt->execute([$status, $errorMessage, $messageId, $id]);
    }

    public function summary(int $campaignId): array
    {
        $stmt = $this->db->prepare('SELECT c.*, t.name AS template_name,
            (SELECT COUNT(*) FROM campaign_recipients cr WHERE cr.campaign_id = c.id) total_recipients,
            (SELECT COUNT(*) FROM campaign_recipients cr WHERE cr.campaign_id = c.id AND cr.status = "sent") sent_recipients,
            (SELECT COUNT(*) FROM campaign_recipients cr WHERE cr.campaign_id = c.id AND cr.status = "failed") failed_recipients,
            (SELECT COUNT(*) FROM campaign_recipients cr WHERE cr.campaign_id = c.id AND cr.status = "queued") queued_recipients,
            (SELECT COUNT(*) FROM messages m WHERE m.campaign_id = c.id AND m.status = "delivered") delivered_count,
            (SELECT COUNT(*) FROM messages m WHERE m.campaign_id = c.id AND m.status = "bounced") bounced_count,
            (SELECT COUNT(*) FROM message_events me INNER JOIN messages m ON m.id = me.message_id WHERE m.campaign_id = c.id AND me.event_type = "open") open_count,
            (SELECT COUNT(DISTINCT me.message_id) FROM message_events me INNER JOIN messages m ON m.id = me.message_id WHERE m.campaign_id = c.id AND me.event_type = "open") unique_open_count,
            (SELECT COUNT(*) FROM message_events me INNER JOIN messages m ON m.id = me.message_id WHERE m.campaign_id = c.id AND me.event_type = "click") click_count,
            (SELECT COUNT(DISTINCT CONCAT(me.message_id, "|", COALESCE(me.event_value, ""))) FROM message_events me INNER JOIN messages m ON m.id = me.message_id WHERE m.campaign_id = c.id AND me.event_type = "click") unique_click_count
            FROM campaigns c LEFT JOIN templates t ON t.id = c.template_id WHERE c.id = ? LIMIT 1');
        $stmt->execute([$campaignId]);
        $campaign = $stmt->fetch() ?: [];
        return $campaign ? $this->appendControlState($campaign) : [];
    }

    public function recipientBreakdown(int $campaignId, int $limit = 200): array
    {
        $stmt = $this->db->prepare('SELECT cr.*, c.first_name, c.last_name, c.company, m.subject, m.status AS message_status FROM campaign_recipients cr LEFT JOIN contacts c ON c.id = cr.contact_id LEFT JOIN messages m ON m.id = cr.message_id WHERE cr.campaign_id = ? ORDER BY cr.id DESC LIMIT ?');
        $stmt->bindValue(1, $campaignId, PDO::PARAM_INT);
        $stmt->bindValue(2, $limit, PDO::PARAM_INT);
        $stmt->execute();
        return $stmt->fetchAll();
    }

    public function messageTrend(int $campaignId): array
    {
        $stmt = $this->db->prepare("SELECT DATE(m.created_at) day,
            SUM(m.status = 'delivered') delivered_count,
            SUM(m.status = 'bounced') bounced_count,
            COUNT(DISTINCT CASE WHEN me.event_type = 'open' THEN me.message_id END) unique_open_count,
            COUNT(DISTINCT CASE WHEN me.event_type = 'click' THEN CONCAT(me.message_id, '|', COALESCE(me.event_value,'')) END) unique_click_count
            FROM messages m
            LEFT JOIN message_events me ON me.message_id = m.id
            WHERE m.campaign_id = ?
            GROUP BY DATE(m.created_at)
            ORDER BY day ASC");
        $stmt->execute([$campaignId]);
        return $stmt->fetchAll();
    }

    public function compareRecent(int $limit = 8): array
    {
        $stmt = $this->db->prepare('SELECT c.id, c.name, c.channel, c.status, c.approval_status,
            (SELECT COUNT(*) FROM campaign_recipients cr WHERE cr.campaign_id = c.id) total_recipients,
            (SELECT COUNT(*) FROM campaign_recipients cr WHERE cr.campaign_id = c.id AND cr.status = "sent") sent_recipients,
            (SELECT COUNT(DISTINCT me.message_id) FROM message_events me INNER JOIN messages m ON m.id = me.message_id WHERE m.campaign_id = c.id AND me.event_type = "open") unique_open_count,
            (SELECT COUNT(DISTINCT CONCAT(me.message_id, "|", COALESCE(me.event_value, ""))) FROM message_events me INNER JOIN messages m ON m.id = me.message_id WHERE m.campaign_id = c.id AND me.event_type = "click") unique_click_count,
            (SELECT COUNT(*) FROM messages m WHERE m.campaign_id = c.id AND m.status = "bounced") bounced_count
            FROM campaigns c ORDER BY c.created_at DESC LIMIT ?');
        $stmt->bindValue(1, $limit, PDO::PARAM_INT);
        $stmt->execute();
        return $this->applyControlStates($stmt->fetchAll());
    }

    public function templatePerformance(int $limit = 12): array
    {
        $stmt = $this->db->prepare('SELECT t.id, t.name, t.type,
            COUNT(DISTINCT c.id) campaign_count,
            COUNT(DISTINCT m.id) message_count,
            SUM(m.status = "delivered") delivered_count,
            SUM(m.status = "bounced") bounced_count,
            COUNT(DISTINCT CASE WHEN me.event_type = "open" THEN me.message_id END) unique_open_count,
            COUNT(DISTINCT CASE WHEN me.event_type = "click" THEN CONCAT(me.message_id, "|", COALESCE(me.event_value, "")) END) unique_click_count
            FROM templates t
            LEFT JOIN campaigns c ON c.template_id = t.id
            LEFT JOIN messages m ON m.campaign_id = c.id
            LEFT JOIN message_events me ON me.message_id = m.id
            GROUP BY t.id, t.name, t.type
            ORDER BY message_count DESC, campaign_count DESC, t.name ASC
            LIMIT ?');
        $stmt->bindValue(1, $limit, PDO::PARAM_INT);
        $stmt->execute();
        return $stmt->fetchAll();
    }

    public function segmentPerformance(int $limit = 12): array
    {
        $stmt = $this->db->prepare('SELECT COALESCE(NULLIF(c.segment_name, ""), "Unlabeled") AS segment_name,
            COUNT(DISTINCT c.id) campaign_count,
            COUNT(DISTINCT m.id) message_count,
            SUM(m.status = "delivered") delivered_count,
            SUM(m.status = "bounced") bounced_count,
            COUNT(DISTINCT CASE WHEN me.event_type = "open" THEN me.message_id END) unique_open_count,
            COUNT(DISTINCT CASE WHEN me.event_type = "click" THEN CONCAT(me.message_id, "|", COALESCE(me.event_value, "")) END) unique_click_count
            FROM campaigns c
            LEFT JOIN messages m ON m.campaign_id = c.id
            LEFT JOIN message_events me ON me.message_id = m.id
            GROUP BY COALESCE(NULLIF(c.segment_name, ""), "Unlabeled")
            ORDER BY message_count DESC, campaign_count DESC, segment_name ASC
            LIMIT ?');
        $stmt->bindValue(1, $limit, PDO::PARAM_INT);
        $stmt->execute();
        return $stmt->fetchAll();
    }

    public function variantPerformance(int $campaignId): array
    {
        $stmt = $this->db->prepare("SELECT m.id, m.status, m.metadata_json,
            COUNT(DISTINCT CASE WHEN me.event_type = 'open' THEN me.message_id END) AS has_open,
            COUNT(DISTINCT CASE WHEN me.event_type = 'click' THEN CONCAT(me.message_id, '|', COALESCE(me.event_value,'')) END) AS has_click
            FROM messages m
            LEFT JOIN message_events me ON me.message_id = m.id
            WHERE m.campaign_id = ?
            GROUP BY m.id, m.status, m.metadata_json
            ORDER BY m.id ASC");
        $stmt->execute([$campaignId]);
        $labels = ['default' => 'Control', 'a' => 'Variant A', 'b' => 'Variant B'];
        $buckets = [
            'default' => ['variant' => 'default', 'label' => 'Control', 'message_count' => 0, 'delivered_count' => 0, 'bounced_count' => 0, 'failed_count' => 0, 'unique_open_count' => 0, 'unique_click_count' => 0],
            'a' => ['variant' => 'a', 'label' => 'Variant A', 'message_count' => 0, 'delivered_count' => 0, 'bounced_count' => 0, 'failed_count' => 0, 'unique_open_count' => 0, 'unique_click_count' => 0],
            'b' => ['variant' => 'b', 'label' => 'Variant B', 'message_count' => 0, 'delivered_count' => 0, 'bounced_count' => 0, 'failed_count' => 0, 'unique_open_count' => 0, 'unique_click_count' => 0],
        ];
        foreach ($stmt->fetchAll() as $row) {
            $meta = json_decode((string)($row['metadata_json'] ?? ''), true) ?: [];
            if (($meta['mode'] ?? null) === 'test_send') {
                continue;
            }
            $variant = (string)($meta['variant'] ?? 'default');
            if (!isset($buckets[$variant])) {
                $buckets[$variant] = [
                    'variant' => $variant,
                    'label' => $labels[$variant] ?? ('Variant ' . strtoupper($variant)),
                    'message_count' => 0,
                    'delivered_count' => 0,
                    'bounced_count' => 0,
                    'failed_count' => 0,
                    'unique_open_count' => 0,
                    'unique_click_count' => 0,
                ];
            }
            $buckets[$variant]['message_count']++;
            if (($row['status'] ?? '') === 'delivered') {
                $buckets[$variant]['delivered_count']++;
            }
            if (($row['status'] ?? '') === 'bounced') {
                $buckets[$variant]['bounced_count']++;
            }
            if (in_array(($row['status'] ?? ''), ['failed', 'undelivered'], true)) {
                $buckets[$variant]['failed_count']++;
            }
            if ((int)($row['has_open'] ?? 0) > 0) {
                $buckets[$variant]['unique_open_count']++;
            }
            if ((int)($row['has_click'] ?? 0) > 0) {
                $buckets[$variant]['unique_click_count']++;
            }
        }
        $result = [];
        foreach ($buckets as $variant => $bucket) {
            if ($bucket['message_count'] <= 0 && in_array($variant, ['a', 'b'], true)) {
                continue;
            }
            $bucket['open_rate'] = round(($bucket['unique_open_count'] / max(1, $bucket['message_count'])) * 100, 1);
            $bucket['click_rate'] = round(($bucket['unique_click_count'] / max(1, $bucket['message_count'])) * 100, 1);
            $bucket['bounce_rate'] = round(($bucket['bounced_count'] / max(1, $bucket['message_count'])) * 100, 1);
            $result[] = $bucket;
        }
        return $result;
    }

    public function cloneForRecurring(array $campaign): int
    {
        return $this->create([
            'name' => ($campaign['name'] ?? 'Recurring Campaign') . ' (Recurring)',
            'channel' => $campaign['channel'] ?? 'email',
            'template_id' => $campaign['template_id'] ?? null,
            'status' => 'queued',
            'recipient_filter' => $campaign['recipient_filter'] ?? null,
            'scheduled_at' => $campaign['scheduled_at'] ?? null,
            'created_by' => $campaign['created_by'] ?? null,
            'subject_override' => $campaign['subject_override'] ?? null,
            'body_override' => $campaign['body_override'] ?? null,
            'segment_name' => $campaign['segment_name'] ?? null,
            'segment_id' => $campaign['segment_id'] ?? null,
            'contact_list_id' => $campaign['contact_list_id'] ?? null,
            'recurring_parent_id' => $campaign['recurring_parent_id'] ?? ($campaign['id'] ?? null),
            'next_occurrence_at' => $campaign['next_occurrence_at'] ?? null,
            'last_run_at' => null,
            'approval_status' => 'not_required',
            'ab_enabled' => !empty($campaign['ab_enabled']) ? 1 : 0,
            'variant_a_subject' => $campaign['variant_a_subject'] ?? null,
            'variant_a_body' => $campaign['variant_a_body'] ?? null,
            'variant_b_subject' => $campaign['variant_b_subject'] ?? null,
            'variant_b_body' => $campaign['variant_b_body'] ?? null,
            'recurring_enabled' => !empty($campaign['recurring_enabled']) ? 1 : 0,
            'recurring_rule' => $campaign['recurring_rule'] ?? null,
            'send_time_optimization' => !empty($campaign['send_time_optimization']) ? 1 : 0,
            'builder_json' => $campaign['builder_json'] ?? null,
        ]);
    }

    public function controlState(int $campaignId): string
    {
        return $this->normalizeControlState((string)(new Setting())->get($this->controlKey($campaignId), 'running'));
    }

    public function setControlState(int $campaignId, string $state): void
    {
        (new Setting())->set($this->controlKey($campaignId), $this->normalizeControlState($state));
    }

    public function applyControlStates(array $campaigns): array
    {
        $states = $this->loadControlStates(array_map(static fn(array $campaign): int => (int)($campaign['id'] ?? 0), $campaigns));
        foreach ($campaigns as &$campaign) {
            $campaign = $this->appendControlState($campaign, $states);
        }
        unset($campaign);
        return $campaigns;
    }

    private function appendControlState(array $campaign, array $states = []): array
    {
        $campaignId = (int)($campaign['id'] ?? 0);
        $operatorState = $states[$campaignId] ?? 'running';
        $operatorState = $this->normalizeControlState($operatorState);
        $campaign['operator_state'] = $operatorState;
        $campaign['display_status'] = in_array($operatorState, ['paused', 'stopped'], true)
            ? ucfirst($operatorState)
            : (string)($campaign['status'] ?? 'draft');
        return $campaign;
    }

    private function loadControlStates(array $campaignIds): array
    {
        $campaignIds = array_values(array_filter(array_unique(array_map('intval', $campaignIds)), static fn(int $id): bool => $id > 0));
        if (!$campaignIds) {
            return [];
        }
        $placeholders = implode(',', array_fill(0, count($campaignIds), '?'));
        $keys = array_map(fn(int $id): string => $this->controlKey($id), $campaignIds);
        $stmt = $this->db->prepare("SELECT setting_key, setting_value FROM settings WHERE setting_key IN ($placeholders)");
        $stmt->execute($keys);
        $states = [];
        foreach ($stmt->fetchAll() as $row) {
            if (preg_match('/campaign_runtime_state_(\d+)/', (string)($row['setting_key'] ?? ''), $matches)) {
                $states[(int)$matches[1]] = $this->normalizeControlState((string)($row['setting_value'] ?? 'running'));
            }
        }
        return $states;
    }

    private function normalizeControlState(?string $state): string
    {
        $state = strtolower(trim((string)$state));
        return in_array($state, ['paused', 'stopped'], true) ? $state : 'running';
    }

    private function controlKey(int $campaignId): string
    {
        return 'campaign_runtime_state_' . $campaignId;
    }
}
