<?php

namespace App\Services;

use App\Models\SelfPaced\Course;
use App\Models\SelfPaced\CourseCategory;
use App\Models\SelfPaced\CourseTopic;
use App\Models\SelfPaced\CourseSubTopic;
use App\Models\SelfPaced\Enrollment;
use App\Models\SelfPaced\LearningProgress;
use App\Models\SelfPaced\TopicTimer;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use App\Services\NotificationService;

/**
 * SelfPacedService
 *
 * Core business logic for the Self-Paced Learning Portal.
 * Handles course management, enrollment lifecycle, payment integration,
 * learning progress tracking, and topic timer management.
 */
class SelfPacedService
{
    public function __construct(
        protected PaymentService $paymentService,
        protected NotificationService $notificationService
    ) {}

    // =========================================================================
    // COURSE MANAGEMENT
    // =========================================================================

    /**
     * Create a new course.
     */
    public function createCourse(array $data, int $createdBy): Course
    {
        $data['slug']       = $this->generateUniqueSlug($data['title'], Course::class);
        $data['created_by'] = $createdBy;
        $data['updated_by'] = $createdBy;

        return Course::create($data);
    }

    /**
     * Update an existing course.
     */
    public function updateCourse(Course $course, array $data, int $updatedBy): Course
    {
        if (isset($data['title']) && $data['title'] !== $course->title) {
            $data['slug'] = $this->generateUniqueSlug($data['title'], Course::class, $course->id);
        }

        $data['updated_by'] = $updatedBy;
        $course->update($data);

        return $course->fresh();
    }

    /**
     * Delete a course — blocked if active enrollments exist.
     *
     * @throws \RuntimeException
     */
    public function deleteCourse(Course $course): bool
    {
        if ($course->hasActiveEnrollments()) {
            $count = $course->enrollments()
                ->whereIn('status', ['active', 'pending_payment'])
                ->count();

            throw new \RuntimeException(
                "Cannot delete course: {$count} active enrollment(s) exist."
            );
        }

        Log::info('Self-paced course deleted', ['course_id' => $course->id, 'title' => $course->title]);

        return $course->delete();
    }

    // =========================================================================
    // ENROLLMENT
    // =========================================================================

    /**
     * Create an enrollment for a user in a course.
     *
     * - Free courses → status set to "active" immediately, no MoMo call.
     * - Paid courses → status "pending_payment", MoMo collection initiated.
     *
     * Property 1: Enrollment uniqueness invariant.
     * Property 8: Free course enrollment bypass.
     *
     * @throws \RuntimeException if a duplicate active/completed enrollment exists.
     */
    public function createEnrollment(
        int $userId,
        Course $course,
        string $phoneNumber = '',
        int $createdBy = 0,
        string $paymentMethod = 'mobile',
        ?string $cardNumber = null,
        ?string $cardExpiry = null,
        ?string $cardCvv = null
    ): array {
        // Guard: duplicate enrollment check
        $existing = Enrollment::where('user_id', $userId)
            ->where('course_id', $course->id)
            ->whereIn('status', ['active', 'completed', 'pending_payment'])
            ->first();

        if ($existing) {
            throw new \RuntimeException('You are already enrolled in this course.');
        }

        return DB::transaction(function () use ($userId, $course, $phoneNumber, $createdBy, $paymentMethod, $cardNumber, $cardExpiry, $cardCvv) {
            // Free course — activate immediately
            if ($course->isFree()) {
                $enrollment = Enrollment::create([
                    'user_id'     => $userId,
                    'course_id'   => $course->id,
                    'status'      => 'active',
                    'amount_paid' => 0.00,
                    'enrolled_at' => now(),
                    'created_by'  => $createdBy ?: $userId,
                ]);

                Log::info('Free course enrollment activated', [
                    'enrollment_id' => $enrollment->id,
                    'user_id'       => $userId,
                    'course_id'     => $course->id,
                ]);

                return ['enrollment' => $enrollment, 'payment' => null, 'success' => true];
            }

            // Paid course — create pending enrollment first
            $reference = 'SP-ENR-' . strtoupper(Str::random(10));

            $storedMethod = $paymentMethod === 'card' ? 'mtn_momo_card' : 'mtn_momo_mobile';

            $enrollment = Enrollment::create([
                'user_id'           => $userId,
                'course_id'         => $course->id,
                'status'            => 'pending_payment',
                'payment_reference' => $reference,
                'payment_method'    => $storedMethod,
                'amount_paid'       => $course->fee_amount,
                'created_by'        => $createdBy ?: $userId,
            ]);

            // Initiate MTN MoMo collection (mobile or card)
            if ($paymentMethod === 'card') {
                $paymentResult = $this->paymentService->initiateCardCollection(
                    cardNumber: $cardNumber ?? '',
                    expiry: $cardExpiry ?? '',
                    cvv: $cardCvv ?? '',
                    amount: (float) $course->fee_amount,
                    reference: $reference,
                    payerMessage: "Payment for course: {$course->title}",
                    payeeNote: "Self-Paced Course Enrollment #{$enrollment->id}"
                );
            } else {
                $paymentResult = $this->paymentService->initiateCollection(
                    phoneNumber: $phoneNumber,
                    amount: (float) $course->fee_amount,
                    reference: $reference,
                    payerMessage: "Payment for course: {$course->title}",
                    payeeNote: "Self-Paced Course Enrollment #{$enrollment->id}"
                );
            }

            if (!$paymentResult['success']) {
                $enrollment->update(['status' => 'payment_failed']);

                Log::warning('Self-paced enrollment payment initiation failed', [
                    'enrollment_id' => $enrollment->id,
                    'error'         => $paymentResult['message'],
                ]);

                return [
                    'enrollment' => $enrollment,
                    'payment'    => $paymentResult,
                    'success'    => false,
                ];
            }

            Log::info('Self-paced enrollment payment initiated', [
                'enrollment_id'  => $enrollment->id,
                'transaction_id' => $paymentResult['transaction_id'],
            ]);

            return [
                'enrollment' => $enrollment,
                'payment'    => $paymentResult,
                'success'    => true,
            ];
        });
    }

    /**
     * Poll MTN MoMo and update enrollment status accordingly.
     */
    public function checkPaymentStatus(Enrollment $enrollment): Enrollment
    {
        if (!$enrollment->isPendingPayment()) {
            return $enrollment;
        }

        if (empty($enrollment->payment_reference)) {
            return $enrollment;
        }

        $result = $this->paymentService->checkTransactionStatus(
            $enrollment->payment_reference,
            'collection'
        );

        $momoStatus = strtoupper($result['status'] ?? '');

        if ($momoStatus === 'SUCCESSFUL') {
            $enrollment->update([
                'status'       => 'active',
                'enrolled_at'  => now(),
                'payment_date' => now(),
            ]);

            Log::info('Self-paced enrollment activated after payment', [
                'enrollment_id' => $enrollment->id,
            ]);

            try {
                $this->notificationService->sendSelfPacedPaymentReceipt($enrollment->fresh());
            } catch (\Throwable $e) {
                Log::error('Failed to send self-paced payment receipt notification', [
                    'enrollment_id' => $enrollment->id,
                    'error'         => $e->getMessage(),
                ]);
            }
        } elseif (in_array($momoStatus, ['FAILED', 'REJECTED', 'TIMEOUT', 'EXPIRED'], true)) {
            $enrollment->update(['status' => 'payment_failed']);

            Log::warning('Self-paced enrollment payment failed', [
                'enrollment_id' => $enrollment->id,
                'momo_status'   => $momoStatus,
            ]);

            try {
                $this->notificationService->sendSelfPacedPaymentFailed($enrollment->fresh());
            } catch (\Throwable $e) {
                Log::error('Failed to send self-paced payment failed notification', [
                    'enrollment_id' => $enrollment->id,
                    'error'         => $e->getMessage(),
                ]);
            }
        }

        return $enrollment->fresh();
    }

    /**
     * Retry payment for a failed enrollment.
     *
     * @throws \RuntimeException
     */
    public function retryPayment(
        Enrollment $enrollment,
        string $phoneNumber,
        string $paymentMethod = 'mobile',
        ?string $cardNumber = null,
        ?string $cardExpiry = null,
        ?string $cardCvv = null
    ): array {
        if (!$enrollment->isPaymentFailed()) {
            throw new \RuntimeException('Only failed enrollments can be retried.');
        }

        $reference    = 'SP-ENR-' . strtoupper(Str::random(10));
        $storedMethod = $paymentMethod === 'card' ? 'mtn_momo_card' : 'mtn_momo_mobile';

        $enrollment->update([
            'status'            => 'pending_payment',
            'payment_reference' => $reference,
            'payment_method'    => $storedMethod,
        ]);

        if ($paymentMethod === 'card') {
            $paymentResult = $this->paymentService->initiateCardCollection(
                cardNumber: $cardNumber ?? '',
                expiry: $cardExpiry ?? '',
                cvv: $cardCvv ?? '',
                amount: (float) $enrollment->course->fee_amount,
                reference: $reference,
                payerMessage: "Payment for course: {$enrollment->course->title}",
                payeeNote: "Self-Paced Course Enrollment #{$enrollment->id}"
            );
        } else {
            $paymentResult = $this->paymentService->initiateCollection(
                phoneNumber: $phoneNumber,
                amount: (float) $enrollment->course->fee_amount,
                reference: $reference,
                payerMessage: "Payment for course: {$enrollment->course->title}",
                payeeNote: "Self-Paced Course Enrollment #{$enrollment->id}"
            );
        }

        if (!$paymentResult['success']) {
            $enrollment->update(['status' => 'payment_failed']);
        }

        return ['enrollment' => $enrollment->fresh(), 'payment' => $paymentResult];
    }

    // =========================================================================
    // LEARNING PROGRESS
    // =========================================================================

    /**
     * Mark a subtopic as read for an enrollment.
     * Returns the progress record (idempotent via firstOrCreate).
     */
    public function markSubtopicRead(Enrollment $enrollment, CourseSubTopic $subtopic): LearningProgress
    {
        $progress = LearningProgress::markRead(
            enrollmentId: $enrollment->id,
            userId: $enrollment->user_id,
            subtopicId: $subtopic->id,
            topicId: $subtopic->topic_id,
            courseId: $enrollment->course_id
        );

        // Check if the parent topic is now fully complete
        $this->checkTopicCompletion($enrollment, $subtopic->topic);

        return $progress;
    }

    /**
     * Check if all published subtopics of a topic are read; if so, mark enrollment
     * as completed when all topics are done.
     */
    protected function checkTopicCompletion(Enrollment $enrollment, CourseTopic $topic): void
    {
        if (!$topic->isCompletedForEnrollment($enrollment->id)) {
            return;
        }

        // Check if ALL topics in the course are now complete
        $allTopics = $topic->course->topics()->where('is_published', true)->get();
        $allComplete = $allTopics->every(
            fn (CourseTopic $t) => $t->isCompletedForEnrollment($enrollment->id)
        );

        if ($allComplete && $enrollment->isActive()) {
            // Content phase complete — enrollment stays "active" until exam passed
            Log::info('All topics completed for enrollment', ['enrollment_id' => $enrollment->id]);
        }
    }

    /**
     * Get the last visited subtopic for an enrollment (for resume logic).
     *
     * Returns the next unread subtopic after the last read one, or falls back
     * to the first published subtopic when all subtopics are read (e.g. completed
     * learners re-reading the course). Never returns null for a course that has
     * at least one published subtopic — fixes Requirement 5.3, 5.6.
     */
    public function getLastVisitedSubtopic(Enrollment $enrollment): ?CourseSubTopic
    {
        $lastProgress = $enrollment->learningProgress()
            ->latest('marked_read_at')
            ->first();

        if (!$lastProgress) {
            // No progress yet — start from the very first published subtopic
            return $this->getFirstSubtopic($enrollment->course_id);
        }

        // Try to return the next unread subtopic after the last read one
        $lastSubtopic = CourseSubTopic::find($lastProgress->subtopic_id);

        if (!$lastSubtopic) {
            // Subtopic was deleted — fall back to first subtopic
            return $this->getFirstSubtopic($enrollment->course_id);
        }

        // Find next subtopic in same topic
        $next = CourseSubTopic::where('topic_id', $lastSubtopic->topic_id)
            ->where('is_published', true)
            ->where('sort_order', '>', $lastSubtopic->sort_order)
            ->orderBy('sort_order')
            ->first();

        if ($next) {
            return $next;
        }

        // Find first subtopic of next topic
        $nextTopic = CourseTopic::where('course_id', $enrollment->course_id)
            ->where('is_published', true)
            ->where('sort_order', '>', $lastSubtopic->topic->sort_order)
            ->orderBy('sort_order')
            ->first();

        if ($nextTopic) {
            return $nextTopic->subtopics()->where('is_published', true)->orderBy('sort_order')->first();
        }

        // All subtopics read — fall back to first subtopic so completed learners
        // can still re-read the course (fixes Requirement 5.3, 5.6).
        return $this->getFirstSubtopic($enrollment->course_id);
    }

    /**
     * Return the first published subtopic of a course, ordered by sort_order.
     * Used as a fallback when no "next" subtopic can be determined.
     */
    private function getFirstSubtopic(int $courseId): ?CourseSubTopic
    {
        return CourseSubTopic::whereHas(
            'topic',
            fn ($q) => $q->where('course_id', $courseId)->where('is_published', true)
        )
            ->where('is_published', true)
            ->orderBy('sort_order')
            ->first();
    }

    // =========================================================================
    // TOPIC TIMER
    // =========================================================================

    /**
     * Get or create a TopicTimer for an enrollment + topic.
     */
    public function getOrCreateTopicTimer(Enrollment $enrollment, CourseTopic $topic): TopicTimer
    {
        return TopicTimer::getOrCreate(
            enrollmentId: $enrollment->id,
            userId: $enrollment->user_id,
            topicId: $topic->id
        );
    }

    /**
     * Add elapsed seconds to a topic timer (called by the 30-second ping).
     */
    public function updateTopicTimer(Enrollment $enrollment, CourseTopic $topic, int $seconds = 30): TopicTimer
    {
        $timer = $this->getOrCreateTopicTimer($enrollment, $topic);
        $timer->addSeconds($seconds);

        return $timer->fresh();
    }

    // =========================================================================
    // HELPERS
    // =========================================================================

    /**
     * Generate a unique slug for a given model class.
     */
    protected function generateUniqueSlug(string $title, string $modelClass, ?int $excludeId = null): string
    {
        $base = Str::slug($title);
        $slug = $base;
        $i    = 1;

        while (
            $modelClass::withTrashed()
                ->where('slug', $slug)
                ->when($excludeId, fn ($q) => $q->where('id', '!=', $excludeId))
                ->exists()
        ) {
            $slug = "{$base}-{$i}";
            $i++;
        }

        return $slug;
    }

    // =========================================================================
    // CALLBACK HANDLER (called by MomoCallbackController)
    // =========================================================================

    /**
     * Handle an MTN MoMo payment callback for a self-paced enrollment.
     *
     * Mirrors the logic in checkPaymentStatus but driven by the webhook
     * rather than polling, so no API call is needed.
     */
    public function handlePaymentCallback(string $reference, string $status, array $data = []): void
    {
        $enrollment = \App\Models\SelfPaced\Enrollment::where('payment_reference', $reference)->first();

        if (!$enrollment) {
            Log::warning('MoMo callback: SelfPaced Enrollment not found', ['reference' => $reference]);
            return;
        }

        $momoStatus = strtoupper($status);

        if ($momoStatus === 'SUCCESSFUL') {
            $enrollment->update([
                'status'       => 'active',
                'enrolled_at'  => now(),
                'payment_date' => now(),
            ]);

            Log::info('Self-paced enrollment activated via MoMo callback', [
                'enrollment_id' => $enrollment->id,
                'reference'     => $reference,
            ]);

            try {
                $this->notificationService->sendSelfPacedPaymentReceipt($enrollment->fresh());
            } catch (\Throwable $e) {
                Log::error('Failed to send self-paced payment receipt (callback)', [
                    'enrollment_id' => $enrollment->id,
                    'error'         => $e->getMessage(),
                ]);
            }
        } elseif (in_array($momoStatus, ['FAILED', 'REJECTED', 'TIMEOUT', 'EXPIRED'], true)) {
            $enrollment->update(['status' => 'payment_failed']);

            Log::warning('Self-paced enrollment payment failed via MoMo callback', [
                'enrollment_id' => $enrollment->id,
                'status'        => $momoStatus,
            ]);

            try {
                $this->notificationService->sendSelfPacedPaymentFailed($enrollment->fresh());
            } catch (\Throwable $e) {
                Log::error('Failed to send self-paced payment failed notification (callback)', [
                    'enrollment_id' => $enrollment->id,
                    'error'         => $e->getMessage(),
                ]);
            }
        }
    }

    // =========================================================================
    // PAYSTACK WEBHOOK HANDLING
    // =========================================================================

    /**
     * Handle Paystack charge.success webhook event
     *
     * Routes events by reference prefix to appropriate handler.
     *
     * @param array $event Paystack webhook event data
     * @return void
     *
     * Requirements: 1.7, 11.1, 11.6
     */
    public function handlePaystackWebhook(array $event): void
    {
        $reference = $event['data']['reference'] ?? null;

        if (!$reference) {
            Log::warning('Paystack webhook: missing reference', ['event' => $event]);
            return;
        }

        // Route by reference prefix
        if (str_starts_with($reference, 'SP-ENR-')) {
            $this->handlePaystackEnrollmentWebhook($event);
        } elseif (str_starts_with($reference, 'SP-CERT-')) {
            $this->handlePaystackCertificateWebhook($event);
        } elseif (str_starts_with($reference, 'SP-BOOK-')) {
            $this->handlePaystackBookWebhook($event);
        } elseif (str_starts_with($reference, 'SP-RESIT-')) {
            $this->handlePaystackResitWebhook($event);
        } else {
            Log::warning('Paystack webhook: unknown reference prefix', ['reference' => $reference]);
        }
    }

    /**
     * Handle enrollment payment via Paystack webhook
     *
     * @param array $event
     * @return void
     */
    protected function handlePaystackEnrollmentWebhook(array $event): void
    {
        $reference = $event['data']['reference'];
        $enrollment = Enrollment::where('payment_reference', $reference)->first();

        if (!$enrollment) {
            Log::warning('Paystack webhook: Enrollment not found', ['reference' => $reference]);
            return;
        }

        // Idempotency check: already activated
        if ($enrollment->status === 'active') {
            Log::info('Paystack webhook: Enrollment already active (idempotent)', ['reference' => $reference]);
            return;
        }

        $enrollment->update([
            'status' => 'active',
            'payment_date' => now(),
        ]);

        Log::info('Self-paced enrollment activated via Paystack webhook', ['enrollment_id' => $enrollment->id]);
    }

    /**
     * Handle certificate fee payment via Paystack webhook
     *
     * @param array $event
     * @return void
     */
    protected function handlePaystackCertificateWebhook(array $event): void
    {
        // Certificate payments are handled synchronously in controller
        Log::debug('Paystack webhook: Certificate payment processed', ['reference' => $event['data']['reference']]);
    }

    /**
     * Handle book purchase via Paystack webhook
     *
     * @param array $event
     * @return void
     */
    protected function handlePaystackBookWebhook(array $event): void
    {
        // Book purchases are handled synchronously in controller
        Log::debug('Paystack webhook: Book purchase processed', ['reference' => $event['data']['reference']]);
    }

    /**
     * Handle re-sit fee payment via Paystack webhook
     *
     * @param array $event
     * @return void
     */
    protected function handlePaystackResitWebhook(array $event): void
    {
        // Re-sit payments are handled synchronously in controller
        Log::debug('Paystack webhook: Re-sit payment processed', ['reference' => $event['data']['reference']]);
    }

    // =========================================================================
    // EXAM UNLOCK AND RE-SIT
    // =========================================================================

    /**
     * Check if all topics are complete and unlock exam if so
     *
     * @param Enrollment $enrollment
     * @return void
     *
     * Requirements: 4.5, 6.2
     */
    public function checkAndUnlockExam(Enrollment $enrollment): void
    {
        // If already unlocked, skip
        if ($enrollment->exam_unlocked_at !== null) {
            return;
        }

        $course = $enrollment->course;
        $publishedTopics = $course->topics()->where('is_published', true)->get();

        if ($publishedTopics->isEmpty()) {
            return;
        }

        // Check if all published topics are complete
        $allComplete = $publishedTopics->every(function ($topic) use ($enrollment) {
            return $topic->isCompletedForEnrollment($enrollment->id);
        });

        if ($allComplete) {
            $enrollment->update(['exam_unlocked_at' => now()]);
            Log::info('Exam unlocked for enrollment', ['enrollment_id' => $enrollment->id]);
            
            // TODO: Send exam unlocked notification
        }
    }

    /**
     * Set content access expiry to 30 days from now
     *
     * @param Enrollment $enrollment
     * @return void
     *
     * Requirements: 5.2
     */
    public function setContentAccessExpiry(Enrollment $enrollment): void
    {
        if ($enrollment->content_access_expires_at === null) {
            $enrollment->update([
                'content_access_expires_at' => now()->addDays(30),
            ]);
            Log::info('Content access expiry set', ['enrollment_id' => $enrollment->id]);
        }
    }

    /**
     * Reset progress and unlock exam for re-sit
     *
     * Called after successful re-sit fee payment.
     *
     * @param Enrollment $enrollment
     * @return void
     *
     * Requirements: 18.10, 18.11, 18.12, 18.13
     */
    public function performResitReset(Enrollment $enrollment): void
    {
        DB::transaction(function () use ($enrollment) {
            // Delete all learning progress
            LearningProgress::where('enrollment_id', $enrollment->id)->delete();

            // Reset exam state
            $enrollment->update([
                'exam_exhausted' => false,
                'exam_unlocked_at' => null,
                'resit_attempt_count' => $enrollment->resit_attempt_count + 1,
            ]);

            Log::info('Enrollment re-sit reset', [
                'enrollment_id' => $enrollment->id,
                'resit_number' => $enrollment->resit_attempt_count,
            ]);
        });
    }

    /**
     * Check if a topic is accessible for an enrollment (sequential unlocking)
     *
     * @param Enrollment $enrollment
     * @param CourseTopic $topic
     * @return bool
     *
     * Requirements: 4.2, 4.3
     */
    public function isTopicAccessible(Enrollment $enrollment, CourseTopic $topic): bool
    {
        return $topic->isAccessibleForEnrollment($enrollment->id);
    }
}
