<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\CampaignModel;
use App\Models\EmailQueueModel;

/**
 * EmailQueueService
 *
 * Two responsibilities: enqueue() (called from CampaignService for
 * bulk sends, or directly for a personal email) and process() (called
 * by bin/process_email_queue.php on a schedule — a worker tick, not a
 * live request). Keeping "queue it" and "send it" as separate steps is
 * what makes a 5,000-recipient bulk send not block the HTTP request
 * that triggered it.
 */
final class EmailQueueService
{
    public function __construct(
        private readonly EmailQueueModel $queue = new EmailQueueModel(),
        private readonly MailService $mailer = new MailService()
    ) {
    }

    public function enqueue(
        string $toEmail,
        ?string $toName,
        string $subject,
        string $bodyHtml,
        ?int $campaignId = null,
        ?int $campaignRecipientId = null,
        ?string $scheduledFor = null,
        ?int $createdBy = null,
        ?int $businessLineId = null
    ): int {
        return $this->queue->insert([
            'campaign_id' => $campaignId,
            'campaign_recipient_id' => $campaignRecipientId,
            'business_line_id' => $businessLineId,
            'to_email' => $toEmail,
            'to_name' => $toName,
            'subject' => $subject,
            'body_html' => $bodyHtml,
            'status' => 'pending',
            'scheduled_for' => $scheduledFor ?? now(),
            'created_by' => $createdBy,
        ]);
    }

    /**
     * Processes up to $limit due, pending queue rows. Safe to run
     * concurrently (claimDue() uses SELECT ... FOR UPDATE) and safe to
     * run every minute via cron — a row is only ever "processing" for
     * the duration of one worker tick.
     *
     * @return array{sent:int, failed:int}
     */
    public function process(int $limit = 50): array
    {
        $rows = $this->queue->claimDue($limit);
        $sent = 0;
        $failed = 0;
        $touchedCampaigns = [];

        foreach ($rows as $row) {
            $ok = $this->mailer->send(
                $row['to_email'],
                $row['to_name'] ?? '',
                $row['subject'],
                $row['body_html'],
                fromEmailOverride: $row['from_email'] ?? null,
                businessLineId: $row['business_line_id'] !== null ? (int) $row['business_line_id'] : null
            );

            if ($ok) {
                $this->queue->markSent((int) $row['id']);
                $this->markRecipientSent($row);
                $sent++;
            } else {
                $this->queue->markFailed((int) $row['id'], 'MailService::send() returned false — see storage/logs for the underlying SMTP error.');
                $failed++;
            }

            if ($row['campaign_id'] !== null) {
                $touchedCampaigns[(int) $row['campaign_id']] = true;
            }
        }

        $campaignModel = new CampaignModel();
        foreach (array_keys($touchedCampaigns) as $campaignId) {
            $campaignModel->refreshStats($campaignId);
            $this->maybeMarkCampaignFullySent($campaignId);
        }

        return ['sent' => $sent, 'failed' => $failed];
    }

    private function markRecipientSent(array $queueRow): void
    {
        if ($queueRow['campaign_recipient_id'] === null) {
            return;
        }

        \App\Core\Database::connection()
            ->prepare("UPDATE campaign_recipients SET status = 'sent', sent_at = NOW() WHERE id = :id AND status = 'pending'")
            ->execute(['id' => $queueRow['campaign_recipient_id']]);
    }

    /** Once every queued email for a campaign has left 'pending', flip the campaign itself from 'sending' to 'sent'. */
    private function maybeMarkCampaignFullySent(int $campaignId): void
    {
        $pdo = \App\Core\Database::connection();
        $stmt = $pdo->prepare("SELECT COUNT(*) FROM email_queue WHERE campaign_id = :id AND status IN ('pending','processing')");
        $stmt->execute(['id' => $campaignId]);

        if ((int) $stmt->fetchColumn() === 0) {
            $pdo->prepare("UPDATE campaigns SET status = 'sent', sent_at = COALESCE(sent_at, NOW()) WHERE id = :id AND status = 'sending'")
                ->execute(['id' => $campaignId]);

            ActivityLogger::record('campaign', $campaignId, 'campaign_sent', 'Campaign finished sending', category: 'system');
        }
    }
}
