const express = require("express");
const sql = require("mssql");
const bcrypt = require("bcrypt");
const cors = require("cors");
const jwt = require("jsonwebtoken")
const dotenv = require("dotenv")

const serverPort = 3001;

const dev = process.argv.length > 2 && process.argv[2] == "-dev";

const app = express();
app.use(express.json());
app.use(express.static("public"))
app.use(cors({
    origin: [
        "https://project.rochesterx.dev",
        "https://localhost:3001"
    ]
}));

let dbConfig = null;

dbConfig = {
    user: "server",
    password: "TorusField1*",
    server: "mssql.rochesterx.dev",
    database: "ProjectTest",
    options: { trustServerCertificate: true }
};

let pool = null;
setupPool();

dotenv.config();

const JWT_SECRET = process.env.JWT_SECRET;
if (!JWT_SECRET) {
    throw new Error("JWT_SECRET not set in environment.");
}

async function setupPool() {
    pool = await sql.connect(dbConfig);
}

app.post("/register", async (req, res) => {
    const { username, password, role } = req.body;
    try {
        const hash = await bcrypt.hash(password, 10);
        await pool.request()
            .input("username", sql.VarChar, username)
            .input("hash", sql.VarChar, hash)
            .input("role", sql.VarChar, role)
            .query("INSERT INTO Users (Username, PasswordHash, Role, CreatedAt) VALUES (@username, @hash, @role, SYSUTCDATETIME())");
        res.send({ success: true, message: "User registered" })
    } catch (err) {
        if (err.message.includes("Violation of UNIQUE KEY constraint")) {
            res.status(500).send({ success: false, message: `Username "${username}" is already taken.` });
        }
        res.status(500).send({ success: false, message: err.message });
    }
});

app.post("/login", async (req, res) => {
    const { username, password } = req.body;
    try {
        const result = await pool.request()
            .input("username", sql.VarChar, username)
            .query("SELECT * FROM Users WHERE Username = @username");
        
        if (result.recordset.length == 0) return res.status(400).send({ message: "User not found" });

        const hash = result.recordset[0].PasswordHash;
        const isDeleted = result.recordset[0].IsDeleted;

        if (isDeleted === true) {
            return res.status(400).json({ message: "User not found (deleted)" })
        }

        const match = await bcrypt.compare(password, hash);

        if (match){
            const token = jwt.sign(result.recordset[0], JWT_SECRET, { expiresIn: "1h" });
            res.send({
                success: true,
                message: "Login successful",
                token
            });
            await pool.request()
                .input("username", sql.VarChar, username)
                .query("UPDATE Users SET LastLogin = SYSUTCDATETIME() WHERE Username = @username");
            
            console.log("Issued token: " + JSON.stringify(token))
        }
        else res.status(401).send({ success: false, message: "Invalid credentials" });
    } catch (err) {
        res.status(500).send({ success: false, message: err.message });
    }
});

app.post("/getPlayers", authenticate, async (req, res) => {
    const { player } = req.body;

    const result = await pool.request()
        .input("query", sql.VarChar, player)
        .query(`SELECT * FROM [Player, IMPORTANT] WHERE player_name LIKE '%' + @query + '%'`);

    res.status(200).json({ query: player, matches: result.recordset });
});

app.post("/getInfo", authenticate, async (req, res) => {
    const userData = req.user;

    res.status(200).json(userData);
});

app.post("/getCourses", authenticate, async (req, res) => {
    const result = await pool.request()
        .query("SELECT * FROM Courses");

    const courses = result.recordset;

    res.status(200).json(courses);
});

app.post("/createSection", authenticate, async (req, res) => {
    let { subject, number, days, startDate, endDate, startTime, endTime, term } = req.body;
    startTime = new Date("1970-01-01T" + startTime);
    endTime = new Date("1970-01-01T" + endTime);
    console.log(req.user);
    try {
    await pool.request()
        .input("subject", sql.VarChar(3), subject)
        .input("number", sql.Int, number)
        .input("professor", sql.Int, req.user.Id)
        .input("term", sql.VarChar(10), term)
        .input("startDate", sql.Date, startDate)
        .input("endDate", sql.Date, endDate)
        .input("startTime", sql.Time(0), startTime)
        .input("endTime", sql.Time(0), endTime)
        .input("days", sql.Int, days)
        .query(`
            INSERT INTO Sections (
                Subject,
                Number,
                ProfessorID,
                Term,
                Days,
                StartDate,
                EndDate,
                StartTime,
                EndTime
            ) VALUES (
                @subject,
                @number,
                @professor,
                @term,
                @days,
                @startDate,
                @endDate,
                @startTime,
                @endTime
            )
        `);
    } catch (err) {
        if (err.message.includes("Violation of UNIQUE KEY constraint")) {
            res.status(500).send({ success: false, message: `Username "${username}" is already taken.` });
        }
        res.status(500).send({ success: false, message: err.message });
    }
    res.status(200).send({ success: true, message: "Section created successfully."});
})

app.post("/setInfo", authenticate, async (req, res) => {
    const { firstName, lastName, dob } = req.body;

    try {
        await pool.request()
            .input("username", sql.VarChar, req.user.Username)
            .input("firstName", sql.NVarChar(50), firstName)
            .input("lastName", sql.NVarChar(50), lastName)
            .input("dob", sql.Date, dob)
            .query(`
                UPDATE Users
                SET FirstName = @firstName,
                    LastName = @lastName,
                    DOB = @dob
                WHERE Username = @username
            `);
    } catch (error) {
        console.log(error);
        res.status(500).json({ message: "Update request failed" })
    }

    res.status(200).json({ message: "Information updated successfully" });
});

app.post("/delete", authenticate, async (req, res) => {
    let { username, actor } = req.body;

    if (username === true) {
        username = req.user.Username;
    }
    if (actor === true) {
        actor = req.user.Username;
    }
        
    console.log(`Deleting user ${username}`);

    await pool.request()
        .input("username", sql.VarChar, username)
        .input("actor", sql.VarChar, actor)
        .query("UPDATE Users SET IsDeleted = 1, DeletedAt = SYSUTCDATETIME(), DeletedBy = @actor WHERE Username = @username");

    console.log(`User ${username} deleted`);
    res.status(200).json({ message: `User "${username}" deleted.` });
});

async function authenticate(req, res, next) {
    try {
        const authenticationHeader = req.headers["authorization"];
        const token = authenticationHeader.split(" ")[1];

        console.log(JSON.stringify(authenticationHeader));
        const decoded = jwt.verify(token, JWT_SECRET);

        req.user = decoded;

        console.log(req.user);

        console.log(decoded.Username + " authenticated");
    } catch (error) {
        console.log("Authentication header missing");
        res.status(401).json({ message: "You are not logged in", error: error, logout: true });
        return;
    }
    next();
}

app.listen(serverPort, "0.0.0.0", () => console.log(`Running ${dev ? "dev " : ""}server on port ${serverPort}`));
