const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const cors = require('cors');
const crypto = require('crypto');
const didJwt = require('did-jwt');
const { Resolver } = require('did-resolver');
const express = require('express');
const { getResolver } = require('ethr-did-resolver');
const fs = require('fs');
const multer = require('multer');
const path = require('path');
const sqlite3 = require('sqlite3').verbose();

require('dotenv').config()

const app = express();
app.use(cors());

const port = process.env.PORT || 3000;
// file name also referenced in flyway.conf and potentially in .env files or in environment variables
const dbFile = process.env.SQLITE_FILE || './image-db.sqlite';

const ethrDidResolver = getResolver;
const resolver =
  new Resolver({
    ...ethrDidResolver({
      infuraProjectId: process.env.INFURA_PROJECT_ID || 'fake-infura-project-id'
    })
  })

// Open a connection to the SQLite database
const db = new sqlite3.Database(dbFile, (err) => {
  if (err) {
    console.error('Error opening database:', err);
  }
});

// Configure AWS
const s3Client = new S3Client({
  region: process.env.AWS_REGION,
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY,
    secretAccessKey: process.env.AWS_SECRET_KEY
  }
});

const uploadDir = 'uploads';
const uploadMulter = multer({ dest: uploadDir + '/' });

// POST endpoint to upload an image
app.post('/image', uploadMulter.single('image'), async (req, res) => {

  // Verify the JWT
  try {
    const auth = req.headers.authorization;
    if (!auth || !auth.startsWith('Bearer ')) {
      return res.status(401).send(JSON.stringify({ success: false, message: 'Missing "Bearer JWT" in Authorization header.'}));
    }
    const jwt = auth.substring('Bearer '.length);
    const verified = await didJwt.verifyJWT(jwt, {resolver});
    if (!verified) {
      return res.status(401).send(JSON.stringify({ success: false, message: 'Got invalid JWT in Authorization header.'}));
    }
    const issuerDid = verified.issuer;

    // Read the file from the temporary location
    fs.readFile(req.file.path, async (err, data) => {
      if (err) throw err; // Handle error

      const hashSum = crypto.createHash('sha256');
      hashSum.update(data);
      const hashHex = hashSum.digest('hex');

      const bucketName = 'gifts-image';
      const fileName = hashHex;
      const params = {
        Body: data,
        Bucket: bucketName, // S3 Bucket name
        ContentType: req.file.mimetype, // File content type
        Key: fileName, // File name to use in S3
      };

      try {

        // record the upload in the database
        const currentDate = new Date().toISOString();
        const localFile = req.file.path.startsWith(uploadDir + '/') ? req.file.path.substring(uploadDir.length + 1) : req.file.path;
        const finalUrl = `https://${bucketName}.s3.amazonaws.com/${fileName}`;
        await db.run('INSERT INTO image (date, did, local_file, size, aws_file, url) VALUES (?, ?, ?, ?, ?, ?)', [
          currentDate,
          issuerDid,
          localFile,
          req.file.size,
          fileName,
          finalUrl
        ], (dbErr) => {
          if (dbErr) {
            console.error(currentDate, "Error inserting record from", issuerDid, "into database:", dbErr);
            // don't continue because then we'll have storage we cannot track (and potentially limit)
            throw dbErr;
          }
        });

        // send to AWS
        const command = new PutObjectCommand(params);
        const response = await s3Client.send(command);
        if (response.$metadata.httpStatusCode !== 200) {
          const errorTime = new Date().toISOString();
          console.error(errorTime, "Error uploading to S3 with bad HTTP status, with metadata:", response.$metadata);
          res.status(500).send(JSON.stringify({
            success: false,
            message: "Got bad status of " + response.$metadata.httpStatusCode + " from AWS. See server logs at " + errorTime
          }));
        } else {
          fs.rm(req.file.path, (err) => {
            if (err) {
              console.error("Error deleting temp file", req.file.path, "with error:", err);
            }
          });
          res.send(JSON.stringify({success: true, url: finalUrl}));
        }
      } catch (uploadError) {
        const errorTime = new Date().toISOString();
        console.error(errorTime, "Error uploading to S3:", uploadError);
        res.status(500).send(JSON.stringify({
          success: false,
          message: "Got error uploading file. See server logs at " + errorTime + " Error Details: " + uploadError
        }));
      }
    })
  } catch (error) {
    const errorTime = new Date().toISOString();
    console.error(errorTime, "Error processing image upload:", error);
    res.status(500).send(JSON.stringify({
      success: false,
      message: "Got error processing image upload. See server logs at " + errorTime + " Error Details: " + error
    }));
  }
});

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
});

// Close the database connection when the Node.js app ends
process.on('SIGINT', () => {
  db.close((err) => {
    if (err) {
      console.error('Error closing DB connection:', err);
      return;
    }
    process.exit(0);
  });
});