import dotenv from 'dotenv';
import { fileModel } from '../../database/schemas/management/file.schema.js';
import log4js from 'log4js';
import multer from 'multer';
import path from 'path';
import fs from 'fs';
import mongoose from 'mongoose';
import {
  deleteObject,
  listObjects,
  getObject,
  editObject,
  newObject,
  listObjectsByProperties,
  flushFile,
  getModelStats,
  getModelHistory,
} from '../../database/database.js';
import {
  uploadFile,
  downloadFile,
  deleteFile as deleteCephFile,
  BUCKETS,
} from '../storage/ceph.js';
import { getFileMeta } from '../../utils.js';
dotenv.config();

const logger = log4js.getLogger('Files');
logger.level = process.env.LOG_LEVEL;

// Set storage engine to memory for Ceph upload
const fileStorage = multer.memoryStorage();

// Initialise upload
const fileUpload = multer({
  storage: fileStorage,
  limits: { fileSize: 500000000 }, // 500MB limit
  fileFilter: function (req, file, cb) {
    checkFileType(file, cb);
  },
}).single('file'); // The name attribute of the file input in the HTML form

export const listFilesRouteHandler = async (
  req,
  res,
  page = 1,
  limit = 25,
  property = '',
  filter = {},
  search = '',
  sort = '',
  order = 'ascend'
) => {
  const result = await listObjects({
    model: fileModel,
    page,
    limit,
    property,
    filter,
    search,
    sort,
    order,
  });

  if (result?.error) {
    logger.error('Error listing files.');
    res.status(result.code).send(result);
    return;
  }

  logger.debug(`List of files (Page ${page}, Limit ${limit}). Count: ${result.length}`);
  res.send(result);
};

export const listFilesByPropertiesRouteHandler = async (
  req,
  res,
  properties = '',
  filter = {},
  masterFilter = {}
) => {
  const result = await listObjectsByProperties({
    model: fileModel,
    properties,
    filter,
    masterFilter,
  });

  if (result?.error) {
    logger.error('Error listing files.');
    res.status(result.code).send(result);
    return;
  }

  logger.debug(`List of files. Count: ${result.length}`);
  res.send(result);
};

export const getFileRouteHandler = async (req, res) => {
  const id = req.params.id;
  const result = await getObject({
    model: fileModel,
    id,
  });
  if (result?.error) {
    logger.warn(`File not found with supplied id.`);
    return res.status(result.code).send(result);
  }
  logger.debug(`Retreived file with ID: ${id}`);
  res.send(result);
};

export const flushFileRouteHandler = async (req, res) => {
  const id = req.params.id;
  const result = await flushFile({ user: req.user, id });
  res.send(result);
};

export const editFileRouteHandler = async (req, res) => {
  // Get ID from params
  const id = new mongoose.Types.ObjectId(req.params.id);

  logger.trace(`File with ID: ${id}`);

  const updateData = {
    updatedAt: new Date(),
    name: req.body.name,
  };

  const result = await editObject({
    model: fileModel,
    id,
    updateData,
    user: req.user,
  });

  if (result.error) {
    logger.error('Error editing file:', result.error);
    res.status(result).send(result);
    return;
  }

  logger.debug(`Edited file with ID: ${id}`);

  res.send(result);
};

export const newFileRouteHandler = async (req, res) => {
  try {
    fileUpload(req, res, async (err) => {
      if (err) {
        return res.status(500).send({
          error: err,
        });
      }

      if (req.file == undefined) {
        return res.send({
          message: 'No file selected!',
        });
      }

      try {
        // Create DB entry first without storage fields
        const extension = path.extname(req.file.originalname);
        const baseName = path.parse(req.file.originalname).name;
        const meta = await getFileMeta(req.file);
        const newData = {
          name: baseName,
          type: req.file.mimetype,
          extension,
          size: req.file.size,
          updatedAt: new Date(),
          metaData: {
            originalName: req.file.originalname,
            ...meta,
          },
        };

        const created = await newObject({
          model: fileModel,
          newData,
          user: req.user,
        });

        if (created.error) {
          logger.error('No file created:', created.error);
          return res.status(created.code).send(created);
        }

        // Use created document _id to generate Ceph key
        const cephKey = `files/${created._id}${extension}`;

        // Upload file to Ceph
        await uploadFile(BUCKETS.FILES, cephKey, req.file.buffer, req.file.mimetype, {
          originalName: req.file.originalname,
          uploadedBy: req.user?.username || 'unknown',
        });

        // Do not update DB with Ceph location. Return created DB record.
        logger.debug(`New file with ID: ${created._id} created and uploaded to Ceph`);

        res.send(created);
      } catch (createError) {
        logger.error('Error creating file record or uploading to storage:', createError);
        // If we created the DB entry but upload failed, remove the DB entry to avoid orphaned records
        try {
          if (created && created._id) {
            await deleteObject({ model: fileModel, id: created._id, user: req.user });
          }
        } catch (cleanupDbError) {
          logger.error('Error cleaning up DB record after upload failure:', cleanupDbError);
        }
        res.status(500).send({ error: createError.message });
      }
    });
  } catch (error) {
    logger.error('Error in newFileRouteHandler:', error);
    res.status(500).send({ error: error.message });
  }
};

export const deleteFileRouteHandler = async (req, res) => {
  // Get ID from params
  const id = new mongoose.Types.ObjectId(req.params.id);

  logger.trace(`File with ID: ${id}`);

  try {
    // First get the file to retrieve Ceph information
    const file = await getObject({
      model: fileModel,
      id,
    });
    if (!file) {
      return res.status(404).send({ error: 'File not found' });
    }

    // Delete from Ceph if it exists there
    if (file.cephBucket && file.cephKey) {
      try {
        await deleteCephFile(file.cephBucket, file.cephKey);
        logger.debug(`Deleted file from Ceph: ${file.cephKey}`);
      } catch (cephError) {
        logger.warn(`Failed to delete file from Ceph: ${cephError.message}`);
        // Continue with database deletion even if Ceph deletion fails
      }
    }

    const result = await deleteObject({
      model: fileModel,
      id,
      user: req.user,
    });
    if (result.error) {
      logger.error('No file deleted:', result.error);
      return res.status(result.code).send(result);
    }

    logger.debug(`Deleted file with ID: ${result._id}`);

    res.send(result);
  } catch (error) {
    logger.error('Error in deleteFileRouteHandler:', error);
    res.status(500).send({ error: error.message });
  }
};

// Check file type
function checkFileType(file, cb) {
  // Allow all file types for general file management
  // You can customize this to restrict specific file types if needed
  const allowedTypes = /.*/; // Allow all file types

  if (allowedTypes.test(file.mimetype)) {
    console.log(file);
    return cb(null, true);
  } else {
    cb('Error: File type not allowed!');
  }
}

export const getFileContentRouteHandler = async (req, res) => {
  try {
    const id = req.params.id;

    const file = await getObject({
      model: fileModel,
      id,
    });

    if (!file) {
      logger.warn(`File not found with supplied id.`);
      return res.status(404).send({ error: 'File not found.' });
    }

    logger.trace(`Returning file contents with ID: ${id}:`);

    // Check if file is stored in Ceph
    if (file._id && file.extension) {
      const cephKey = `files/${id}${file.extension}`;
      try {
        const body = await downloadFile(BUCKETS.FILES, cephKey);

        // Set appropriate content type and disposition
        res.set('Content-Type', file.type || 'application/octet-stream');
        res.set('Content-Disposition', `attachment; filename="${file.name}${file.extension}"`);

        // Expose file size so clients can compute download progress
        if (typeof file.size === 'number' && !Number.isNaN(file.size)) {
          res.set('Content-Length', String(file.size));
        }

        // Stream or send buffer
        if (body && typeof body.pipe === 'function') {
          // Handle stream errors
          body.on('error', (err) => {
            logger.error('Error streaming file from Ceph:', err);
            // If headers not sent, send a 500; otherwise destroy the response
            if (!res.headersSent) {
              try {
                res.status(500).send({ error: 'Error streaming file from storage.' });
              } catch (_) {
                // Ignore secondary errors
              }
            } else {
              res.destroy(err);
            }
          });

          // If client disconnects, stop reading from source
          res.on('close', () => {
            if (typeof body.destroy === 'function') {
              body.destroy();
            }
          });

          body.pipe(res);
          logger.debug('Retrieved:', cephKey);
          return;
        }

        // Unknown body type
        logger.error('Unknown Ceph body type; cannot send response');
        return res.status(500).send({ error: 'Error reading file from storage.' });
      } catch (cephError) {
        logger.error('Error downloading file from Ceph:', cephError);
        // Fall through to local filesystem fallback below
      }

      // Fallback to local file system for backward compatibility
      const filePath = path.join(
        process.env.FILE_STORAGE || './uploads',
        file.fileName || file.name
      );

      // Read the file
      fs.readFile(filePath, (err, data) => {
        if (err) {
          if (err.code === 'ENOENT') {
            return res.status(404).send({ error: 'File not found!' });
          }
          return res.status(500).send({ error: 'Error reading file.' });
        }

        res.set('Content-Type', file.type || 'application/octet-stream');
        res.set('Content-Disposition', `inline; filename="${file.name}${file.extension || ''}"`);

        // Ensure Content-Length is set for progress events if possible.
        const length =
          typeof file.size === 'number' && !Number.isNaN(file.size) ? file.size : data.length;
        res.set('Content-Length', String(length));

        return res.send(data);
      });
    } else {
      logger.error('Error fetching file:', 'No ceph bucket or key supplied.');
      res.status(500).send({ error: 'No ceph bucket or key supplied.' });
    }
  } catch (error) {
    logger.error('Error fetching file:', error);
    res.status(500).send({ error: error.message });
  }
};

export const parseFileHandler = async (req, res) => {
  try {
    // Use the same upload middleware as the uploadFileContentRouteHandler
    fileUpload(req, res, async (err) => {
      if (err) {
        return res.status(500).send({
          error: err,
        });
      }

      if (req.file == undefined) {
        return res.send({
          message: 'No file selected!',
        });
      }

      try {
        // Read the file content from memory buffer
        const fileContent = req.file.buffer.toString('utf8');

        // Return basic file info as JSON
        const fileInfo = {
          filename: req.file.originalname,
          originalName: req.file.originalname,
          size: req.file.size,
          mimetype: req.file.mimetype,
          content: fileContent.substring(0, 1000), // First 1000 characters
        };

        res.json(fileInfo);
      } catch (parseError) {
        logger.error('Error parsing file:', parseError);
        res.status(500).send({ error: parseError.message });
      }
    });
  } catch (error) {
    logger.error('Error in parseFileHandler:', error);
    res.status(500).send({ error: error.message });
  }
};

export const getFileStatsRouteHandler = async (req, res) => {
  const result = await getModelStats({ model: fileModel });
  if (result?.error) {
    logger.error('Error fetching file stats:', result.error);
    return res.status(result.code).send(result);
  }
  logger.trace('File stats:', result);
  res.send(result);
};

export const getFileHistoryRouteHandler = async (req, res) => {
  const from = req.query.from;
  const to = req.query.to;
  const result = await getModelHistory({ model: fileModel, from, to });
  if (result?.error) {
    logger.error('Error fetching file history:', result.error);
    return res.status(result.code).send(result);
  }
  logger.trace('File history:', result);
  res.send(result);
};
