<?php

namespace App\Services;

use App\Models\Campaign;
use App\Models\CampaignEvent;
use App\Models\Contact;
use App\Jobs\ProcessSequenceStepJob;

class SequenceEngine
{
    /**
     * Enroll a contact into a sequence, starting its first email step.
     * $startDelaySeconds pushes back the first step's dispatch (used for a
     * "start at a scheduled date/time" sequence launch); each subsequent
     * step still uses its own configured delay from there.
     */
    public static function enroll(Campaign $sequence, Contact $contact, int $startDelaySeconds = 0): void
    {
        $hasEnrolled = CampaignEvent::where('campaign_id', $sequence->id)
            ->where(function ($q) use ($contact) {
                $q->where('contact_id', $contact->id);
                if ($contact->email) {
                    $q->orWhereHas('contact', fn($c) => $c->withoutGlobalScopes()->where('email', $contact->email));
                }
            })
            ->where('event_type', 'enrolled')
            ->exists();

        if ($hasEnrolled) {
            return;
        }

        // Record enrollment event
        CampaignEvent::create([
            'campaign_id' => $sequence->id,
            'contact_id' => $contact->id,
            'event_type' => 'enrolled',
        ]);

        $contact->logActivity('sequence_enrolled', "Enrolled in sequence: \"{$sequence->title}\"");
        $sequence->increment('recipients_count');

        $steps = $sequence->settings['steps'] ?? [];
        if (empty($steps)) {
            self::complete($sequence, $contact);
            return;
        }

        // Dispatch the first step
        $firstStep = $steps[0];
        $delaySeconds = $startDelaySeconds + self::parseDelay($firstStep['delay'] ?? []);

        ProcessSequenceStepJob::dispatch($sequence->id, $contact->id, $firstStep['id'])
            ->delay(now()->addSeconds($delaySeconds));
    }

    public static function complete(Campaign $sequence, Contact $contact): void
    {
        // Record completion event
        CampaignEvent::create([
            'campaign_id' => $sequence->id,
            'contact_id' => $contact->id,
            'event_type' => 'completed',
        ]);

        // Apply completion tags
        $completionTags = $sequence->settings['completion_tags'] ?? [];
        foreach ($completionTags as $tagName) {
            $tag = \App\Models\Tag::firstOrCreate(
                [
                    'title' => $tagName,
                    'user_id' => $sequence->user_id,
                ],
                [
                    'slug' => \Illuminate\Support\Str::slug($tagName) . '-' . rand(100, 999),
                ]
            );
            $contact->tags()->syncWithoutDetaching([$tag->id]);
        }

        $contact->logActivity('sequence_completed', "Completed sequence: \"{$sequence->title}\"");

        self::maybeMarkSequenceCompleted($sequence);
    }

    /**
     * Ends a contact's journey through the sequence on a bad outcome
     * (domain-level bounce, or the job permanently failed after retries)
     * rather than finishing every step. Unlike complete(), this does NOT
     * apply completion tags - the contact never actually received
     * everything - but it still counts toward "is this sequence done",
     * otherwise one bad address would leave the sequence stuck "Active"
     * forever with nothing left to send.
     */
    public static function completeAsTerminal(Campaign $sequence, Contact $contact): void
    {
        $contact->logActivity('sequence_stopped', "Sequence \"{$sequence->title}\" stopped early (bounce or send failure).");

        self::maybeMarkSequenceCompleted($sequence);
    }

    /**
     * Once every enrolled contact has finished - reached `completed`,
     * `bounced`, or the terminal `failed` state - flip the sequence itself
     * to `completed` so it stops showing as perpetually "Active" once
     * there's nothing left to send. Only meaningful for a running sequence -
     * draft/paused sequences are left alone.
     */
    private static function maybeMarkSequenceCompleted(Campaign $sequence): void
    {
        if ($sequence->status !== 'active') {
            return;
        }

        $enrolledCount = CampaignEvent::where('campaign_id', $sequence->id)
            ->where('event_type', 'enrolled')
            ->distinct('contact_id')
            ->count('contact_id');

        if ($enrolledCount === 0) {
            return;
        }

        $doneCount = CampaignEvent::where('campaign_id', $sequence->id)
            ->whereIn('event_type', ['completed', 'bounced', 'failed'])
            ->distinct('contact_id')
            ->count('contact_id');

        if ($doneCount >= $enrolledCount) {
            $sequence->update(['status' => 'completed']);
        }
    }

    public static function parseDelay(array $delay): int
    {
        $amount = (int) ($delay['amount'] ?? 0);
        $unit = $delay['unit'] ?? 'days';

        if ($amount <= 0) {
            return 0;
        }

        return match ($unit) {
            'minutes' => $amount * 60,
            'hours' => $amount * 3600,
            'weeks' => $amount * 7 * 86400,
            default => $amount * 86400, // days
        };
    }
}
