import express from 'express'
import expressWs from 'express-ws'
import { sequelize, Structure } from './db.js'
import multer from 'multer'
import { logger } from './logger.js'
import { serveStatic } from './staticPath.js'
import { redisClient } from './redis.js'
import { initOnlineUserCount } from './online-counter.js'
import { initSubscribeStructureLikes, publishLikes } from './likes.js'

const storage = multer.memoryStorage()
const upload = multer({ dest: 'uploads/', storage })

const app = express()
expressWs(app)
const port = 3000

app.use(express.json());

try {
    await sequelize.authenticate();
    await Structure.sync({ alter: true });
    logger.info('Connection to the database has been established successfully.');
} catch (error) {
    logger.error('Unable to connect to the database:', error);
    process.exit(1);
}

app.get('/api/health', (req, res) => {
    res.send('ok')
})

app.post('/api/structures', upload.single('structureFile'), async (req, res) => {
    if (!req.body) {
        return res.status(400).json({ error: 'Request body is required' })
    }

    const { name, description } = req.body
    const structureFile = req.file

    if (!name || !description || !structureFile) {
        return res.status(400).json({ error: 'name, description, and structureFile are required' })
    }

    try {
        const structure = await Structure.create({
            name,
            description,
            structureFile: structureFile.buffer
        })
        res.status(201).json({
            id: structure.id,
            name: structure.name,
            description: structure.description,
            likes: structure.likes,
            createdAt: structure.createdAt,
            updatedAt: structure.updatedAt
        })
    } catch (error) {
        logger.error('Error creating structure:', error)
        res.status(500).json({ error: 'Internal server error' })
    }
})

app.get('/api/structures', async (req, res) => {
    try {
        const structures = await Structure.findAll()
        res.status(200).json(structures.map(structure => ({
            id: structure.id,
            name: structure.name,
            description: structure.description,
            likes: structure.likes,
            createdAt: structure.createdAt,
            updatedAt: structure.updatedAt
        })))
    } catch (error) {
        logger.error('Error fetching structures:', error)
        res.status(500).json({ error: 'Internal server error' })
    }
})

app.get('/api/structures/:id', async (req, res) => {
    const { id } = req.params

    try {
        const structure = await Structure.findByPk(id)
        if (!structure) {
            return res.status(404).json({ error: 'Structure not found' })
        }
        res.status(200).json({
            id: structure.id,
            name: structure.name,
            description: structure.description,
            likes: structure.likes,
            createdAt: structure.createdAt,
            updatedAt: structure.updatedAt
        })
    } catch (error) {
        logger.error('Error fetching structure:', error)
        res.status(500).json({ error: 'Internal server error' })
    }
})

app.get('/api/structures/:id/file', async (req, res) => {
    const { id } = req.params

    try {
        const structure = await Structure.findByPk(id)
        if (!structure) {
            return res.status(404).json({ error: 'Structure not found' })
        }

        res.set('Content-Type', 'application/octet-stream')
        res.set('Content-Disposition', `attachment; filename="${structure.name}.mca"`)
        res.send(structure.structureFile)
    } catch (error) {
        logger.error('Error fetching structure file:', error)
        res.status(500).json({ error: 'Internal server error' })
    }
})

app.post('/api/structures/:id/like', async (req, res) => {
    const { id } = req.params

    try {
        const structure = await Structure.findByPk(id)
        if (!structure) {
            return res.status(404).json({ error: 'Structure not found' })
        }

        structure.likes += 1
        await structure.save()

        publishLikes(structure.id, structure.likes)

        res.status(200).json({
            id: structure.id,
            name: structure.name,
            description: structure.description,
            likes: structure.likes,
            createdAt: structure.createdAt,
            updatedAt: structure.updatedAt
        })
    } catch (error) {
        logger.error('Error liking structure:', error)
        res.status(500).json({ error: 'Internal server error' })
    }
})

initSubscribeStructureLikes(app);
initOnlineUserCount(app);

// Serve static files when STATIC_PATH is set
const staticPath = process.env.STATIC_PATH;
if (staticPath) {
    serveStatic(app, staticPath)
} else {
    logger.warn('STATIC_PATH is not set, not serving static files');
}

try {
    await redisClient.connect()
} catch (err) {
    logger.error('Error connecting to Redis: ', err)
    process.exit(1)
}

const server = app.listen(port, () => {
    logger.info(`MineDrive listening on port ${port}`)

    const shutdown = exitCode => {
        server.close(() => {
            logger.info('Server shutdown complete.');
        });

        process.exit(exitCode);
    }

    process.on('SIGTERM', () => {
        logger.info('Received SIGTERM. Exiting...');
        shutdown(0);
    })

    redisClient.on('error', err => {
        logger.error('Redis error: ', err)
        logger.info('Shutting down server due to Redis error...')
        shutdown(1)
    })
})