const express = require('express');
const multer = require('multer');
const upload = multer();
const TrainingProject = require('../models/TrainingProject.js');
const LabelStudioProject = require('../models/LabelStudioProject.js')
const { seedLabelStudio, updateStatus } = require('../services/seed-label-studio.js');
const fs = require('fs');
const path = require('path');
const {generateTrainingJson} = require('../services/generate-json-yolox.js')


const router = express.Router();

// Ensure JSON bodies are parsed for all routes
router.use(express.json());

router.get('/seed', async (req, res) => {
  const result = await seedLabelStudio();
  res.json(result);
});



// Trigger generate-json-yolox.js

router.post('/generate-yolox-json', async (req, res) => {
  const { project_id } = req.body;
  if (!project_id) {
    return res.status(400).json({ message: 'Missing project_id in request body' });
  }
  try {
    // Generate COCO JSONs
    // Find all TrainingProjectDetails for this project
    const TrainingProjectDetails = require('../models/TrainingProjectDetails.js');
    const detailsRows = await TrainingProjectDetails.findAll({ where: { project_id } });
    if (!detailsRows || detailsRows.length === 0) {
      return res.status(404).json({ message: 'No TrainingProjectDetails found for project ' + project_id });
    }
    // For each details row, generate coco.jsons and exp.py in projectfolder/project_details_id
    const Training = require('../models/training.js');
    const { saveYoloxExp } = require('../services/generate-yolox-exp.js');
    const TrainingProject = require('../models/TrainingProject.js');
    const trainingProject = await TrainingProject.findByPk(project_id);
    const projectName = trainingProject.name ? trainingProject.name.replace(/\s+/g, '_') : `project_${project_id}`;
    for (const details of detailsRows) {
      const detailsId = details.id;
      await generateTrainingJson(detailsId);
      const trainings = await Training.findAll({ where: { project_details_id: detailsId } });
      if (trainings.length === 0) continue;
      // For each training, save exp.py in projectfolder/project_details_id
      const outDir = path.join(__dirname, '..', projectName, String(detailsId));
      if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
      for (const training of trainings) {
        const expFilePath = path.join(outDir, 'exp.py');
        await saveYoloxExp(training.id, expFilePath);
      }
    }

    // Find all trainings for this project
    // ...existing code...
    res.json({ message: 'YOLOX JSON and exp.py generated for project ' + project_id });
  } catch (err) {
    console.error('Error generating YOLOX JSON:', err);
    res.status(500).json({ message: 'Failed to generate YOLOX JSON', error: err.message });
  }
});

// Start YOLOX training
const { spawn } = require('child_process');
router.post('/start-yolox-training', async (req, res) => {
  try {
    const { project_id, training_id } = req.body;
    // Get project name
    const trainingProject = await TrainingProject.findByPk(project_id);
    const projectName = trainingProject.name ? trainingProject.name.replace(/\s+/g, '_') : `project_${project_id}`;
    // Look up training row by id or project_details_id
    const Training = require('../models/training.js');
    let trainingRow = await Training.findByPk(training_id);
    if (!trainingRow) {
      trainingRow = await Training.findOne({ where: { project_details_id: training_id } });
    }
    if (!trainingRow) {
      return res.status(404).json({ error: `Training row not found for id or project_details_id ${training_id}` });
    }
    const project_details_id = trainingRow.project_details_id;
    // Use the generated exp.py from the correct project folder
    const outDir = path.join(__dirname, '..', projectName, String(project_details_id));
    const yoloxMainDir = '/home/kitraining/Yolox/YOLOX-main';
    const expSrc = path.join(outDir, 'exp.py');
    if (!fs.existsSync(expSrc)) {
      return res.status(500).json({ error: `exp.py not found at ${expSrc}` });
    }
    // Source venv and run YOLOX training in YOLOX-main folder
    const yoloxVenv = '/home/kitraining/Yolox/yolox_venv/bin/activate';
    // Determine model argument based on selected_model and transfer_learning
    let modelArg = '';
    let cmd = '';
    if (
      trainingRow.transfer_learning &&
      typeof trainingRow.transfer_learning === 'string' &&
      trainingRow.transfer_learning.toLowerCase() === 'coco'
    ) {
      // If transfer_learning is 'coco', add -o and modelArg
      modelArg = ` -c /home/kitraining/Yolox/YOLOX-main/pretrained/${trainingRow.selected_model}`;
      cmd = `bash -c 'source ${yoloxVenv} && python tools/train.py -f ${expSrc} -d 1 -b 8 --fp16 -o ${modelArg}.pth --cache'`;
    } else if (
      trainingRow.selected_model &&
      trainingRow.selected_model.toLowerCase() === 'coco' &&
      (!trainingRow.transfer_learning || trainingRow.transfer_learning === false)
    ) {
      // If selected_model is 'coco' and not transfer_learning, add modelArg only
      modelArg = ` -c /pretrained/${trainingRow.selected_model}`;
      cmd = `bash -c 'source ${yoloxVenv} && python tools/train.py -f ${expSrc} -d 1 -b 8 --fp16 -o ${modelArg}.pth --cache'`;
    } else {
      // Default: no modelArg
      cmd = `bash -c 'source ${yoloxVenv} && python tools/train.py -f ${expSrc} -d 1 -b 8 --fp16' --cache`;
    }
    console.log(cmd)
    const child = spawn(cmd, { shell: true, cwd: yoloxMainDir });
    child.stdout.pipe(process.stdout);
    child.stderr.pipe(process.stderr);

    res.json({ message: 'Training started' });
  } catch (err) {
    res.status(500).json({ error: 'Failed to start training', details: err.message });
  }
});

// Get YOLOX training log
router.get('/training-log', async (req, res) => {
  try {
    const { project_id, training_id } = req.query;
    const trainingProject = await TrainingProject.findByPk(project_id);
    const projectName = trainingProject.name ? trainingProject.name.replace(/\s+/g, '_') : `project_${project_id}`;
    const outDir = path.join(__dirname, '..', projectName, String(training_id));
    const logPath = path.join(outDir, 'training.log');
    if (!fs.existsSync(logPath)) {
      return res.status(404).json({ error: 'Log not found' });
    }
    const logData = fs.readFileSync(logPath, 'utf8');
    res.json({ log: logData });
  } catch (err) {
    res.status(500).json({ error: 'Failed to fetch log', details: err.message });
  }
});

router.post('/training-projects', upload.single('project_image'), async (req, res) => {
  try {
    const { title, description } = req.body;
    const classes = JSON.parse(req.body.classes);
    const project_image = req.file ? req.file.buffer : null;
    const project_image_type = req.file ? req.file.mimetype : null;
    await TrainingProject.create({
      title,
      description,
      classes,
      project_image,
      project_image_type
    });
    res.json({ message: 'Project created!' });
  } catch (error) {
    console.error('Error creating project:', error);
    res.status(500).json({ message: 'Failed to create project', error: error.message });
  }
});

router.get('/training-projects', async (req, res) => {
  try {
    const projects = await TrainingProject.findAll();
    // Convert BLOB to base64 data URL for each project
    const serialized = projects.map(project => {
      const plain = project.get({ plain: true });
      if (plain.project_image) {
        const base64 = Buffer.from(plain.project_image).toString('base64');
        const mimeType = plain.project_image_type || 'image/png';
        plain.project_image = `data:${mimeType};base64,${base64}`;
      }
      return plain;
    });
    res.json(serialized);
  } catch (error) {
    res.status(500).json({ message: 'Failed to fetch projects', error: error.message });
  }
});

router.get('/update-status', async (req, res) => {
  res.json(updateStatus)
})

router.get('/label-studio-projects', async (req, res) => {
  try {
    const LabelStudioProject = require('../models/LabelStudioProject.js');
    const Image = require('../models/Images.js');
    const Annotation = require('../models/Annotation.js');
    const labelStudioProjects = await LabelStudioProject.findAll();
    const projectsWithCounts = await Promise.all(labelStudioProjects.map(async project => {
      const plain = project.get({ plain: true });
      // Get all images for this project
      const images = await Image.findAll({ where: { project_id: plain.project_id } });
      let annotationCounts = {};
      if (images.length > 0) {
        const imageIds = images.map(img => img.image_id);
        // Get all annotations for these images
        const annotations = await Annotation.findAll({ where: { image_id: imageIds } });
        // Count by label
        for (const ann of annotations) {
          const label = ann.Label;
          annotationCounts[label] = (annotationCounts[label] || 0) + 1;
        }
      }
      plain.annotationCounts = annotationCounts;
      return plain;
    }));
    res.json(projectsWithCounts);
  } catch (error) {
    res.status(500).json({ message: 'Failed to fetch projects', error: error.message });
  }
})


// POST endpoint to create TrainingProjectDetails with all fields
router.post('/training-project-details', async (req, res) => {
  try {
    const {
      project_id,
      annotation_projects,
      class_map,
      description
    } = req.body;
    if (!project_id || !annotation_projects) {
      return res.status(400).json({ message: 'Missing required fields' });
    }
    const TrainingProjectDetails = require('../models/TrainingProjectDetails.js');
    const created = await TrainingProjectDetails.create({
      project_id,
      annotation_projects,
      class_map: class_map || null,
      description: description || null
    });
    res.json({ message: 'TrainingProjectDetails created', details: created });
  } catch (error) {
    res.status(500).json({ message: 'Failed to create TrainingProjectDetails', error: error.message });
  }
});

// GET endpoint to fetch all TrainingProjectDetails
router.get('/training-project-details', async (req, res) => {
  try {
    const TrainingProjectDetails = require('../models/TrainingProjectDetails.js');
    const details = await TrainingProjectDetails.findAll();
    res.json(details);
  } catch (error) {
    res.status(500).json({ message: 'Failed to fetch TrainingProjectDetails', error: error.message });
  }
});

// PUT endpoint to update class_map and description in TrainingProjectDetails
router.put('/training-project-details', async (req, res) => {
  try {
    const { project_id, class_map, description } = req.body;
    if (!project_id || !class_map || !description) {
      return res.status(400).json({ message: 'Missing required fields' });
    }
    const TrainingProjectDetails = require('../models/TrainingProjectDetails.js');
    const details = await TrainingProjectDetails.findOne({ where: { project_id } });
    if (!details) {
      return res.status(404).json({ message: 'TrainingProjectDetails not found' });
    }
    details.class_map = class_map;
    details.description = description;
    await details.save();
    res.json({ message: 'Class map and description updated', details });
  } catch (error) {
    res.status(500).json({ message: 'Failed to update class map or description', error: error.message });
  }
});

// POST endpoint to receive YOLOX settings and save to DB (handles multipart/form-data)
router.post('/yolox-settings', upload.any(), async (req, res) => {
  try {
    const settings = req.body;
    // Debug: Log all received fields and types
    console.log('--- YOLOX settings received ---');
    console.log('settings:', settings);
    if (req.files && req.files.length > 0) {
      console.log('Files received:', req.files.map(f => ({ fieldname: f.fieldname, originalname: f.originalname, size: f.size })));
    }
    // Declare requiredFields once
    const requiredFields = ['project_details_id', 'exp_name', 'max_epoch', 'depth', 'width', 'activation', 'train', 'valid', 'test', 'selected_model', 'transfer_learning'];
    // Log types of required fields
    requiredFields.forEach(field => {
      console.log(`Field '${field}': value='${settings[field]}', type='${typeof settings[field]}'`);
    });
    // Map select_model to selected_model if present
    if (settings && settings.select_model && !settings.selected_model) {
      settings.selected_model = settings.select_model;
      delete settings.select_model;
    }
    // Lookup project_details_id from project_id
    if (!settings.project_id || isNaN(Number(settings.project_id))) {
      throw new Error('Missing or invalid project_id in request. Cannot assign training to a project.');
    }
    const TrainingProjectDetails = require('../models/TrainingProjectDetails.js');
    let details = await TrainingProjectDetails.findOne({ where: { project_id: settings.project_id } });
    if (!details) {
      details = await TrainingProjectDetails.create({
        project_id: settings.project_id,
        annotation_projects: [],
        class_map: null,
        description: null
      });
    }
    settings.project_details_id = details.id;
    // Map 'act' from frontend to 'activation' for DB
    if (settings.act !== undefined) {
      settings.activation = settings.act;
      delete settings.act;
    }
    // Type conversion for DB compatibility
    [
      'max_epoch', 'depth', 'width', 'warmup_epochs', 'warmup_lr', 'no_aug_epochs', 'min_lr_ratio', 'weight_decay', 'momentum', 'print_interval', 'eval_interval', 'test_conf', 'nmsthre', 'multiscale_range', 'degrees', 'translate', 'shear', 'train', 'valid', 'test'
    ].forEach(f => {
      if (settings[f] !== undefined) settings[f] = Number(settings[f]);
    });
    // Improved boolean conversion
    ['ema', 'enable_mixup', 'save_history_ckpt'].forEach(f => {
      if (settings[f] !== undefined) {
        if (typeof settings[f] === 'string') {
          settings[f] = settings[f].toLowerCase() === 'true';
        } else {
          settings[f] = Boolean(settings[f]);
        }
      }
    });
    // Improved array conversion
    ['mosaic_scale', 'mixup_scale', 'scale'].forEach(f => {
      if (settings[f] && typeof settings[f] === 'string') {
        settings[f] = settings[f]
          .split(',')
          .map(s => Number(s.trim()))
          .filter(n => !isNaN(n));
      }
    });
    // Trim all string fields
    Object.keys(settings).forEach(f => {
      if (typeof settings[f] === 'string') settings[f] = settings[f].trim();
    });
    // Set default for transfer_learning if missing
    if (settings.transfer_learning === undefined) settings.transfer_learning = false;
    // Convert empty string seed to null
    if ('seed' in settings && (settings.seed === '' || settings.seed === undefined)) {
      settings.seed = null;
    }
    // Validate required fields for training table
    for (const field of requiredFields) {
      if (settings[field] === undefined || settings[field] === null || settings[field] === '') {
        console.error('Missing required field:', field, 'Value:', settings[field]);
        throw new Error('Missing required field: ' + field);
      }
    }
    console.log('Received YOLOX settings:', settings);
    // Handle uploaded model file (ckpt_upload)
    if (req.files && req.files.length > 0) {
      const ckptFile = req.files.find(f => f.fieldname === 'ckpt_upload');
      if (ckptFile) {
        const uploadDir = path.join(__dirname, '..', 'uploads');
        if (!fs.existsSync(uploadDir)) fs.mkdirSync(uploadDir);
        const filename = ckptFile.originalname || `uploaded_model_${settings.project_id}.pth`;
        const filePath = path.join(uploadDir, filename);
        fs.writeFileSync(filePath, ckptFile.buffer);
        settings.model_upload = filePath;
      }
    }
    // Save settings to DB only (no file)
    const { pushYoloxExpToDb } = require('../services/push-yolox-exp.js');
    const training = await pushYoloxExpToDb(settings);
    res.json({ message: 'YOLOX settings saved to DB', training });
  } catch (error) {
    console.error('Error in /api/yolox-settings:', error.stack || error);
    res.status(500).json({ message: 'Failed to save YOLOX settings', error: error.message });
  }
});

// POST endpoint to receive binary model file and save to disk (not DB)
router.post('/yolox-settings/upload', async (req, res) => {
  try {
    const projectId = req.query.project_id;
    if (!projectId) return res.status(400).json({ message: 'Missing project_id in query' });
    // Save file to disk
    const uploadDir = path.join(__dirname, '..', 'uploads');
    if (!fs.existsSync(uploadDir)) fs.mkdirSync(uploadDir);
    const filename = req.headers['x-upload-filename'] || `uploaded_model_${projectId}.pth`;
    const filePath = path.join(uploadDir, filename);
    const chunks = [];
    req.on('data', chunk => chunks.push(chunk));
    req.on('end', async () => {
      const buffer = Buffer.concat(chunks);
      fs.writeFile(filePath, buffer, async err => {
        if (err) {
          console.error('Error saving file:', err);
          return res.status(500).json({ message: 'Failed to save model file', error: err.message });
        }
        // Update latest training row for this project with file path
        try {
          const TrainingProjectDetails = require('../models/TrainingProjectDetails.js');
          const Training = require('../models/training.js');
          // Find details row for this project
          const details = await TrainingProjectDetails.findOne({ where: { project_id: projectId } });
          if (!details) return res.status(404).json({ message: 'No TrainingProjectDetails found for project_id' });
          // Find latest training for this details row
          const training = await Training.findOne({ where: { project_details_id: details.id }, order: [['createdAt', 'DESC']] });
          if (!training) return res.status(404).json({ message: 'No training found for project_id' });
          // Save file path to model_upload field
          training.model_upload = filePath;
          await training.save();
          res.json({ message: 'Model file uploaded and saved to disk', filename, trainingId: training.id });
        } catch (dbErr) {
          console.error('Error updating training with file path:', dbErr);
          res.status(500).json({ message: 'File saved but failed to update training row', error: dbErr.message });
        }
      });
    });
  } catch (error) {
    console.error('Error in /api/yolox-settings/upload:', error.stack || error);
    res.status(500).json({ message: 'Failed to upload model file', error: error.message });
  }
});

// GET endpoint to fetch all trainings (optionally filtered by project_id)
router.get('/trainings', async (req, res) => {
  try {
    const project_id = req.query.project_id;
    const TrainingProjectDetails = require('../models/TrainingProjectDetails.js');
    const Training = require('../models/training.js');
    if (project_id) {
      // Find all details rows for this project
      const detailsRows = await TrainingProjectDetails.findAll({ where: { project_id } });
      if (!detailsRows || detailsRows.length === 0) return res.json([]);
      // Get all trainings linked to any details row for this project
      const detailsIds = detailsRows.map(d => d.id);
      const trainings = await Training.findAll({ where: { project_details_id: detailsIds } });
      return res.json(trainings);
    } else {
      // Return all trainings if no project_id is specified
      const trainings = await Training.findAll();
      return res.json(trainings);
    }
  } catch (error) {
    res.status(500).json({ message: 'Failed to fetch trainings', error: error.message });
  }
});

// DELETE endpoint to remove a training by id
router.delete('/trainings/:id', async (req, res) => {
  try {
    const Training = require('../models/training.js');
    const id = req.params.id;
    const deleted = await Training.destroy({ where: { id } });
    if (deleted) {
      res.json({ message: 'Training deleted' });
    } else {
      res.status(404).json({ message: 'Training not found' });
    }
  } catch (error) {
    res.status(500).json({ message: 'Failed to delete training', error: error.message });
  }
});

// DELETE endpoint to remove a training project and all related entries
router.delete('/training-projects/:id', async (req, res) => {
  try {
    const projectId = req.params.id;
    const TrainingProject = require('../models/TrainingProject.js');
    const TrainingProjectDetails = require('../models/TrainingProjectDetails.js');
    const Training = require('../models/training.js');
    // Find details row(s) for this project
    const detailsRows = await TrainingProjectDetails.findAll({ where: { project_id: projectId } });
    const detailsIds = detailsRows.map(d => d.id);
    // Delete all trainings linked to these details
    if (detailsIds.length > 0) {
      await Training.destroy({ where: { project_details_id: detailsIds } });
      await TrainingProjectDetails.destroy({ where: { project_id: projectId } });
    }
    // Delete the project itself
    const deleted = await TrainingProject.destroy({ where: { project_id: projectId } });
    if (deleted) {
      res.json({ message: 'Training project and all related entries deleted' });
    } else {
      res.status(404).json({ message: 'Training project not found' });
    }
  } catch (error) {
    res.status(500).json({ message: 'Failed to delete training project', error: error.message });
  }
});

module.exports = router;
