<?php

namespace App\Services\Promotions;

use App\Models\PromotionReminderLog;
use App\Models\Student;
use App\Models\StudentPromotionRecord;
use Carbon\CarbonImmutable;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

/**
 * Sends reminders to parents and persists a log entry per dispatch
 * (plan section 14).
 *
 * The actual delivery is delegated to whichever notification dispatcher
 * is registered. A nullable dispatcher is supported so the service can
 * be used from CLI/tests without external side effects.
 */
class PromotionReminderService
{
    /** @var (callable(int $userId, string $title, string $body, array $channels):void)|null */
    private $dispatcher;

    public function __construct(
        private PromotionAuditService $audit,
        ?callable $dispatcher = null
    ) {
        $this->dispatcher = $dispatcher;
    }

    /**
     * Records a single reminder dispatch.
     */
    public function send(
        StudentPromotionRecord $record,
        string $reminderType,
        ?int $userId = null,
        ?string $subject = null,
        ?string $message = null,
        array $channels = ['in_app', 'email']
    ): PromotionReminderLog {
        if (!in_array($reminderType, PromotionReminderLog::ALLOWED_TYPES, true)) {
            $reminderType = PromotionReminderLog::TYPE_MANUAL;
        }

        $studentName = $this->studentName($record);
        $deadline = $record->enrollment_deadline?->toDateString();
        $level = $record->promoted_level_name ?: 'the next level';

        $defaultSubject = 'Reminder: complete enrollment for the next school year';
        $defaultMessage = sprintf(
            '%s is eligible for promotion to %s.%s Please complete enrollment as soon as possible.',
            $studentName ?: 'Your child',
            $level,
            $deadline ? ' Deadline: ' . $deadline . '.' : ''
        );

        $finalSubject = $subject ?: $defaultSubject;
        $finalMessage = $message ?: $defaultMessage;

        return DB::transaction(function () use (
            $record,
            $reminderType,
            $finalSubject,
            $finalMessage,
            $channels,
            $userId
        ) {
            $log = PromotionReminderLog::query()->create([
                'promotion_id' => (int) $record->getKey(),
                'student_id' => (int) $record->student_id,
                'parent_id' => (int) $record->parent_id ?: null,
                'reminder_type' => $reminderType,
                'channel' => implode(',', array_values(array_unique(array_filter($channels)))),
                'subject' => $finalSubject,
                'message' => $finalMessage,
                'sent_at' => now(),
                'sent_by' => $userId,
            ]);

            if ($record->parent_id) {
                $this->dispatch((int) $record->parent_id, $finalSubject, $finalMessage, $channels);
            }

            if (!$record->parent_notified_at) {
                $record->parent_notified_at = now();
                $record->save();
                $this->audit->logParentNotified($record, $userId, $reminderType);
            }
            $this->audit->logReminderSent($record, $reminderType, $userId);

            return $log;
        });
    }

    /**
     * Plan section 14 – schedule-driven reminders. Sends the next due
     * reminder type for every actionable record whose deadline is set.
     *
     * @return array{ processed:int, sent:int, skipped:int }
     */
    public function dispatchScheduledReminders(?\DateTimeInterface $now = null, ?int $userId = null): array
    {
        $now = CarbonImmutable::instance($now ?: now());

        $records = StudentPromotionRecord::query()
            ->whereIn('promotion_status', StudentPromotionRecord::parentActionableStatuses())
            ->whereNotNull('enrollment_deadline')
            ->get();

        $processed = 0;
        $sent = 0;
        $skipped = 0;

        foreach ($records as $record) {
            $processed++;
            $type = $this->resolveReminderType($record, $now);
            if ($type === null) {
                $skipped++;
                continue;
            }
            $alreadySent = PromotionReminderLog::query()
                ->where('promotion_id', $record->getKey())
                ->where('reminder_type', $type)
                ->exists();
            if ($alreadySent) {
                $skipped++;
                continue;
            }
            try {
                $this->send($record, $type, $userId);
                $sent++;
            } catch (\Throwable $e) {
                Log::error('Promotion reminder dispatch failed', [
                    'promotion_id' => $record->getKey(),
                    'exception' => $e,
                ]);
                $skipped++;
            }
        }

        return [
            'processed' => $processed,
            'sent' => $sent,
            'skipped' => $skipped,
        ];
    }

    /**
     * Returns the reminder type that should be sent next based on the
     * deadline / current time.
     */
    private function resolveReminderType(StudentPromotionRecord $record, CarbonImmutable $now): ?string
    {
        $deadline = $record->enrollment_deadline ? CarbonImmutable::instance($record->enrollment_deadline) : null;
        if ($deadline === null) {
            return null;
        }
        $createdAt = $record->created_at ? CarbonImmutable::instance($record->created_at) : $now;

        if ($now->greaterThan($deadline)) {
            return PromotionReminderLog::TYPE_EXPIRATION;
        }

        $totalSeconds = max(1, $deadline->getTimestamp() - $createdAt->getTimestamp());
        $elapsedSeconds = max(0, $now->getTimestamp() - $createdAt->getTimestamp());
        $remainingSeconds = max(0, $deadline->getTimestamp() - $now->getTimestamp());
        $remainingDays = (int) floor($remainingSeconds / 86400);

        if ($elapsedSeconds === 0) {
            return PromotionReminderLog::TYPE_FIRST;
        }
        if ($remainingDays <= 3) {
            return PromotionReminderLog::TYPE_FINAL;
        }
        if ($elapsedSeconds * 2 >= $totalSeconds) {
            return PromotionReminderLog::TYPE_HALFWAY;
        }
        return PromotionReminderLog::TYPE_FIRST;
    }

    private function studentName(StudentPromotionRecord $record): ?string
    {
        $student = Student::query()->find($record->student_id);
        if (!$student) {
            return null;
        }
        $name = trim((string) ($student->firstname ?? '') . ' ' . (string) ($student->lastname ?? ''));
        return $name !== '' ? $name : null;
    }

    private function dispatch(int $userId, string $subject, string $message, array $channels): void
    {
        if (!$this->dispatcher) {
            return;
        }
        try {
            ($this->dispatcher)($userId, $subject, $message, $channels);
        } catch (\Throwable $e) {
            Log::error('Promotion reminder dispatcher threw', [
                'user_id' => $userId,
                'exception' => $e,
            ]);
        }
    }
}
