'use server'

import { APIError, attemptAPIAction } from "@/util/api/error";
import { sequelize, Bucket, Auth, Post, PostTag, Tag, User, dbSync } from "@/model/Models";
import { cookies } from "next/headers";
import { Attachment } from "@/model/Attachment";
import { UUID, randomUUID } from "crypto";
import { mkdir, mkdirSync, writeFile } from "fs";
import { where } from "@sequelize/core";

async function writeFilesToFS(uuid: UUID, files: any[]): Promise<{ success: boolean, filePaths: string[] }> {

    type FileArrayEntryWithPromise = {
        name: string;
        content: Promise<ReadableStreamReadResult<Uint8Array>> | ReadableStreamReadResult<Uint8Array>
    }
    type FileArrayEntry = {
        name: string;
        content: ReadableStreamReadResult<Uint8Array>
    };

    // Resolve promises
    const fileArray: FileArrayEntryWithPromise[] = await Promise.all(files.map((file: File) => {
        return { 'name': (() => file.name)(), 'content': file.stream().getReader().read() };
    }));



    let finalFileArray: FileArrayEntry[] = await (
        async () => {
            for (let file in fileArray) {
                fileArray[file].content = await fileArray[file].content
            }
            return [...fileArray as FileArrayEntry[]]
        })();

    // Make Bucket Directory
    mkdirSync(`./bucket/${uuid}/`, { recursive: true })

    // Write files to filesystem
    for (let file in finalFileArray) {
        writeFile(`./bucket/${uuid}/${finalFileArray[file].name}`
            , Buffer.from(finalFileArray[file].content.value as Uint8Array)
            , (e) => { console.log(e) });
    }
    return {
        success: true,
        filePaths: finalFileArray.map((e) => e.name)
    }
}

async function addToNewBucketForPost(postid: number): Promise<Bucket> {
    Post.sync();
    const post = await Post.findOne({ where: { id: postid } });

    if (!post) throw new APIError({ status: 500, responseText: "invalid postid" });
    const bucket = await post.createBucket({ id: randomUUID() });
    return bucket;
}
async function addToExistingBucket(bucketid: number): Promise<Bucket> {
    const bucket = await Bucket.findOne({
        where: { id: bucketid }, include: { association: Bucket.associations.posts }
    });
    if (!bucket) throw new APIError({ status: 500, responseText: "invalid bucketid" });
    return bucket;
}


export async function tryCreateAttachment(request: Request) {

    // Make sure the DB is ready
    await dbSync;

    // Prepare data
    const formData = await request.formData();
    const requestData: string | Object | undefined = formData.get('data')?.valueOf();
    const files: FormDataEntryValue[] = formData.getAll('files')
    const authCkie = await cookies().get("auth");

    // Sanity check auth cookie
    if (!authCkie || !authCkie.value) throw new APIError({ status: 500, responseText: "missing auth cookie" });

    // Get JSON from the Cookie
    const cookieJSON = authCkie.value;
    const authObject = JSON.parse(cookieJSON);

    // Fetch User Auth from the database
    const auth = await Auth.findOne({
        include: [
            {
                model: User.withScope(['withPerms']),
                attributes: {
                    exclude: ['username', 'password', 'updatedAt', 'createdAt']
                }
            }
        ],
        where: { token: authObject.token }
    });


    // Sanity check the auth and associated user for authorization
    if (!auth
        || !auth.user) throw new APIError({ status: 401, responseText: "Authentication Error" });
    if (!auth.user.id) throw new APIError({ status: 401, responseText: "Missing user id" });
    if (!auth.user.perms
        || !auth.user.perms.isAdmin) throw new APIError({ status: 401, responseText: `Unauthorized` });
    // Handle incomplete data or other problems
    if (!files) throw new APIError({ status: 500, responseText: "Missing file" });
    if (!formData) throw new APIError({ status: 500, responseText: "Empty request body" });
    if (!requestData) throw new APIError({ status: 500, responseText: "Missing request data" });
    if (!(typeof requestData == "string")) throw new APIError({ status: 500, responseText: "Malformed request data" });


    // Parse JSON
    const data = JSON.parse(requestData);

    // Get or create bucket
    const bucket: Bucket = (data.postid && !data.bucketid)
        ? await addToNewBucketForPost(data.postid)
        : await addToExistingBucket(data.bucketid);

    // Write files to bucket and store as attachments in DB
    const writeResult = await writeFilesToFS(bucket.id, files);
    // Handle failure
    if (!writeResult.success) throw new APIError({ status: 500, responseText: "Error writing files to Bucket" });

    // Write attachments to db
    const attachments = ((writeResult).filePaths.map(async (fp) => {
        return (await Attachment.create(
            {
                bucket_id: bucket.id,
                filename: fp
            },
            { include: Attachment.associations.bucket }
        ));
    }))
    attachments;

    return new Response(
        JSON.stringify({
            bucket: await Bucket.findOne({
                where: { id: bucket.id },
                include: [Bucket.associations.posts, Bucket.associations.attachments]
            })
        }),
        { status: 200 }
    );
}

export async function tryFetchAttachments(request: Request) {

    await Post.sync();

    const foundPosts = await Post.findAll({
        include: [
            {
                association: Post.associations.user,
                attributes: { exclude: ['password', 'createdAt', 'updatedAt'] }
            }, {
                association: Post.associations.postTags
            }]
    });

    return new Response(JSON.stringify(foundPosts), { status: 200 });

}

export async function GET(request: Request) {
    return await attemptAPIAction(tryFetchAttachments, request);
}

export async function POST(request: Request) {
    return await attemptAPIAction(tryCreateAttachment, request);
}
