// Matrix Bot by Hammer1279
// Matrix: @hammer1279:ht-dev.de | #general:ht-dev.de
// https://matrix.to/#/@hammer1279:ht-dev.de
// https://matrix.to/#/#general:ht-dev.de

import { initAsync, Tracing, OlmMachine, UserId, LoggerLevel, DeviceId } from "@matrix-org/matrix-sdk-crypto-wasm";
import { AuthType, ClientEvent, createClient, GuestAccess, KnownMembership, RoomEvent, RoomMemberEvent } from "matrix-js-sdk";
import { existsSync } from "node:fs";
import { writeFile, readFile, appendFile } from "node:fs/promises";
import { inspect } from "node:util";

import config from "./config.json" with {
    type: "json"
}

const { homeserver, deviceId, userId, password, registerToken, leaveOnStart } = config;

let matrixClient;
let roomList = new Set();

async function loadCrypto(userId, deviceId) {
    // Do this before any other calls to the library
    await initAsync();

    // Optional: enable tracing in the rust-sdk
    new Tracing(LoggerLevel.Trace).turnOn();

    // Create a new OlmMachine
    //
    // The following will use an in-memory store. It is recommended to use
    // indexedDB where that is available.
    // See https://matrix-org.github.io/matrix-rust-sdk-crypto-wasm/classes/OlmMachine.html#initialize
    // const olmMachine = await OlmMachine.initialize(new UserId(userId), new DeviceId(deviceId), "MyStore", "MySuperSecretKey");
    const olmMachine = await OlmMachine.initialize(new UserId(userId), new DeviceId(deviceId));

    return olmMachine;
}

await loadCrypto(userId, deviceId);

if (!existsSync("./token.txt")) {
    matrixClient = createClient({ baseUrl: homeserver, userId: userId, deviceId: deviceId, timelineSupport: true });
    matrixClient.login(AuthType.Password, {"user": userId, "password": password}).then(async (response) => {
        await writeFile("./token.txt", response.access_token);
    });
} else {
    // matrixClient.loginWithToken((await readFile("./token.txt")).toString());
    matrixClient = createClient({ baseUrl: homeserver, userId: userId, accessToken: (await readFile("./token.txt")).toString(), deviceId: deviceId, timelineSupport: true });
}

// i cannot get this to work with a indexed db so this is false
await matrixClient.initRustCrypto({useIndexedDB: false});

appendFile("./client.log", `${new Date().toISOString()} | Started!\n`);

// attempt at automating registers, this might get used in a service for only allowing registers of certain users via prior oauth2 auth

// if (await matrixClient.isUsernameAvailable("demo123")) {
//     matrixClient.register("demo123", "test123", undefined, {
//         type: AuthType.Dummy
//     }, undefined, undefined, true).then(() => console.log("gud"), reject => {
//         const session = reject.data.session;
//         matrixClient.register("demo123", "test123", session, {
//             type: AuthType.RegistrationToken,
//             token: registerToken,
//             session: session,
//         }, undefined, undefined, true)
//     });
// } else {
//     console.log("Username taken");
// }

matrixClient.on(RoomEvent.MyMembership, function (room, membership, prevMembership) {
    if (membership === KnownMembership.Invite) {
        matrixClient.joinRoom(room.roomId).then(function () {
            console.log("Auto-joined %s", room.roomId);
            roomList.add(room.roomId);
            appendFile("./client.log", `${new Date().toISOString()} | Joined ${room.roomId}\n`);
        });
    }
});

// // Listen for low-level MatrixEvents
matrixClient.on(ClientEvent.Event, function (event) {
    console.log(event.getType());
});

matrixClient.on(RoomMemberEvent.Membership, function (event, member) {
    // appendFile("./client.log", `${new Date().toISOString()} | Membership Event: ${inspect(event)}\n`);
    // appendFile("./client.log", `${new Date().toISOString()} | Membership Member: ${inspect(member)}\n`);
    if (event.event.sender == userId && event.event.content.membership == KnownMembership.Join) {
        roomList.add(event.event.room_id);
        appendFile("./client.log", `${new Date().toISOString()} | I'm in?: ${event.event.room_id}\n`);
    }
    console.log(inspect(event), inspect(member))
})

// Listen for typing changes
matrixClient.on(RoomMemberEvent.Typing, function (event, member) {
    if (member.typing) {
        console.log(member.name + " is typing...");
    } else {
        console.log(member.name + " stopped typing.");
    }
});

matrixClient.on(RoomEvent.Timeline, async function (event, room, toStartOfTimeline) {
    if (toStartOfTimeline) {
        return; // don't print paginated results
    }
    // if (event.getType() !== "m.room.message") {
    //     return; // only print messages
    // }
    // console.log(
    //     // the room name will update with m.room.name events automatically
    //     "(%s) %s :: %s",
    //     room.name,
    //     event.getSender(),
    //     event.getContent().body,
    // );
    // matrixClient.sendMessage(event.getRoomId(), "Hello")
    let body = '';
	try {
		if (event.getType() === 'm.room.encrypted') {
			const clearEvent = await matrixClient.getCrypto().decryptEvent(event);
			({ body } = clearEvent.clearEvent.content);
		} else {
			({ body } = event.getContent());
            appendFile("./client.log", `${new Date().toISOString()} | Message Event: ${event.getType()}\n`);
		}
		if (body) {
            // do something
            if (event.getSender() != userId) {
                if (new Date().getTime() - 10000 > event.getDate()) {
                    console.log("Ignoring old message:", body);
                    appendFile("./client.log", `${new Date().toISOString()} | Ignoring old message: ${body}\n`);
                    return;
                }
                console.log("MESSAGE:", body);
                if (body == "!help leave") {
                    leaveAllRooms();
                    appendFile("./client.log", `${new Date().toISOString()} | ${event.getSender()} told me to leave all rooms\n`);
                } else {
                    appendFile("./client.log", `${new Date().toISOString()} | Received Message: ${body}\n`);
                    matrixClient.sendTextMessage(event.getRoomId(), "Hewwo :3")
                }
            }
		}
	} catch (error) {
		console.error('#### ', error);
	}
});

matrixClient.getRooms().forEach(room => {
    appendFile("./client.log", `${new Date().toISOString()} | I'm in: ${inspect(room)}\n`);
})

if (leaveOnStart) {
    setTimeout(() => {
        leaveAllRooms();
    }, 1000);
}

function leaveRoom(roomId) {
    matrixClient.leave(roomId).then(val => {
        appendFile("./client.log", `${new Date().toISOString()} | Left ${roomId}\n`);
    }, () => {}) // do not give a shit if it failed
}

function leaveAllRooms() {
    roomList.forEach(leaveRoom);
}

matrixClient.startClient();