<?php

declare(strict_types=1);

namespace App\Services;

use App\Core\Database;
use App\Models\CampaignModel;
use App\Models\EmailTemplateModel;
use DateInterval;
use DateTimeImmutable;

/**
 * CampaignService
 *
 * Owns everything that's more than a plain CampaignModel CRUD call:
 * resolving the audience into real `campaign_recipients` rows,
 * queueing the actual sends (via EmailQueueService, never sending
 * synchronously), scheduling vs. sending immediately, cancellation,
 * and recurring-campaign generation (same real-child-row pattern as
 * Phase 3's recurring appointments).
 */
final class CampaignService
{
    public function __construct(
        private readonly CampaignModel $campaigns = new CampaignModel(),
        private readonly AudienceSegmentService $audience = new AudienceSegmentService(),
        private readonly EmailQueueService $queue = new EmailQueueService()
    ) {
    }

    /** Creates a draft campaign — no recipients or queueing happens until send()/schedule() is called. */
    public function createDraft(array $data, int $actingUserId): int
    {
        $data['status'] = 'draft';
        $recurrence = $data['recurrence_rule'] ?? null;
        $data['is_recurring_parent'] = $recurrence !== null ? 1 : 0;
        $data['recurrence_rule'] = $recurrence !== null ? json_encode($recurrence) : null;

        return $this->campaigns->insert($data, $actingUserId);
    }

    /** Resolves the audience now, stores it as real campaign_recipients rows, and queues every send immediately. */
    public function sendNow(int $campaignId, int $actingUserId): array
    {
        return $this->dispatch($campaignId, now(), $actingUserId);
    }

    /** Same as sendNow() but the queue rows are scheduled_for a future time — the worker just won't pick them up until then. */
    public function schedule(int $campaignId, string $sendAt, int $actingUserId): array
    {
        $this->campaigns->update($campaignId, ['status' => 'scheduled', 'scheduled_at' => $sendAt], $actingUserId);

        return $this->dispatch($campaignId, $sendAt, $actingUserId, queueOnly: true);
    }

    private function dispatch(int $campaignId, string $sendAt, int $actingUserId, bool $queueOnly = false): array
    {
        $campaign = $this->campaigns->findWithRelations($campaignId);
        if ($campaign === null) {
            throw new \RuntimeException('Campaign not found.');
        }

        $definition = $campaign['audience_segment_id'] !== null
            ? $this->savedSegmentDefinition((int) $campaign['audience_segment_id'])
            : json_decode((string) $campaign['audience_filter'], true) ?? [];

        $recipients = $this->audience->resolve($definition);

        if ($recipients === []) {
            return ['success' => false, 'message' => 'No recipients matched this audience — nothing was sent.'];
        }

        Database::transaction(function () use ($campaignId, $recipients, $campaign, $sendAt, $actingUserId) {
            $this->campaigns->addRecipients($campaignId, $recipients);
            $this->campaigns->update($campaignId, ['status' => 'sending'], $actingUserId);

            $freshRecipients = $this->campaigns->recipients($campaignId);

            foreach ($freshRecipients as $recipientRow) {
                $mergeData = ['first_name' => '', 'last_name' => ''];
                // campaign_recipients doesn't store the name, only the
                // email — re-derive first/last name from the original
                // resolve() batch by matching on email.
                foreach ($recipients as $r) {
                    if ($r['email'] === $recipientRow['email_address']) {
                        $mergeData = $r;
                        break;
                    }
                }

                $personalizedSubject = EmailTemplateModel::renderMergeFields((string) $campaign['subject'], $mergeData);
                $personalizedBody = EmailTemplateModel::renderMergeFields((string) $campaign['body_html'], $mergeData)
                    . $this->trackingPixel($recipientRow['unsubscribe_token'])
                    . $this->unsubscribeFooter($recipientRow['unsubscribe_token']);

                $this->queue->enqueue(
                    $recipientRow['email_address'],
                    trim(($mergeData['first_name'] ?? '') . ' ' . ($mergeData['last_name'] ?? '')),
                    $personalizedSubject,
                    $personalizedBody,
                    $campaignId,
                    (int) $recipientRow['id'],
                    $sendAt,
                    $actingUserId,
                    isset($mergeData['business_line_id']) ? (int) $mergeData['business_line_id'] : null
                );
            }
        });

        ActivityLogger::record(
            'campaign',
            $campaignId,
            'campaign_dispatched',
            $queueOnly ? "Campaign scheduled for {$sendAt} (" . count($recipients) . ' recipients)' : 'Campaign sent to ' . count($recipients) . ' recipients',
            ['recipient_count' => count($recipients)]
        );

        return ['success' => true, 'message' => count($recipients) . ' email(s) queued.', 'recipient_count' => count($recipients)];
    }

    public function cancel(int $campaignId, int $actingUserId): void
    {
        $pdo = Database::connection();
        $pdo->prepare("DELETE FROM email_queue WHERE campaign_id = :id AND status = 'pending'")->execute(['id' => $campaignId]);

        $this->campaigns->update($campaignId, ['status' => 'cancelled'], $actingUserId);
        ActivityLogger::record('campaign', $campaignId, 'campaign_cancelled', 'Campaign cancelled', category: 'user_action');
    }

    /**
     * Generates the next occurrence of a recurring campaign as a real
     * child campaign row (draft), following the recurrence_rule on the
     * parent. Call from a scheduled worker (e.g. daily) — it's
     * idempotent per period because it computes the next date strictly
     * after the most recent child.
     */
    public function generateNextOccurrence(int $parentCampaignId): ?int
    {
        $parent = $this->campaigns->findWithRelations($parentCampaignId);
        if ($parent === null || (int) $parent['is_recurring_parent'] !== 1 || $parent['recurrence_rule'] === null) {
            return null;
        }

        $rule = json_decode((string) $parent['recurrence_rule'], true);
        $frequency = $rule['frequency'] ?? 'monthly';
        $interval = max(1, (int) ($rule['interval'] ?? 1));
        $until = isset($rule['until']) ? new DateTimeImmutable($rule['until']) : null;

        $lastChild = Database::connection()->prepare(
            'SELECT scheduled_at FROM campaigns WHERE parent_campaign_id = :id ORDER BY scheduled_at DESC LIMIT 1'
        );
        $lastChild->execute(['id' => $parentCampaignId]);
        $lastSentAt = $lastChild->fetchColumn();

        $baseDate = $lastSentAt ? new DateTimeImmutable($lastSentAt) : new DateTimeImmutable((string) ($parent['scheduled_at'] ?? 'now'));

        $step = match ($frequency) {
            'weekly' => new DateInterval('P' . (7 * $interval) . 'D'),
            'biweekly' => new DateInterval('P' . (14 * $interval) . 'D'),
            'monthly' => new DateInterval("P{$interval}M"),
            default => new DateInterval('P1M'),
        };

        $nextDate = $baseDate->add($step);

        if ($until !== null && $nextDate > $until) {
            return null; // series has ended
        }

        return $this->campaigns->insert([
            'business_line_id' => $parent['business_line_id'],
            'email_template_id' => $parent['email_template_id'],
            'parent_campaign_id' => $parentCampaignId,
            'name' => $parent['name'] . ' — ' . $nextDate->format('M j, Y'),
            'subject' => $parent['subject'],
            'body_html' => $parent['body_html'],
            'audience_segment' => $parent['audience_segment'],
            'audience_filter' => $parent['audience_filter'],
            'audience_segment_id' => $parent['audience_segment_id'],
            'status' => 'draft',
            'scheduled_at' => $nextDate->format('Y-m-d H:i:s'),
        ]);
    }

    private function savedSegmentDefinition(int $segmentId): array
    {
        $stmt = Database::connection()->prepare('SELECT filter_definition FROM audience_segments WHERE id = :id');
        $stmt->execute(['id' => $segmentId]);
        $json = $stmt->fetchColumn();

        return $json ? (json_decode((string) $json, true) ?? []) : [];
    }

    private function trackingPixel(string $token): string
    {
        $url = base_url("track/open/{$token}");

        return "<img src=\"{$url}\" width=\"1\" height=\"1\" style=\"display:none;\" alt=\"\">";
    }

    private function unsubscribeFooter(string $token): string
    {
        $url = base_url("unsubscribe/{$token}");

        return "<p style=\"font-size:11px;color:#98A2B3;margin-top:24px;\">"
            . "You're receiving this because you're a valued contact of A&amp;B Homecare Services / A&amp;B Cleaner Janitorial Services. "
            . "<a href=\"{$url}\" style=\"color:#98A2B3;\">Unsubscribe</a></p>";
    }
}
