<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Str;
use Carbon\Carbon;

class QuizAttempt extends Model
{
    use HasFactory;

    protected $fillable = [
        'quiz_id',
        'user_id',
        'attempt_id',
        'status',
        'current_state',
        'settings',
        'metadata',
        'total_score',
        'max_score',
        'percentage',
        'time_spent',
        'questions_answered',
        'total_questions',
        'started_at',
        'last_activity_at',
        'completed_at',
        'expires_at',
        'ip_address',
        'user_agent',
    ];

    protected $casts = [
        'current_state' => 'array',
        'settings' => 'array',
        'metadata' => 'array',
        'total_score' => 'decimal:2',
        'max_score' => 'decimal:2',
        'percentage' => 'decimal:2',
        'started_at' => 'datetime',
        'last_activity_at' => 'datetime',
        'completed_at' => 'datetime',
        'expires_at' => 'datetime',
    ];

    protected $appends = [
        'is_expired',
        'is_in_progress',
        'is_completed',
        'time_remaining',
        'progress_percentage',
    ];

    // Relationships
    public function quiz(): BelongsTo
    {
        return $this->belongsTo(Quiz::class);
    }

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    public function answers(): HasMany
    {
        return $this->hasMany(QuizAttemptAnswer::class);
    }

    // Scopes
    public function scopeInProgress(Builder $query): Builder
    {
        return $query->where('status', 'in_progress');
    }

    public function scopeCompleted(Builder $query): Builder
    {
        return $query->where('status', 'completed');
    }

    public function scopeByUser(Builder $query, int $userId): Builder
    {
        return $query->where('user_id', $userId);
    }

    public function scopeByQuiz(Builder $query, int $quizId): Builder
    {
        return $query->where('quiz_id', $quizId);
    }

    public function scopeNotExpired(Builder $query): Builder
    {
        return $query->where(function ($q) {
            $q->whereNull('expires_at')
              ->orWhere('expires_at', '>', now());
        });
    }

    // Accessors
    public function getIsExpiredAttribute(): bool
    {
        return $this->expires_at && $this->expires_at->isPast();
    }

    public function getIsInProgressAttribute(): bool
    {
        return $this->status === 'in_progress';
    }

    public function getIsCompletedAttribute(): bool
    {
        return $this->status === 'completed';
    }

    public function getTimeRemainingAttribute(): ?int
    {
        if (!$this->expires_at) {
            return null;
        }

        $remaining = $this->expires_at->diffInSeconds(now());
        return max(0, $remaining);
    }

    public function getProgressPercentageAttribute(): float
    {
        if ($this->total_questions === 0) {
            return 0;
        }

        return round(($this->questions_answered / $this->total_questions) * 100, 2);
    }

    // Methods
    public static function createNew(int $quizId, int $userId, array $settings = []): self
    {
        $quiz = Quiz::findOrFail($quizId);
        
        // Check if user can attempt this quiz
        if (!$quiz->canBeAttemptedBy(User::find($userId))) {
            throw new \Exception('User cannot attempt this quiz');
        }

        // Check for existing in-progress attempt
        $existingAttempt = $quiz->getInProgressAttempt(User::find($userId));
        if ($existingAttempt) {
            return $existingAttempt;
        }

        $attempt = static::create([
            'quiz_id' => $quizId,
            'user_id' => $userId,
            'attempt_id' => Str::uuid(),
            'status' => 'in_progress',
            'current_state' => [
                'current_question_id' => null,
                'answered_questions' => [],
                'skipped_questions' => [],
                'navigation_history' => [],
            ],
            'settings' => array_merge($quiz->settings ?? [], $settings),
            'total_questions' => $quiz->total_questions,
            'max_score' => $quiz->total_points,
            'started_at' => now(),
            'last_activity_at' => now(),
            'expires_at' => $this->calculateExpiryTime($quiz),
            'ip_address' => request()->ip(),
            'user_agent' => request()->userAgent(),
        ]);

        // Set first question
        $firstQuestion = $quiz->questions()->first();
        if ($firstQuestion) {
            $attempt->setCurrentQuestion($firstQuestion->id);
        }

        return $attempt;
    }

    public function resume(): self
    {
        if ($this->is_expired) {
            $this->update(['status' => 'expired']);
            throw new \Exception('Attempt has expired');
        }

        $this->update(['last_activity_at' => now()]);
        return $this;
    }

    public function setCurrentQuestion(int $questionId): void
    {
        $currentState = $this->current_state;
        $currentState['current_question_id'] = $questionId;
        
        // Add to navigation history
        if (!in_array($questionId, $currentState['navigation_history'])) {
            $currentState['navigation_history'][] = $questionId;
        }

        $this->update(['current_state' => $currentState]);
    }

    public function getCurrentQuestion(): ?QuizQuestion
    {
        $currentQuestionId = $this->current_state['current_question_id'] ?? null;
        
        if (!$currentQuestionId) {
            return null;
        }

        return QuizQuestion::find($currentQuestionId);
    }

    public function answerQuestion(int $questionId, $answer, array $metadata = []): QuizAttemptAnswer
    {
        $question = QuizQuestion::findOrFail($questionId);
        
        // Validate answer
        $validationErrors = $question->validateAnswer($answer);
        if (!empty($validationErrors)) {
            throw new \Exception('Validation failed: ' . implode(', ', $validationErrors));
        }

        // Calculate score
        $scoringResult = $this->calculateQuestionScore($question, $answer);

        // Create or update answer
        $attemptAnswer = $this->answers()->updateOrCreate(
            ['question_id' => $questionId],
            [
                'answer_data' => $answer,
                'scoring_data' => $scoringResult,
                'score' => $scoringResult['score'],
                'max_score' => $scoringResult['max_score'],
                'is_correct' => $scoringResult['is_correct'],
                'is_partial' => $scoringResult['is_partial'],
                'answered_at' => now(),
            ]
        );

        // Update attempt state
        $this->updateAttemptState($questionId, $answer);

        // Update scores
        $this->updateScores();

        return $attemptAnswer;
    }

    public function skipQuestion(int $questionId): void
    {
        $currentState = $this->current_state;
        
        if (!in_array($questionId, $currentState['skipped_questions'])) {
            $currentState['skipped_questions'][] = $questionId;
        }

        $this->update(['current_state' => $currentState]);
    }

    public function navigateToQuestion(int $questionId): void
    {
        $this->setCurrentQuestion($questionId);
    }

    public function getNextQuestion(): ?QuizQuestion
    {
        $answeredQuestions = $this->current_state['answered_questions'] ?? [];
        $skippedQuestions = $this->current_state['skipped_questions'] ?? [];
        $completedQuestions = array_merge($answeredQuestions, $skippedQuestions);

        $nextQuestion = $this->quiz->questions()
            ->whereNotIn('id', $completedQuestions)
            ->first();

        return $nextQuestion;
    }

    public function getPreviousQuestion(): ?QuizQuestion
    {
        $navigationHistory = $this->current_state['navigation_history'] ?? [];
        
        if (count($navigationHistory) < 2) {
            return null;
        }

        $previousQuestionId = $navigationHistory[count($navigationHistory) - 2];
        return QuizQuestion::find($previousQuestionId);
    }

    public function complete(): self
    {
        $this->update([
            'status' => 'completed',
            'completed_at' => now(),
        ]);

        // Trigger webhooks
        $this->triggerWebhooks('attempt_completed');

        return $this;
    }

    public function abandon(): self
    {
        $this->update(['status' => 'abandoned']);
        
        // Trigger webhooks
        $this->triggerWebhooks('attempt_abandoned');

        return $this;
    }

    public function expire(): self
    {
        $this->update(['status' => 'expired']);
        
        // Trigger webhooks
        $this->triggerWebhooks('attempt_expired');

        return $this;
    }

    private function calculateExpiryTime(Quiz $quiz): ?Carbon
    {
        $durationMinutes = $quiz->duration_minutes;
        
        if (!$durationMinutes) {
            return null;
        }

        return now()->addMinutes($durationMinutes);
    }

    private function calculateQuestionScore(QuizQuestion $question, $answer): array
    {
        $scoringData = $question->getScoringData();
        $scoringType = $scoringData['type'] ?? 'binary'; // binary, partial, custom

        switch ($scoringType) {
            case 'binary':
                return $this->calculateBinaryScore($question, $answer);
            case 'partial':
                return $this->calculatePartialScore($question, $answer);
            case 'custom':
                return $this->calculateCustomScore($question, $answer, $scoringData);
            default:
                return $this->calculateBinaryScore($question, $answer);
        }
    }

    private function calculateBinaryScore(QuizQuestion $question, $answer): array
    {
        $correctAnswer = $question->getAnswerData('correct_answer');
        $isCorrect = $this->compareAnswers($answer, $correctAnswer, $question->type);

        return [
            'score' => $isCorrect ? $question->score_points : 0,
            'max_score' => $question->score_points,
            'is_correct' => $isCorrect,
            'is_partial' => false,
            'scoring_type' => 'binary',
        ];
    }

    private function calculatePartialScore(QuizQuestion $question, $answer): array
    {
        $correctAnswer = $question->getAnswerData('correct_answer');
        $partialScores = $question->getAnswerData('partial_scores', []);
        
        $score = 0;
        $isCorrect = false;
        $isPartial = false;

        if ($question->type === 'MULTIPLE_CHOICE') {
            $correctOptions = is_array($correctAnswer) ? $correctAnswer : [$correctAnswer];
            $selectedOptions = is_array($answer) ? $answer : [$answer];
            
            $correctCount = count(array_intersect($selectedOptions, $correctOptions));
            $totalCorrect = count($correctOptions);
            
            if ($correctCount === $totalCorrect) {
                $score = $question->score_points;
                $isCorrect = true;
            } elseif ($correctCount > 0) {
                $score = ($correctCount / $totalCorrect) * $question->score_points;
                $isPartial = true;
            }
        } else {
            // For other question types, use binary scoring
            $isCorrect = $this->compareAnswers($answer, $correctAnswer, $question->type);
            $score = $isCorrect ? $question->score_points : 0;
        }

        return [
            'score' => $score,
            'max_score' => $question->score_points,
            'is_correct' => $isCorrect,
            'is_partial' => $isPartial,
            'scoring_type' => 'partial',
        ];
    }

    private function calculateCustomScore(QuizQuestion $question, $answer, array $scoringData): array
    {
        // Implement custom scoring logic based on scoring_data
        // This would be specific to the question's custom scoring rules
        return [
            'score' => 0,
            'max_score' => $question->score_points,
            'is_correct' => false,
            'is_partial' => false,
            'scoring_type' => 'custom',
        ];
    }

    private function compareAnswers($userAnswer, $correctAnswer, string $questionType): bool
    {
        switch ($questionType) {
            case 'MULTIPLE_CHOICE':
                if (is_array($userAnswer) && is_array($correctAnswer)) {
                    sort($userAnswer);
                    sort($correctAnswer);
                    return $userAnswer === $correctAnswer;
                }
                return $userAnswer === $correctAnswer;

            case 'TRUE_FALSE':
                return (bool) $userAnswer === (bool) $correctAnswer;

            case 'FILL_BLANK':
                if (is_array($userAnswer) && is_array($correctAnswer)) {
                    return count($userAnswer) === count($correctAnswer) &&
                           array_map('strtolower', $userAnswer) === array_map('strtolower', $correctAnswer);
                }
                return strtolower($userAnswer) === strtolower($correctAnswer);

            case 'SHORT_ANSWER':
            case 'ESSAY':
                return strtolower(trim($userAnswer)) === strtolower(trim($correctAnswer));

            case 'MATCH':
                if (is_array($userAnswer) && is_array($correctAnswer)) {
                    return $userAnswer === $correctAnswer;
                }
                return false;

            case 'ORDER':
                if (is_array($userAnswer) && is_array($correctAnswer)) {
                    return $userAnswer === $correctAnswer;
                }
                return false;

            case 'MATRIX':
                if (is_array($userAnswer) && is_array($correctAnswer)) {
                    return $userAnswer === $correctAnswer;
                }
                return false;

            case 'NUMERIC':
                return (float) $userAnswer === (float) $correctAnswer;

            case 'DATE':
                return strtotime($userAnswer) === strtotime($correctAnswer);

            default:
                return $userAnswer === $correctAnswer;
        }
    }

    private function updateAttemptState(int $questionId, $answer): void
    {
        $currentState = $this->current_state;
        
        if (!in_array($questionId, $currentState['answered_questions'])) {
            $currentState['answered_questions'][] = $questionId;
        }

        $this->update([
            'current_state' => $currentState,
            'questions_answered' => count($currentState['answered_questions']),
            'last_activity_at' => now(),
        ]);
    }

    private function updateScores(): void
    {
        $totalScore = $this->answers()->sum('score');
        $maxScore = $this->answers()->sum('max_score');
        $percentage = $maxScore > 0 ? ($totalScore / $maxScore) * 100 : 0;

        $this->update([
            'total_score' => $totalScore,
            'max_score' => $maxScore,
            'percentage' => round($percentage, 2),
        ]);
    }

    private function triggerWebhooks(string $event): void
    {
        // This would trigger webhooks for the event
        // Implementation would depend on your webhook system
    }

    // Boot method
    protected static function boot()
    {
        parent::boot();

        static::creating(function ($attempt) {
            if (empty($attempt->attempt_id)) {
                $attempt->attempt_id = Str::uuid();
            }
        });
    }
}