/**
 * Attachments Controller (§A — File Attachments plan)
 *
 * Polymorphic file storage for syllabuses, course materials,
 * supplementary assignment files, homework sheets, test papers,
 * student submissions, and teacher-uploaded marked scripts.
 *
 * Routes (all under /api/attachments):
 *   POST   /              multipart upload (parent_kind, parent_id, optional caption, files[])
 *   GET    /?parent_kind=&parent_id=   list
 *   GET    /:uid          single row
 *   DELETE /:uid          soft-delete (sets is_deleted=1)
 *
 * Auth: every route requires a Bearer JWT. Visibility rules for GET
 * are deliberately loose for §A (any authenticated caller; tighter
 * RBAC lands in §B/C/D per plan §3.1 and §8). The server remains the
 * source of truth — the role gate below rejects caller roles that
 * obviously shouldn't be uploading.
 */

const express = require('express');
const router = express.Router();
const { v4: uuidv4 } = require('uuid');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const Database = require('better-sqlite3');

const uploadDir = path.join(__dirname, '../../uploads');
if (!fs.existsSync(uploadDir)) {
  fs.mkdirSync(uploadDir, { recursive: true });
}

const ALLOWED_MIMES = new Set([
  'application/pdf',
  'application/msword',
  'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  'application/vnd.ms-excel',
  'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  'application/vnd.ms-powerpoint',
  'application/vnd.openxmlformats-officedocument.presentationml.presentation',
  'image/png',
  'image/jpeg',
  'image/gif',
  'application/zip',
  'text/plain',
  'video/mp4',
  'video/webm',
  'video/ogg',
  'video/quicktime',
  'audio/mpeg',
  'audio/wav',
  'audio/ogg',
  'audio/mp4',
  'audio/x-m4a',
]);

const ALLOWED_PARENT_KINDS = new Set([
  'subject',
  'assignment',
  'homework',
  'test',
  'exam_paper',
  'submission',
  'marked_script',
]);

const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, uploadDir);
  },
  filename: function (req, file, cb) {
    const suffix = Math.random().toString(36).slice(2, 8);
    cb(null, `${Date.now()}-${suffix}${path.extname(file.originalname)}`);
  },
});

const upload = multer({
  storage,
  limits: { fileSize: 20 * 1024 * 1024, files: 10 },
  fileFilter: function (req, file, cb) {
    if (!ALLOWED_MIMES.has(file.mimetype)) {
      return cb(new Error(`Unsupported file type: ${file.mimetype}`));
    }
    cb(null, true);
  },
});

// Multer error translator. Without this wrapper, multer's LIMIT_FILE_SIZE
// (and friends) errors bubble to the global error handler and surface as
// 500 Internal Server Error, leaking a stack trace to the client.
// The UAT TC-LMS-004 (oversize upload) caught this. We translate to a
// clean 413/400 with a caller-friendly message here.
function handleUpload(req, res, next) {
  upload.array('files', 10)(req, res, (err) => {
    if (!err) return next();
    if (err.code === 'LIMIT_FILE_SIZE') {
      return res.status(413).json({
        error: `File too large; limit is 20MB per file`,
        code: 'LIMIT_FILE_SIZE',
        limit_mb: 20,
      });
    }
    if (err.code === 'LIMIT_FILE_COUNT') {
      return res.status(400).json({
        error: `Too many files; limit is 10 files per upload`,
        code: 'LIMIT_FILE_COUNT',
        limit: 10,
      });
    }
    if (err.code === 'LIMIT_UNEXPECTED_FILE') {
      return res.status(400).json({
        error: `Unexpected file field "${err.field}"; expected "files"`,
        code: 'LIMIT_UNEXPECTED_FILE',
        expected_field: 'files',
      });
    }
    if (err instanceof multer.MulterError) {
      return res.status(400).json({ error: err.message, code: err.code });
    }
    // fileFilter rejection ("Unsupported file type: …") or other
    return res.status(400).json({ error: err.message || 'Upload failed' });
  });
}

const dbPath = process.env.DB_PATH || path.join(__dirname, '../../data/school.db');
const db = new Database(dbPath);
db.pragma('foreign_keys = ON');

const auth = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) return res.status(401).json({ error: 'No token provided' });
  try {
    const jwt = require('jsonwebtoken');
    const { jwtSecret: JWT_SECRET } = require('../config');
    req.user = jwt.verify(token, JWT_SECRET);
    next();
  } catch (err) {
    return res.status(401).json({ error: 'Invalid token' });
  }
};

const UPLOAD_ROLES = new Set([
  'teacher',
  'school_admin',
  'systems_admin',
  'principal',
  'student',
]);

const deleteFiles = (files) => {
  if (!files || files.length === 0) return;
  for (const file of files) {
    if (file.path) {
      fs.unlink(file.path, (err) => {
        if (err) console.error('Failed to delete uploaded file on failure:', file.path, err);
      });
    }
  }
};

const insertAttachmentStmt = db.prepare(`
  INSERT INTO attachments (
    uid, parent_kind, parent_id, original_filename, stored_filename,
    mime_type, size_bytes, uploaded_by, caption, sync_status
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending')
`);

router.post('/', auth, handleUpload, (req, res) => {
  const files = req.files || [];
  try {
    if (!UPLOAD_ROLES.has(req.user.role)) {
      deleteFiles(files);
      return res.status(403).json({ error: 'Not allowed to upload attachments' });
    }

    const { parent_kind, parent_id, caption } = req.body;

    if (!parent_kind || !ALLOWED_PARENT_KINDS.has(parent_kind)) {
      deleteFiles(files);
      return res.status(400).json({ error: 'Invalid or missing parent_kind' });
    }
    if (!parent_id) {
      deleteFiles(files);
      return res.status(400).json({ error: 'parent_id is required' });
    }

    if (files.length === 0) {
      return res.status(400).json({ error: 'At least one file is required' });
    }

    const inserted = [];
    const tx = db.transaction((batch) => {
      for (const file of batch) {
        const uid = uuidv4();
        const result = insertAttachmentStmt.run(
          uid,
          parent_kind,
          Number(parent_id),
          file.originalname,
          file.filename,
          file.mimetype,
          file.size,
          req.user.id,
          caption || null,
        );
        const row = db.prepare('SELECT * FROM attachments WHERE id = ?').get(result.lastInsertRowid);
        inserted.push(row);
      }
    });
    tx(files);

    // PR 3 §13 follow-up — fan out a realtime event when a marked_script
    // is uploaded so the student (and any linked parents) see the new
    // script without reloading. Only fan-out for parent_kind='marked_script'
    // since other kinds (submission, subject, homework, test, ...) are not
    // surfaced as "marked" UI events.
    if (parent_kind === 'marked_script' && inserted.length > 0) {
      const wsHub = req.app.get('wsHub');
      if (wsHub) {
        const subRow = db.prepare(`
          SELECT * FROM submissions WHERE id = ? AND is_deleted = 0
        `).get(Number(parent_id));
        if (subRow) {
          const payload = {
            type: 'marked_script:uploaded',
            data: {
              submission_id: subRow.id,
              assignment_id: subRow.assignment_id,
              student_id: subRow.student_id,
              status: subRow.status,
              attachments: inserted,
            },
          };
          wsHub.broadcastToUser(subRow.student_id, payload);
          try {
            const parents = db.prepare(`
              SELECT parent_id FROM parent_students
              WHERE student_id = ? AND is_deleted = 0
            `).all(subRow.student_id);
            for (const p of parents) wsHub.broadcastToUser(p.parent_id, payload);
          } catch (err) {
            console.warn('[attachments] parent fan-out failed:', err.message);
          }
        }
      }
    }

    res.status(201).json(inserted);
  } catch (error) {
    deleteFiles(files);
    res.status(500).json({ error: error.message });
  }
});

router.get('/', auth, (req, res) => {
  try {
    const { parent_kind, parent_id } = req.query;

    if (!parent_kind || !ALLOWED_PARENT_KINDS.has(parent_kind)) {
      return res.status(400).json({ error: 'Invalid or missing parent_kind' });
    }

    if (!parent_id && (req.user.role === 'student' || req.user.role === 'parent')) {
      return res.status(400).json({ error: 'parent_id is required' });
    }

    if (parent_id && (parent_kind === 'submission' || parent_kind === 'marked_script')) {
      const submission = db.prepare('SELECT student_id FROM submissions WHERE id = ? AND is_deleted = 0').get(Number(parent_id));
      if (!submission) {
        return res.status(404).json({ error: 'Submission not found' });
      }
      const studentId = submission.student_id;
      
      if (req.user.role === 'student' && req.user.id !== studentId) {
        return res.status(403).json({ error: 'Not authorized to access this submission' });
      }
      if (req.user.role === 'parent') {
        const linked = db.prepare(
          'SELECT 1 FROM parent_students WHERE parent_id = ? AND student_id = ? AND is_deleted = 0'
        ).get(req.user.id, studentId);
        if (!linked) {
          return res.status(403).json({ error: 'Not authorized to access this student\'s submission' });
        }
      }
    }

    let rows;
    if (parent_id) {
      rows = db.prepare(`
        SELECT * FROM attachments
        WHERE parent_kind = ? AND parent_id = ? AND is_deleted = 0
        ORDER BY uploaded_at DESC
      `).all(parent_kind, Number(parent_id));
    } else {
      rows = db.prepare(`
        SELECT * FROM attachments
        WHERE parent_kind = ? AND is_deleted = 0
        ORDER BY uploaded_at DESC
      `).all(parent_kind);
    }

    res.json(rows);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

router.get('/:uid', auth, (req, res) => {
  try {
    const row = db.prepare(
      'SELECT * FROM attachments WHERE uid = ? AND is_deleted = 0',
    ).get(req.params.uid);

    if (!row) {
      return res.status(404).json({ error: 'Attachment not found' });
    }

    const { parent_kind, parent_id } = row;
    if (parent_kind === 'submission' || parent_kind === 'marked_script') {
      const submission = db.prepare('SELECT student_id FROM submissions WHERE id = ? AND is_deleted = 0').get(parent_id);
      if (!submission) {
        return res.status(404).json({ error: 'Linked submission not found' });
      }
      const studentId = submission.student_id;
      
      if (req.user.role === 'student' && req.user.id !== studentId) {
        return res.status(403).json({ error: 'Not authorized to access this attachment' });
      }
      if (req.user.role === 'parent') {
        const linked = db.prepare(
          'SELECT 1 FROM parent_students WHERE parent_id = ? AND student_id = ? AND is_deleted = 0'
        ).get(req.user.id, studentId);
        if (!linked) {
          return res.status(403).json({ error: 'Not authorized to access this student\'s attachment' });
        }
      }
    }

    res.json(row);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

router.delete('/:uid', auth, (req, res) => {
  try {
    const row = db.prepare(
      'SELECT * FROM attachments WHERE uid = ? AND is_deleted = 0',
    ).get(req.params.uid);

    if (!row) {
      return res.status(404).json({ error: 'Attachment not found' });
    }

    const isPrivileged = ['school_admin', 'systems_admin', 'principal'].includes(req.user.role);
    if (!isPrivileged && row.uploaded_by !== req.user.id) {
      return res.status(403).json({ error: 'Only the uploader or an admin may delete this attachment' });
    }

    db.prepare(`
      UPDATE attachments
      SET is_deleted = 1, sync_status = 'pending'
      WHERE id = ?
    `).run(row.id);

    res.json({ ok: true });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

module.exports = router;