const express = require("express");
const sqlite = require("better-sqlite3");
const bcrypt = require("bcrypt");
const cors = require("cors");
const jwt = require("jsonwebtoken")
const dotenv = require("dotenv");
const Database = require("better-sqlite3");

const serverPort = 3001;

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

const app = express();
app.use(express.json());
app.use((req, res, next) =>{
    if (req.path.endsWith(".html")) {
        res.redirect(req.path.replace(".html", ""));
        return;
    }
    next();
})
app.use(express.static("public"))
app.use(cors({
    origin: [
        "https://project.rochesterx.dev",
        "https://localhost:3001"
    ]
}));

const db = new Database("project.db");
db.pragma("journal_mode = WAL")

dotenv.config();

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

app.post("/register", async (req, res) => {
    const { username, password, role } = req.body;
    try {
        const hash = await bcrypt.hash(password, 10);

        const inputs = {
            username: username,
            hash: hash,
            role: role
        }
        const query = db.prepare("INSERT INTO Users (Username, PasswordHash, Role, CreatedAt) VALUES (@username, @hash, @role, datetime('now'))");
        query.run(inputs);

        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 query = db.prepare("SELECT * FROM Users WHERE Username = @username");
        const results = query.all({ username: username });
        
        if (results.length == 0) return res.status(400).send({ message: "User not found" });

        const hash = results[0].PasswordHash;
        const isDeleted = results[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(results[0], JWT_SECRET, { expiresIn: "1h" });
            res.send({
                success: true,
                message: "Login successful",
                token
            });
            const update = db.prepare("UPDATE Users SET LastLogin = datetime('now') WHERE Username = @username");
            update.run({ 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("/getWatchlist", authenticate, async (req, res) => {
    const { id } = req.body;

    const query = db.prepare(`SELECT Player.PlayerName, Player.Team, Player.Position, Player.PlayerID FROM Watch JOIN Player ON Watch.PlayerID = Player.PlayerID WHERE UserID = @userID ORDER BY Player.PlayerName`);
    const watchlist = query.all({
        userID: req.user.Id,
        id: id
    });


    res.status(200).json({ watchlist: watchlist });
});

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

    const query = db.prepare(`SELECT PlayerID FROM Watch WHERE UserID = @userID`);
    const watchlist = query.all({ userID: req.user.Id, id: id});

    const isWatched = watchlist.some(row => row.PlayerID === parseInt(id));

    res.status(200).json({ isWatched: isWatched });
});

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

    const query = db.prepare(`SELECT PlayerID FROM Watch WHERE UserID = @userID`);
    const watchlist = query.all({ userID: req.user.Id, id: id });

    const isWatched = watchlist.some(row => row.PlayerID === parseInt(id));

    if (isWatched) {
        const query = db.prepare(`DELETE FROM Watch WHERE UserID = @userID AND PlayerID = @id`);
        query.run({ userID: req.user.Id, id: id });

        res.status(200).json({ message: "No longer watching player" });
        return;
    }

    // Otherwise, watch the player
    const watchQuery = db.prepare(`INSERT INTO Watch (UserID, PlayerID) VALUES (@userID, @id)`);
    watchQuery.run({ userID: req.user.Id, id: id });

    res.status(200).json({ message: "Watching Player" });
});

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

    const query = db.prepare(`SELECT p.PlayerID, p.PlayerName, c.TotalValue, p.Team, p.Position FROM Player AS p JOIN Contract AS c ON p.PlayerID = c.PlayerID WHERE p.PlayerName LIKE '%' || @query || '%' AND p.Position IN (@one, @two, @three, @four) ORDER BY p.PlayerName;`);
    const matches = query.all({
        query: player,
        one: positions[0],
        two: positions[1],
        three: positions[2],
        four: positions[3]
    });

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

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

    const query = db.prepare(`
            SELECT p.PlayerID, p.PlayerName, p.[Position], p.Team, TotalValue, TrueAvgPerYear, Years
            FROM Player AS p JOIN Contract AS c ON p.PlayerID = c.PlayerID
            ORDER BY TotalValue DESC
            LIMIT @amount;
        `);
    const matches = query.all({ amount: amount });

    res.status(200).json({ matches: matches });
});

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

    const query = db.prepare(`
SELECT Player.PlayerID, Player.[Position], Player.PlayerName, Player.Team,
SUM(total_yards) AS TotalYards,
SUM(passing_yards) AS PassingYards,
SUM(rushing_yards) AS RushingYards,
SUM(receiving_yards) AS RecievingYards,
CASE WHEN Player.Position = 'QB' THEN (SUM(pass_touchdown)) ELSE 0 END + SUM(receiving_touchdown) + SUM(rush_touchdown) AS AmendedTotalTDs,
CASE WHEN Player.Position = 'QB' THEN (SUM(pass_touchdown)) ELSE 0 END AS PassTDs,
SUM(receiving_touchdown) AS ReceivingTDs,
SUM(rush_touchdown) AS RushTDs,
SUM(offense_snaps) * 1.0 / SUM(team_offense_snaps) AS SnapPercentage,
SUM(interception) + sum(fumble_lost) AS Turnovers,
SUM(tackled_for_loss) AS TackledForLoss,
    CASE 
        WHEN Player.Position = 'QB'
        THEN SUM(qb_dropback) - SUM(pass_attempts) - SUM(qb_scramble)
        ELSE 0
    END AS Sacks,
SUM(safety) AS Safties,

SUM(total_yards)
+ ((CASE WHEN Player.Position = 'QB' THEN (SUM(pass_touchdown)) ELSE 0 END + SUM(receiving_touchdown) + SUM(rush_touchdown)) * 50)
+ (SUM(offense_snaps) * 100.0 / SUM(team_offense_snaps))
- ((SUM(interception) + sum(fumble_lost)) * 75)
- (
    SUM(tackled_for_loss))

- (SUM(safety) * 100.0)
AS OffenseScore,

AvgPerYear,
(SUM(total_yards)
+ ((CASE WHEN Player.Position = 'QB' THEN (SUM(pass_touchdown)) ELSE 0 END + SUM(receiving_touchdown) + SUM(rush_touchdown)) * 50)
+ (SUM(offense_snaps) * 100.0 / SUM(team_offense_snaps))
- ((SUM(interception) + sum(fumble_lost)) * 75)
- (
SUM(tackled_for_loss)
)
- (SUM(safety) * 100.0)) / AvgPerYear AS PaydirtScore

FROM Player JOIN DatasetPlayerStats ON Player.PlayerID = DatasetPlayerStats.PlayerID JOIN Contract ON Player.PlayerID = Contract.PlayerID
WHERE season = 2024 AND SeasonType = 'REG'
GROUP BY Player.PlayerID, Player.PlayerName, Player.Team, Player.[Position], Contract.AvgPerYear
ORDER BY OffenseScore DESC
LIMIT @amount;
        `);
    const matches = query.all({ amount: amount });

    res.status(200).json({ matches: matches });
});


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

    const query = db.prepare(`
SELECT player.playerid, player.playername, season, seasontype, week, pass_attempts, complete_pass, total_yards, total_tds, interception, receptions, receiving_yards, receiving_touchdown, rush_attempts, rushing_yards, rush_touchdown, fumble
fROM Player JOIN DatasetPlayerStats ON Player.PlayerID = DatasetPlayerStats.PlayerID
WHERE Player.PlayerID = @playerID
ORDER BY Season, week;
        `);
    const matches = query.all({ playerID: playerID })

    res.status(200).json({ matches: matches });
});

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

    const query = db.prepare(`
SELECT Player.PlayerID, Player.[Position], Player.PlayerName, Player.Team,
c.TotalValue, c.TrueAvgPerYear, c.Years, c.StartYear, c.EndYear, Player.Height, Player.Weight,
SUM(total_yards) AS TotalYards,
SUM(passing_yards) AS PassingYards,
SUM(rushing_yards) AS RushingYards,
SUM(receiving_yards) AS RecievingYards,
CASE WHEN Player.Position = 'QB' THEN (SUM(pass_touchdown)) ELSE 0 END + SUM(receiving_touchdown) + SUM(rush_touchdown) AS AmendedTotalTDs,
CASE WHEN Player.Position = 'QB' THEN (SUM(pass_touchdown)) ELSE 0 END AS PassTDs,
SUM(receiving_touchdown) AS ReceivingTDs,
SUM(rush_touchdown) AS RushTDs,
SUM(offense_snaps) * 1.0 / SUM(team_offense_snaps) AS SnapPercentage,
SUM(interception) + sum(fumble_lost) AS Turnovers,
SUM(tackled_for_loss) AS TackledForLoss,
    CASE 
        WHEN Player.Position = 'QB'
        THEN SUM(qb_dropback) - SUM(pass_attempts) - SUM(qb_scramble)
        ELSE 0
    END AS Sacks,
SUM(safety) AS Safties,

SUM(total_yards)
+ ((CASE WHEN Player.Position = 'QB' THEN (SUM(pass_touchdown)) ELSE 0 END + SUM(receiving_touchdown) + SUM(rush_touchdown)) * 50)
+ (SUM(offense_snaps) * 100.0 / SUM(team_offense_snaps))
- ((SUM(interception) + sum(fumble_lost)) * 75)
- (
    SUM(tackled_for_loss)
    ) 

- (SUM(safety) * 100.0)
AS OffenseScore,

AvgPerYear,
(SUM(total_yards)
+ ((CASE WHEN Player.Position = 'QB' THEN (SUM(pass_touchdown)) ELSE 0 END + SUM(receiving_touchdown) + SUM(rush_touchdown)) * 50)
+ (SUM(offense_snaps) * 100.0 / SUM(team_offense_snaps))
- ((SUM(interception) + sum(fumble_lost)) * 75)
- (
    SUM(tackled_for_loss)
)
- (SUM(safety) * 100.0)) / AvgPerYear * 1000000 AS PaydirtScore


FROM Player JOIN DatasetPlayerStats ON Player.PlayerID = DatasetPlayerStats.PlayerID JOIN Contract AS c ON Player.PlayerID = c.PlayerID
WHERE season = 2024 AND SeasonType = 'REG' AND Player.PlayerID = @playerID
GROUP BY c.TotalValue, c.TrueAvgPerYear, c.Years, c.StartYear, Player.Height, Player.Weight, c.EndYear, Player.PlayerID, Player.PlayerName, Player.Team, Player.[Position], c.AvgPerYear
ORDER BY PaydirtScore DESC;
        `);
    const matches = query.all({ playerID: playerID })

    res.status(200).json({ match: matches[0] });
});



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

    const query = db.prepare(`
SELECT Player.PlayerID, Player.[Position], Player.PlayerName, Player.Team,
SUM(total_yards) AS TotalYards,
SUM(passing_yards) AS PassingYards,
SUM(rushing_yards) AS RushingYards,
SUM(receiving_yards) AS RecievingYards,
CASE WHEN Player.Position = 'QB' THEN (SUM(pass_touchdown)) ELSE 0 END + SUM(receiving_touchdown) + SUM(rush_touchdown) AS AmendedTotalTDs,
CASE WHEN Player.Position = 'QB' THEN (SUM(pass_touchdown)) ELSE 0 END AS PassTDs,
SUM(receiving_touchdown) AS ReceivingTDs,
SUM(rush_touchdown) AS RushTDs,
SUM(offense_snaps) * 1.0 / SUM(team_offense_snaps) AS SnapPercentage,
SUM(interception) + sum(fumble_lost) AS Turnovers,
SUM(tackled_for_loss) AS TackledForLoss,
    CASE 
        WHEN Player.Position = 'QB'
        THEN SUM(qb_dropback) - SUM(pass_attempts) - SUM(qb_scramble)
        ELSE 0
    END AS Sacks,
SUM(safety) AS Safties,

SUM(total_yards)
+ ((CASE WHEN Player.Position = 'QB' THEN (SUM(pass_touchdown)) ELSE 0 END + SUM(receiving_touchdown) + SUM(rush_touchdown)) * 50)
+ (SUM(offense_snaps) * 100.0 / SUM(team_offense_snaps))
- ((SUM(interception) + sum(fumble_lost)) * 75)
- (
    SUM(tackled_for_loss))

- (SUM(safety) * 100.0)
AS OffenseScore,

AvgPerYear,
(SUM(total_yards)
+ ((CASE WHEN Player.Position = 'QB' THEN (SUM(pass_touchdown)) ELSE 0 END + SUM(receiving_touchdown) + SUM(rush_touchdown)) * 50)
+ (SUM(offense_snaps) * 100.0 / SUM(team_offense_snaps))
- ((SUM(interception) + sum(fumble_lost)) * 75)
- (
SUM(tackled_for_loss)
)

- (SUM(safety) * 100.0)) / AvgPerYear * 1000000 AS PaydirtScore


FROM Player JOIN DatasetPlayerStats ON Player.PlayerID = DatasetPlayerStats.PlayerID JOIN Contract ON Player.PlayerID = Contract.PlayerID
WHERE season = 2024 AND SeasonType = 'REG'
GROUP BY Player.PlayerID, Player.PlayerName, Player.Team, Player.[Position], Contract.AvgPerYear
ORDER BY PaydirtScore DESC
LIMIT @amount;
        `);
    const matches = query.all({ amount: amount });

    res.status(200).json({ matches: matches });
});

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

    const query = db.prepare(`SELECT p.PlayerName, p.PlayerID, c.TotalValue, c.TrueAvgPerYear, c.Years, c.StartYear, c.EndYear, p.Team, p.Position FROM Player AS p JOIN Contract AS c ON p.PlayerID = c.PlayerID WHERE p.PlayerID = @query`);
    const matches = query.run({ query: id });

    if (matches.length !== 1) {
        res.status(400).json({ success: false })
        return;
    }
    res.status(200).json({ success: true, match: matches[0] });
});

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

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

app.post("/getCourses", authenticate, async (req, res) => {
    const query = db.prepare("SELECT * FROM Courses");
    const courses = query.all();

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

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

    try {
        const query = db.prepare(`
                UPDATE Users
                SET FirstName = @firstName,
                    LastName = @lastName,
                    DOB = @dob
                WHERE Username = @username
            `);
        query.run({
            username: req.user.Username,
            firstName: firstName,
            lastName: lastName,
            dob: dob || null
        });
    } catch (error) {
        console.log(error);
        if (error.message.includes("failed for parameter 'dob'.")) {
            res.status(500).json({ message: "Must input date of birth" })
            return;
        }
        res.status(500).json({ message: "Update request failed" })
        return;
    }

    var updatedUser = req.user;
    updatedUser.FirstName = firstName;
    updatedUser.LastName = lastName;
    updatedUser.DOB = dob;

    const token = jwt.sign(updatedUser, JWT_SECRET);
    console.log("Issued token: " + JSON.stringify(token))
    res.status(200).send({
        success: true,
        message: "Information updated successfully",
        token
    });
});

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}`);

    const query = ("UPDATE Users SET IsDeleted = 1, DeletedAt = datetime('now'), DeletedBy = @actor WHERE Username = @username");
    query.run({
        username: username,
        actor: actor
    });

    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"];
        console.log("authenticationheader: " + authenticationHeader);
        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");
        console.log(error);
        res.status(401).json({ message: "You are not logged in", error: error, logout: true });
        return;
    }
    next();
}

app.get("/player/:id", (req, res) => {
    res.sendFile(__dirname + "/public/player.html");
})

app.get("/search", (req, res) => {
    res.sendFile(__dirname + "/public/search.html");
})

app.get("/home", (req, res) => {
    res.sendFile(__dirname + "/public/home.html");
})

app.get("/info", (req, res) => {
    res.sendFile(__dirname + "/public/info.html");
})

app.get("/register", (req, res) => {
    res.sendFile(__dirname + "/public/register.html");
})

app.get("/login", (req, res) => {
    res.sendFile(__dirname + "/public/login.html");
})

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

