const express = require('express');
const { Client } = require('pg');
const session = require('express-session');
const dotenv = require('dotenv');
const MemoryStore = require('memorystore')(session);
const fs = require('fs');
const bcrypt = require('bcryptjs');
// const fileupload = require('express-fileupload');
const cors = require('cors');

const app = express();

dotenv.config({ path: './web.env' });
const default_board_settings = require('../default_board_settings.json');

const db = new Client({
    user: process.env.DB_USER,
    host: process.env.DB_HOST,
    database: process.env.DB_NAME,
    password: process.env.DB_PASS,
    port: 5432
});

const errorHandler = (err) => {
    if (err) console.log(err);
}

db.connect((error) => {
    if (error) console.log(error);
    else console.log("Database connected");
    db.query('SELECT to_regclass(\'admins\');', (err, res) => {
        if(res.rows[0].to_regclass != "admins") init();
    })
})

const init = async () => {
    let initSQL = fs.readFileSync("./database_schematic.pgsql").toString();
    
    console.log("No tables found, assuming first run, creating database scheme");

    db.query(initSQL, errorHandler);

    let adminPassword = Math.random().toString(36).slice(-8);
    let passwordHash = await bcrypt.hash(adminPassword, 8);
    console.log(`Creating admin account with credentials: admin:${adminPassword}`);
    db.query("INSERT INTO priveleges (privelege_name, access_level) VALUES ('admin', 100)");
    db.query("INSERT INTO admins (login, password_hash, privelege_name) VALUES ('admin', $1, 'admin')", [passwordHash])
}

let tokens = {};

app.use(express.urlencoded({ extended: true }))
app.use(express.json())
app.use(cors());

app.use(session({
    secret: process.env.SESSION_SECRET,
    store: new MemoryStore({
        checkPeriod: 86400000
    }),
    resave: false,
    saveUninitialized: false,
    cookie: { maxAge: 1000*60*60*24 }
}));


app.post('/api/uploadMedia', async (req, res) => {

});

app.get('/api/getPosts/:boardId/:threadId', async (req, res) => {
    posts = [];
    (await db.query('SELECT post_id, content, timestamp, options FROM posts WHERE board_id = $1 AND thread_id = $2', [req.params.boardId, req.params.threadId])).rows
        .forEach((post) => posts.push(post))
    
    res.setHeader('Content-Type', 'application/json');
    res.end(JSON.stringify(posts));
});

app.post('/api/post', async (req, res) => {
    let login = req.session.login, 
        token = req.session.token, 
        isAdmin = false;
    const {options, content, threadId, boardId} = req.body;

    if (!threadId || !boardId) return res.status(400).send("Не указано ID треда или доски");

    if (login && token) {
        if (authorize(login, token)) isAdmin = true;
        else res.status(403).send("Невалидный токен");
    }

    let postId = Number((await db.query('SELECT post_id FROM posts WHERE board_id = $1 ORDER BY post_id DESC LIMIT 1', [boardId])).rows[0].post_id) + 1
    await db.query('INSERT INTO posts(board_id, thread_id, post_id, options, content, media_ids, is_root, timestamp, user_ip) VALUES ($1, $2, $3, $4, $5, \'{}\', false, NOW(), $6)', [boardId, threadId, postId, options, content, req.socket.remoteAddress]);
    await db.query('UPDATE threads SET posts_ids = ARRAY_APPEND(posts_ids, $1) WHERE thread_id = $2 AND board_id = $3', [postId, threadId, boardId]);
 
    res.status(200).send("Пост отправлен"); 
});

app.post('/api/createThread', async (req, res) => {
    let login = req.session.login,
        token = req.session.token,
        isLocked,
        isPinned,
        isAdmin = false;
    const { boardId, threadTitle, content, options} = req.body;

    if (!boardId) return res.status(400).send("Не указано имя доски");

    isLocked = isLocked? isLocked : false; // if undefined then false
    isPinned = isPinned? isPinned : false;

    console.log(`Board id: ${boardId}\nThread name: ${threadTitle}\nIs locked: ${isLocked}\nIs pinned: ${isPinned}\nContent: ${content}\nOptions: ${options}`);
    
    if (login && token) {
        if (authorize(login, token)) isAdmin = true;
        else res.status(403).send("Невалидный токен");
    }

    const boardOptions = (await db.query('SELECT options FROM boards WHERE board_id = $1', [boardId])).rows[0].options;

    let threadId = (await db.query('SELECT EXISTS(SELECT FROM threads WHERE board_id = $1)', [boardId])).rows[0].exists?
        Number((await db.query('SELECT thread_id FROM threads WHERE board_id = $1 ORDER BY thread_id DESC LIMIT 1', [boardId])).rows[0].thread_id) + 1
        : 0
    let postId = (await db.query('SELECT EXISTS(SELECT FROM posts WHERE board_id = $1)', [boardId])).rows[0].exists?
        Number((await db.query('SELECT post_id FROM posts WHERE board_id = $1 ORDER BY post_id DESC LIMIT 1', [boardId])).rows[0].post_id) + 1
        : 0
    console.log(`ThreadId: ${threadId} postId: ${postId}`);

    let validateResults = validateThread(threadTitle, isLocked, 
                         isPinned, content, options, 
                         boardOptions, isAdmin);
    if (validateResults != "ok") return res.status(400).send(validateResults);

    await db.query('INSERT INTO posts (board_id, thread_id, post_id, content, is_root, timestamp, user_ip) VALUES($1, $2, $3, $4, $5, NOW(), $6)', [boardId, threadId, postId, content, true, req.socket.remoteAddress]);
    await db.query('INSERT INTO threads (board_id, thread_id, thread_name, posts_ids, is_locked, is_pinned, options) VALUES ($1, $2, $3, $4, $5, $6, $7)', [boardId, threadId, threadTitle, [postId], isLocked, isPinned, options]);
    res.redirect(`/${boardId}/${postId}`);    
});

app.get('/api/getThreads/:boardId', async (req, res) => {
    threads = [];
    (await db.query('SELECT thread_id FROM threads WHERE board_id = $1', [req.params.boardId])).rows
        .forEach((thread) => threads.push(thread.thread_id))
    
    res.setHeader('Content-Type', 'application/json');
    res.end(JSON.stringify(threads));
});

app.get('/api/getBoards', async (req, res) => {
    let queryRes = await db.query('SELECT * FROM boards');

    res.setHeader('Content-Type', 'application/json');
    res.end(JSON.stringify(queryRes.rows));
    // res.json(JSON.stringify(queryRes.rows));
});

app.post('/api/login', async (req, res) => {
    const { login, password } = req.body;

    if (!(login && password)) return res.status(401).send("Не указан логин или пароль");
    
    queryRes = await db.query('SELECT * FROM admins WHERE login = $1', [login])
    if (queryRes.rowCount == 0) return res.status(401).send("Такого лоигна нет");

    let hashedPassword = queryRes.rows[0].password_hash;
    bcrypt.compare(password, hashedPassword, (err, result) => {
        if (!result) return res.status(403).send("Пароли не совпадают");
        let currentSession = req.session;
        currentSession.login = login;
        if (!tokens[login]) {
            let token = Math.random().toString(26).slice(2); // ONLY IN DEV MODE
            tokens[login] = token;
            currentSession.token = token;
            console.log(token);
        }
        res.redirect("/");
    });
});

app.post('/api/createBoard', async (req, res) => {
    let login, token;
    let { boardId, boardTitle, options} = req.body;

    try {
        let currentSession = req.session;
        token = currentSession.token;
        login = currentSession.login;
    } catch (err) {
        console.log(err);
    }

    if (token != tokens[login] || !token) return res.status(403).send("Невалидный токен");
    if (!boardId || !boardTitle) return res.status(400).send("Неверно сформирован запрос");

    console.log(`Admin ${login} is creating new board: ${boardId}, ${boardTitle}`);

    let queryRes = await db.query('SELECT * FROM boards WHERE board_id = $1::text', [boardId]);
    if (boardId.length == 0 || boardId.length > 5) return res.status(401).send("Неверный размер URI борды");
    if (boardTitle.length == 0 || boardTitle.length > 32) return res.status(401).send("Неверный размер имени борды");
    if (queryRes.rowCount > 0) return res.status(401).send("Такая борда уже существует.");
    
    if (!options) options = default_board_settings;

    await db.query('INSERT INTO boards (board_id, board_name, options) VALUES ($1, $2, $3)', [boardId, boardTitle, options]);
    return res.status(200).send("Борда успешно создана");
});

app.listen(process.env.APP_PORT, () => {
    console.log("App started");
});

const validateThread = (threadName, isLocked, isPinned, content, options, boardOptions, isAdmin) => {
    if ((isPinned || isLocked) && !isAdmin) return "Нет прав на выставление админских флагов";
    if (!content && boardOptions.requireContentForThreadCreation) return "Нельзя создать тред без текста";

    //TODO: check if image is required
    return 'ok'
};

const authorize = (login, token) => {
    return tokens[login] == token? true : false;
};