import { NodePgDatabase } from "drizzle-orm/node-postgres";
import { Express } from "express";
import {
    follows,
    inviteCodes,
    journalEntries,
    profiles,
    updates,
    users
} from "../db/schema.js";
import { and, count, desc, eq, sql } from "drizzle-orm";
import dayjs from "dayjs";
import {
    confirm,
    getMoods,
    journalMoodString,
    render,
    render404,
    UserStatus
} from "./util.js";

export default async function (app: Express, db: NodePgDatabase) {
    const { moods, moodsSorted } = await getMoods();

    // DASHBOARD
    app.get("/dashboard", async (req, res) => {
        if (!req.session["loggedIn"]) {
            res.redirect("/login");
            return;
        }
        const user = (
            await db
                .select({
                    name: users.name,
                    bio: profiles.bio,
                    website: profiles.website //! validate this
                })
                .from(users)
                .where(eq(users.name, req.session["user"]))
                .leftJoin(profiles, eq(users.id, profiles.user))
        )[0];

        const now = dayjs();
        const moodHistory = (
            await db
                .select({ mood: updates.mood, date: updates.date })
                .from(updates)
                .where(eq(updates.user, req.session["uid"]))
                .orderBy(desc(updates.date))
                .limit(10)
        ).map((e) => {
            return { mood: moods[e.mood], date: now.to(dayjs(e.date)) };
        });

        const recentUpdates = (
            await db
                .select({
                    user: users.name,
                    mood: updates.mood,
                    desc: updates.description,
                    date: updates.date
                })
                .from(updates)
                .innerJoin(
                    follows,
                    and(
                        eq(follows.userId, updates.user),
                        eq(follows.followerId, req.session["uid"])
                    )
                )
                .leftJoin(users, eq(updates.user, users.id))
                .orderBy(desc(updates.date))
                .limit(25)
        ).map((e) => {
            return {
                user: e.user,
                mood: moods[e.mood],
                desc: e.desc,
                date: e.date,
                relativeDate: now.to(dayjs(e.date))
            };
        });

        // user invite codes
        const codes = (
            await db
                .select({
                    token: inviteCodes.token,
                    expires: inviteCodes.expires
                })
                .from(inviteCodes)
                .where(eq(inviteCodes.user, req.session["uid"]))
        ).map((e) => {
            return {
                token: e.token,
                expires: e.expires,
                expiresString: now.to(dayjs(e.expires || 0))
            };
        });
        const { codesUsed } = (
            await db
                .select({ codesUsed: count() })
                .from(inviteCodes)
                .where(
                    and(
                        eq(inviteCodes.user, req.session["uid"]),
                        eq(
                            sql`extract(month from granted)`,
                            sql`extract(month from current_date)`
                        )
                    )
                )
        )[0];

        render(db, "dashboard", "dashboard", res, req, {
            user,
            moods,
            moodsSorted,
            moodHistory,
            recentUpdates,
            codes,
            codesUsed,
            feed: []
        });
    });
    app.post("/update/mood", async (req, res) => {
        if (!req.session["loggedIn"]) {
            res.redirect("/login");
            return;
        }
        const moodIndex = moods.indexOf(req.body.mood.trim());
        if (moodIndex === -1) {
            req.flash(
                "error",
                "That mood doesn't exist in the database, WTF are you trying to do??"
            );
            res.redirect("/dashboard");
            return;
        }
        if (req.body.desc.length > 512) {
            req.flash(
                "error",
                "Mood description can't be longer than 512 characters"
            );
            res.redirect("/dashboard");
        }

        await db
            .insert(updates)
            // @ts-expect-error
            .values({
                user: req.session["uid"],
                mood: moodIndex,
                description: req.body.desc,
                date: new Date(Date.now())
            });
        req.flash("success", "Mood updated!");
        res.redirect("/dashboard");
    });

    // JOURNAL
    app.get("/journal", async (req, res) => {
        render(db, "journal", "your journal", res, req);
    });
    app.get("/journal/:id", async (req, res) => {
        const id = parseInt(req.params.id);
        if (isNaN(id)) {
            req.flash("error", "Invalid ID");
            render404(db, res, req);
            return;
        }

        const entry: {
            id: number;
            uid: number;
            uname: string;
            title: string;
            content: string;
            moodChange: number;
            moodString?: string;
            date: Date;
            visibility: number;
        } = (
            await db
                .select({
                    id: journalEntries.id,
                    uid: users.id,
                    uname: users.name,
                    title: journalEntries.title,
                    content: journalEntries.entry,
                    moodChange: journalEntries.moodChange,
                    date: journalEntries.date,
                    visibility: journalEntries.visibility
                })
                .from(journalEntries)
                .where(eq(journalEntries.id, id))
                .leftJoin(users, eq(journalEntries.user, users.id))
        )[0];

        //? put into util function?
        //? also GOD
        entry.moodString = journalMoodString(entry.moodChange);

        const isMod = req.session["status"] & UserStatus.MODERATOR;
        if (
            !entry ||
            (entry.visibility === 0 &&
                entry.uname !== req.session["user"] && !isMod)
        ) {
            render404(db, res, req);
            return;
        }

        // maybe turn ([x] !== req.session["user"] && !(req.session["status"] & UserStatus.MODERATOR)) into util function
        entry.content =
            entry.visibility === 2 &&
            entry.uid !== req.session["uid"] &&
            !(req.session["status"] & UserStatus.MODERATOR)
                ? "This journal entry's contents have been made private."
                : entry.content;

        const entryTimestamp = dayjs(entry.date).fromNow();
        const isSelf = entry.uid === req.session["uid"];
        render(db, "journal_view", entry.title, res, req, {
            entry,
            entryTimestamp,
            isSelf,
            isMod
        });
    });
    app.post("/journal/:id/edit", async (req, res) => {
        const id = parseInt(req.params.id);
        if (isNaN(id)) {
            req.flash("error", "Invalid ID");
            render404(db, res, req);
            return;
        }

        const entry = (
            await db
                .select({
                    uid: journalEntries.user
                })
                .from(journalEntries)
                .where(eq(journalEntries.id, id))
                .limit(1)
        )[0];

        const isMod = req.session["status"] & UserStatus.MODERATOR;
        if (
            !entry ||
            (entry?.uid !== req.session["uid"] &&
                !isMod)
        ) {
            render404(db, res, req);
            return;
        }
        if (isMod && entry.uid !== req.session["uid"] && req.body.action !== "delete") {
            req.flash("error", "Moderators can only delete other users' posts.");
            res.redirect(`/journal/${req.params.id}`);
            return;
        }

        if (req.body.action === "edit") {
            // TODO!!
        } else if (req.body.action === "delete") {
            if (!req.body.confirm) {
                confirm(db, res, req);
                return;
            }
            await db.delete(journalEntries).where(eq(journalEntries.id, id));
            req.flash("success", "Journal entry deleted ;w;");
            res.redirect("/");
        } else {
            render404(db, res, req);
        }
    });
    app.post("/update/journal", async (req, res) => {
        if (!req.session["loggedIn"]) {
            res.redirect("/login");
            return;
        }
        if (req.body.title.length > 64) {
            req.flash("error", "Title too long!");
            res.redirect("/journal");
            return;
        }
        if (req.body.description.length > 4096) {
            req.flash("error", "Entry too long!");
            res.redirect("/journal");
            return;
        }

        const moodChange = parseInt(req.body.moodDelta);
        const visibility = parseInt(req.body.visibility);
        if (isNaN(moodChange) || isNaN(visibility)) {
            req.flash("error", "One of the values was improperly specified.");
            res.redirect("/journal");
            return;
        }

        let id: number;
        try {
            const entry = await db
                .insert(journalEntries)
                // @ts-expect-error
                .values({
                    user: req.session["uid"],
                    moodChange,
                    visibility,
                    title: req.body.title,
                    entry: req.body.description
                        .replaceAll("&", "&")
                        .replaceAll("<", "&lt;")
                        .replaceAll(">", "&gt;")
                        .replaceAll("\n", "<br>"),
                    date: new Date(Date.now())
                })
                .returning({ id: journalEntries.id });
            id = entry[0].id;
        } catch (err) {
            req.flash(
                "error",
                "Failed to create your entry. Try again later or send these logs to roxwize so she can know what's up:<br><br>" +
                    err
            );
            res.redirect("/journal");
            return;
        }
        req.flash(
            "success",
            `Your journal entry is now available as <a href="/journal/${id}">#${id}</a>!`
        );
        res.redirect("/journal");
    });
}