const express = require('express');
const cors = require('cors');

const app = express();
const PORT = process.env.PORT || 8000;

app.use(cors());
app.use(express.json());

// In-memory stores
const idempotencyStore = new Map();
const sessions = new Map();

// Instructors list (simple directory)
const instructors = [
  { id: 'i1', name: 'Chef A', email: 'chef.a@example.com' },
  { id: 'i2', name: 'Chef B', email: 'chef.b@example.com' },
  { id: 'i3', name: 'Chef C', email: 'chef.c@example.com' }
];

// Sample data aligned with OpenAPI examples
const courses = [
  {
    id: 'c1',
    title: 'Basic Knife Skills',
    instructor: 'Chef A',
    progress: 30,
    status: 'in_progress',
    color: '#FF9800',
    thumbnail: '/images/knife-skills.jpg',
    duration: '6h',
    modules: 12,
    enrolled: '1.2k'
  },
  {
    id: 'c2',
    title: 'Bread Baking 101',
    instructor: 'Chef B',
    progress: 0,
    status: 'not_started',
    color: '#3F51B5',
    thumbnail: '/images/bread.jpg',
    duration: '4h',
    modules: 8,
    enrolled: '860'
  }
];

const courseDetails = {
  c1: {
    id: 'c1',
    title: 'Basic Knife Skills',
    description: 'Learn core knife handling techniques.',
    instructor: 'Chef A',
    duration: '6h',
    totalModules: 12,
    completedModules: 3,
    progress: 25,
    thumbnail: '/images/knife-skills.jpg',
    color: '#FF9800',
    modules: [
      {
        id: 'm1',
        title: 'Intro',
        description: 'Overview',
        duration: '15m',
        isUnlocked: true,
        isCompleted: false,
        progress: 0,
        type: 'reading',
        hasQuiz: false,
        hasAssignment: false,
      }
    ],
  },
  c2: {
    id: 'c2',
    title: 'Bread Baking 101',
    description: 'Learn how to bake bread.',
    instructor: 'Chef B',
    duration: '4h',
    totalModules: 8,
    completedModules: 0,
    progress: 0,
    thumbnail: '/images/bread.jpg',
    color: '#3F51B5',
    modules: [
      {
        id: 'm21',
        title: 'Starter',
        description: 'Basics',
        duration: '10m',
        isUnlocked: true,
        isCompleted: false,
        progress: 0,
        type: 'video',
        hasQuiz: false,
        hasAssignment: false,
      }
    ],
  }
};

const exams = [
  {
    id: 1,
    title: 'Kuis Interaktif - Keamanan Dapur BGN',
    course: 'Keamanan Pangan dan Gizi - Badan Gizi Nasional',
    type: 'Interactive Quiz',
    date: '2024-01-26',
    startTime: '09:00',
    endTime: '10:00',
    duration: 60,
    questions: 10,
    status: 'available',
    attempts: 0,
    maxAttempts: 3,
    passingScore: 70,
    description: 'Kuis interaktif tentang keamanan pangan dan praktik dapur yang baik di lingkungan BGN',
    instructions: [
      'Pastikan koneksi internet stabil',
      'Siapkan alat tulis',
      'Baca soal dengan teliti'
    ],
    isInteractive: true,
    score: null,
    grade: null,
  },
  {
    id: 2,
    title: 'Ujian Tengah Semester - HACCP',
    course: 'HACCP - Hazard Analysis Critical Control Point',
    type: 'Midterm Exam',
    date: '2024-01-28',
    startTime: '10:00',
    endTime: '12:00',
    duration: 120,
    questions: 50,
    status: 'upcoming',
    attempts: 0,
    maxAttempts: 1,
    passingScore: 75,
    description: 'Ujian tengah semester mencakup materi HACCP dari modul 1-6',
    instructions: [
      'Pastikan koneksi internet stabil',
      'Siapkan alat tulis',
      'Baca soal dengan teliti'
    ],
    isInteractive: false,
    score: null,
    grade: null,
  },
];

// Answer key for scoring (simple)
const examAnswerKey = {
  'exam-001': {
    q1: 0,
    q2: 2,
    q3: 1,
    q4: 3,
    q5: 0,
    q6: 1,
    q7: 2,
    q8: 1,
    q9: 0,
    q10: 3,
  }
};

// Health check
app.get('/health', (req, res) => {
  res.json({ status: 'ok', service: 'lms-bgn-backend', time: new Date().toISOString() });
});

// =====================
// Epik 1 — Courses APIs
// =====================
app.get('/api/courses', (req, res) => {
  const page = parseInt(req.query.page || '1', 10);
  const pageSize = parseInt(req.query.pageSize || '20', 10);
  if (isNaN(page) || isNaN(pageSize) || page < 1 || pageSize < 1 || pageSize > 100) {
    return res.status(400).json({ error: 'Invalid query parameters' });
  }
  const q = (req.query.q || '').toString().toLowerCase();
  const filtered = courses.filter(c => !q || c.title.toLowerCase().includes(q));
  const start = (page - 1) * pageSize;
  const items = filtered.slice(start, start + pageSize);
  res.json({ page, pageSize, total: filtered.length, items });
});

app.get('/api/courses/:courseId', (req, res) => {
  const { courseId } = req.params;
  const detail = courseDetails[courseId];
  if (!detail) return res.status(404).json({ error: 'Not Found' });
  res.json(detail);
});

// Instructors directory
app.get('/api/instructors', (req, res) => {
  const q = (req.query.q || '').toString().toLowerCase();
  const items = instructors.filter(i => !q || i.name.toLowerCase().includes(q) || i.email.toLowerCase().includes(q));
  res.json({ items, total: items.length });
});

// Create Course
app.post('/api/courses', (req, res) => {
  const { title, description, instructor, duration, thumbnail, color, status } = req.body || {};
  const errors = {};
  if (!title || String(title).trim().length < 3) {
    errors.title = 'Judul minimal 3 karakter';
  }
  if (!instructor || String(instructor).trim().length < 3) {
    errors.instructor = 'Instruktur minimal 3 karakter';
  }
  if (!duration || typeof duration !== 'string') {
    errors.duration = 'Durasi wajib';
  } else {
    const okDur = /^(\d+h|\d+m|\d+h\s\d+m)$/.test(duration.trim());
    if (!okDur) errors.duration = 'Durasi harus format seperti 6h, 30m, atau 1h 30m';
  }
  if (!thumbnail || typeof thumbnail !== 'string') {
    errors.thumbnail = 'Thumbnail wajib';
  } else {
    const t = thumbnail.trim();
    const okT = t.startsWith('/') || /^https?:\/\//.test(t);
    if (!okT) errors.thumbnail = 'Thumbnail harus URL http(s) atau path dimulai /';
  }
  if (color) {
    const okColor = /^#([A-Fa-f0-9]{6})$/.test(String(color).trim());
    if (!okColor) errors.color = 'Warna harus hex contoh #FF9800';
  }
  if (status && !['not_started', 'in_progress', 'completed'].includes(status)) {
    errors.status = 'Status tidak valid';
  }
  if (Object.keys(errors).length > 0) {
    return res.status(400).json({ error: 'Invalid request', details: errors });
  }

  const newId = `c${Date.now()}`;
  const summary = {
    id: newId,
    title: String(title),
    instructor: String(instructor),
    progress: 0,
    status: status || 'not_started',
    color: color || '#3F51B5',
    thumbnail: String(thumbnail),
    duration: String(duration),
    modules: 0,
    enrolled: '0'
  };
  courses.unshift(summary);
  courseDetails[newId] = {
    id: newId,
    title: String(title),
    description: description || '',
    instructor: String(instructor),
    duration: String(duration),
    totalModules: 0,
    completedModules: 0,
    progress: 0,
    thumbnail: String(thumbnail),
    color: color || '#3F51B5',
    modules: [],
  };
  res.status(201).json({ id: newId, status: 'created' });
});

// Update Course
app.put('/api/courses/:courseId', (req, res) => {
  const { courseId } = req.params;
  const existingDetail = courseDetails[courseId];
  if (!existingDetail) return res.status(404).json({ error: 'Not Found' });

  const { title, description, instructor, duration, thumbnail, color, status } = req.body || {};
  const errors = {};
  if (title !== undefined && String(title).trim().length < 3) {
    errors.title = 'Judul minimal 3 karakter';
  }
  if (instructor !== undefined && String(instructor).trim().length < 3) {
    errors.instructor = 'Instruktur minimal 3 karakter';
  }
  if (duration !== undefined) {
    const okDur = /^(\d+h|\d+m|\d+h\s\d+m)$/.test(String(duration).trim());
    if (!okDur) errors.duration = 'Durasi harus format seperti 6h, 30m, atau 1h 30m';
  }
  if (thumbnail !== undefined) {
    const t = String(thumbnail).trim();
    const okT = t.startsWith('/') || /^https?:\/\//.test(t);
    if (!okT) errors.thumbnail = 'Thumbnail harus URL http(s) atau path dimulai /';
  }
  if (color !== undefined) {
    const okColor = /^#([A-Fa-f0-9]{6})$/.test(String(color).trim());
    if (!okColor) errors.color = 'Warna harus hex contoh #FF9800';
  }
  if (status !== undefined && !['not_started', 'in_progress', 'completed'].includes(status)) {
    errors.status = 'Status tidak valid';
  }
  if (Object.keys(errors).length > 0) {
    return res.status(400).json({ error: 'Invalid request', details: errors });
  }

  // Update detail
  const updatedDetail = { ...existingDetail };
  if (title !== undefined) updatedDetail.title = String(title);
  if (description !== undefined) updatedDetail.description = String(description);
  if (instructor !== undefined) updatedDetail.instructor = String(instructor);
  if (duration !== undefined) updatedDetail.duration = String(duration);
  if (thumbnail !== undefined) updatedDetail.thumbnail = String(thumbnail);
  if (color !== undefined) updatedDetail.color = String(color);
  if (status !== undefined) updatedDetail.status = status;
  courseDetails[courseId] = updatedDetail;

  // Update summary
  const idx = courses.findIndex(c => c.id === courseId);
  if (idx >= 0) {
    const updatedSummary = { ...courses[idx] };
    if (title !== undefined) updatedSummary.title = String(title);
    if (instructor !== undefined) updatedSummary.instructor = String(instructor);
    if (duration !== undefined) updatedSummary.duration = String(duration);
    if (thumbnail !== undefined) updatedSummary.thumbnail = String(thumbnail);
    if (color !== undefined) updatedSummary.color = String(color);
    if (status !== undefined) updatedSummary.status = status;
    courses[idx] = updatedSummary;
  }

  res.json({ id: courseId, status: 'updated' });
});

// Delete Course
app.delete('/api/courses/:courseId', (req, res) => {
  const { courseId } = req.params;
  const idx = courses.findIndex(c => c.id === courseId);
  if (idx < 0) return res.status(404).json({ error: 'Not Found' });
  courses.splice(idx, 1);
  delete courseDetails[courseId];
  res.status(204).send();
});

app.post('/api/modules/:moduleId/progress', (req, res) => {
  const { moduleId } = req.params;
  const { progressPercent, updatedAt } = req.body || {};
  if (typeof progressPercent !== 'number' || progressPercent < 0 || progressPercent > 100) {
    return res.status(400).json({ error: 'Invalid progressPercent' });
  }
  // Accept and simulate async processing
  res.status(202).json({ status: 'accepted', moduleId, updatedAt: updatedAt || new Date().toISOString() });
});

app.post('/api/assignments/:assignmentId/submission', (req, res) => {
  const { assignmentId } = req.params;
  const { content, attachments, idempotencyKey } = req.body || {};
  if (!idempotencyKey) return res.status(400).json({ error: 'Missing idempotencyKey' });
  const key = `assignment-${idempotencyKey}`;
  if (idempotencyStore.has(key)) {
    return res.status(409).json({ error: 'Conflict (duplicate idempotency key)' });
  }
  idempotencyStore.set(key, { assignmentId, at: Date.now() });
  const submissionId = `sub-${Date.now()}`;
  res.status(201).json({ submissionId, status: 'created' });
});

// =====================
// Epik 2 — Exams APIs
// =====================
const AUTH_TOKEN = process.env.TEST_BEARER_TOKEN || 'test-token';
const requireAuth = (req, res, next) => {
  const auth = req.headers.authorization || '';
  if (!auth.startsWith('Bearer ')) return res.status(401).json({ error: 'Unauthorized' });
  const token = auth.slice('Bearer '.length);
  if (token !== AUTH_TOKEN) return res.status(401).json({ error: 'Unauthorized' });
  next();
};
app.post('/api/exam-session', requireAuth, (req, res) => {
  const { examId, userId } = req.body || {};
  if (!examId || !userId) return res.status(400).json({ error: 'Missing examId or userId' });
  const sessionId = `sess-${Date.now()}`;
  const startTime = new Date().toISOString();
  sessions.set(sessionId, { examId, userId, startTime });
  res.status(201).json({ sessionId, status: 'created', startTime });
});

app.post('/api/exam-session/:sessionId/score', requireAuth, (req, res) => {
  const { sessionId } = req.params;
  const session = sessions.get(sessionId);
  if (!session) return res.status(404).json({ error: 'Session not found' });
  const { idempotencyKey, answers, mode } = req.body || {};
  if (!idempotencyKey) return res.status(400).json({ error: 'Missing idempotencyKey' });
  const key = `score-${idempotencyKey}`;
  if (idempotencyStore.has(key)) {
    return res.status(409).json({ error: 'Conflict (duplicate idempotency key)' });
  }
  idempotencyStore.set(key, { sessionId, at: Date.now() });

  const perQuestion = (answers || []).map(a => scoreAnswer(session.examId, a));
  const totalScore = perQuestion.reduce((sum, p) => sum + p.earnedPoints, 0);
  const totalQuestions = perQuestion.length;
  const correctAnswers = perQuestion.filter(p => p.isCorrect).length;
  const result = { totalScore, totalQuestions, correctAnswers, perQuestion };
  sessions.set(sessionId, { ...session, lastScore: result, lastAnswers: perQuestion, completedAt: new Date().toISOString() });
  res.json(result);
});

app.get('/api/exam-session/:sessionId/summary', requireAuth, (req, res) => {
  const { sessionId } = req.params;
  const session = sessions.get(sessionId);
  if (!session) return res.status(404).json({ error: 'Session not found' });
  const { lastScore, lastAnswers } = session;
  const summary = {
    examId: session.examId || 'exam-001',
    examTitle: 'Kuis Interaktif - Keamanan Dapur BGN',
    totalQuestions: lastScore ? lastScore.totalQuestions : 0,
    correctAnswers: lastScore ? lastScore.correctAnswers : 0,
    score: lastScore ? lastScore.totalScore : 0,
    timeSpent: '25 menit 30 detik',
    completedAt: session.completedAt || new Date().toISOString(),
    answers: (lastAnswers || []).map(a => ({
      questionId: a.questionId,
      isCorrect: a.isCorrect,
      type: a.type,
      earnedPoints: a.earnedPoints,
      maxPoints: a.maxPoints,
      details: a.details,
    })),
  };
  res.json(summary);
});

app.get('/api/exams', (req, res) => {
  const page = parseInt(req.query.page || '1', 10);
  const pageSize = parseInt(req.query.pageSize || '20', 10);
  if (isNaN(page) || isNaN(pageSize) || page < 1 || pageSize < 1 || pageSize > 100) {
    return res.status(400).json({ error: 'Invalid query parameters' });
  }
  const q = (req.query.q || '').toString().toLowerCase();
  const status = (req.query.status || '').toString();
  const filtered = exams.filter(e => {
    const okQ = !q || e.title.toLowerCase().includes(q) || e.course.toLowerCase().includes(q);
    const okStatus = !status || e.status === status;
    return okQ && okStatus;
  });
  const start = (page - 1) * pageSize;
  const items = filtered.slice(start, start + pageSize);
  res.json({ page, pageSize, total: filtered.length, items });
});

app.post('/api/certificates/:certificateId/resend', (req, res) => {
  const { certificateId } = req.params;
  const { recipientEmail, idempotencyKey } = req.body || {};
  if (!recipientEmail) return res.status(400).json({ error: 'Missing recipientEmail' });
  if (!idempotencyKey) return res.status(400).json({ error: 'Missing idempotencyKey' });
  const key = `cert-${idempotencyKey}`;
  const prev = idempotencyStore.get(key);
  if (prev && Date.now() - prev.at < 30000) {
    return res.status(429).json({ error: 'Too Many Requests' });
  }
  idempotencyStore.set(key, { certificateId, at: Date.now() });
  const jobId = `job-${Date.now()}`;
  res.status(202).json({ jobId, status: 'accepted' });
});

// Global error handler (fallback)
app.use((err, req, res, next) => {
  console.error('Unhandled error:', err);
  res.status(500).json({ error: 'Internal Server Error' });
});

if (process.env.NODE_ENV !== 'test') {
  app.listen(PORT, () => {
    console.log(`LMS-BGN backend running on http://localhost:${PORT}`);
  });
}

module.exports = app;

const interactiveAnswerKey = {
  'exam-001': {
    'mcq-1': { type: 'enhanced_mcq', correctChoiceIndex: 2, maxPoints: 10 },
    'video-1': { type: 'video_scenario', steps: [ { stepId: 's1', correctOptionId: 'optA' }, { stepId: 's2', correctOptionId: 'optB' } ], maxPoints: 10 },
    'hotspot-1': { type: 'image_hotspot', correctHotspots: ['spot1','spot3'], maxPoints: 10 },
    'gallery-1': { type: 'media_gallery', minViewed: 3, maxPoints: 10 },
    'puzzle-1': { type: 'puzzle', matches: [ { pieceId: 'p1', targetId: 't1' }, { pieceId: 'p2', targetId: 't2' } ], maxPoints: 10 },
    'scenario-1': { type: 'scenario', correctOptionId: 'optX', maxPoints: 10 },
  }
};

function scoreAnswer(sessionExamId, a) {
  const maxPointsDefault = 10;
  const type = a.type || 'multiple-choice';
  const ik = interactiveAnswerKey[sessionExamId] || {};
  const key = ik[a.questionId];
  let earnedPoints = 0;
  let maxPoints = (key && key.maxPoints) || maxPointsDefault;
  let isCorrect = false;
  let details = {};

  switch (type) {
    case 'enhanced_mcq':
    case 'multiple-choice': {
      const ak = examAnswerKey[sessionExamId] || {};
      const correctIndex = key && key.correctChoiceIndex !== undefined ? key.correctChoiceIndex : ak[a.questionId];
      isCorrect = typeof correctIndex === 'number' && a.choiceIndex === correctIndex;
      earnedPoints = isCorrect ? maxPoints : 0;
      details = { choiceIndex: a.choiceIndex, correctIndex };
      break;
    }
    case 'video_scenario': {
      const expected = (key && key.steps) || [];
      const steps = Array.isArray(a.scenarioAnswers) ? a.scenarioAnswers : [];
      let correctCount = 0;
      const stepDetails = steps.map(s => {
        const exp = expected.find(e => e.stepId === s.stepId);
        const ok = !!exp && s.selectedOptionId === exp.correctOptionId;
        if (ok) correctCount++;
        return { stepId: s.stepId, selectedOptionId: s.selectedOptionId, correctOptionId: exp ? exp.correctOptionId : null, isCorrect: ok };
      });
      const total = expected.length || steps.length || 1;
      earnedPoints = Math.round((correctCount / total) * maxPoints);
      isCorrect = correctCount === total && total > 0;
      details = { steps: stepDetails, correctCount, totalSteps: total };
      break;
    }
    case 'image_hotspot': {
      const selected = Array.isArray(a.selectedHotspots) ? a.selectedHotspots : [];
      const expected = (key && key.correctHotspots) || [];
      const setExp = new Set(expected);
      let matchCount = 0;
      selected.forEach(h => { if (setExp.has(h)) matchCount++; });
      const totalRequired = expected.length || 1;
      earnedPoints = Math.round((matchCount / totalRequired) * maxPoints);
      isCorrect = matchCount === totalRequired && totalRequired > 0;
      details = { selectedHotspots: selected, correctHotspots: expected, matchCount, totalRequired };
      break;
    }
    case 'media_gallery': {
      const viewed = Array.isArray(a.viewedItems) ? a.viewedItems : [];
      const minViewed = (key && key.minViewed) || 3;
      const ratio = Math.min(viewed.length, minViewed) / minViewed;
      earnedPoints = Math.round(ratio * maxPoints);
      isCorrect = viewed.length >= minViewed;
      details = { viewedCount: viewed.length, minViewed };
      break;
    }
    case 'puzzle': {
      const matches = Array.isArray(a.matches || a.solvedSteps) ? (a.matches || a.solvedSteps) : [];
      const expected = (key && key.matches) || [];
      let correctCount = 0;
      const matchDetails = matches.map(m => {
        const ok = expected.some(e => e.pieceId === m.pieceId && e.targetId === m.targetId);
        if (ok) correctCount++;
        return { pieceId: m.pieceId, targetId: m.targetId, isCorrect: ok };
      });
      const totalPairs = expected.length || matches.length || 1;
      earnedPoints = Math.round((correctCount / totalPairs) * maxPoints);
      isCorrect = correctCount === totalPairs && totalPairs > 0;
      details = { solvedSteps: matchDetails, correctCount, totalPairs };
      break;
    }
    case 'scenario': {
      const correctOptionId = key && key.correctOptionId;
      isCorrect = !!correctOptionId && a.selectedOptionId === correctOptionId;
      earnedPoints = isCorrect ? maxPoints : 0;
      details = { selectedOptionId: a.selectedOptionId, correctOptionId };
      break;
    }
    default: {
      const ak = examAnswerKey[sessionExamId] || {};
      const correctIndex = ak[a.questionId];
      isCorrect = typeof correctIndex === 'number' && a.choiceIndex === correctIndex;
      earnedPoints = isCorrect ? maxPoints : 0;
      details = { choiceIndex: a.choiceIndex, correctIndex };
    }
  }

  return { questionId: a.questionId, type, isCorrect, earnedPoints, maxPoints, details };
}