import sqlite3
import bcrypt
import time
from random import random, choice
from math import floor
from uuid import uuid4

connection = sqlite3.connect("./database.db")
cursor = connection.cursor()


def random_string():
    return str(
        floor(random() * 1e16)
    )  # 1e16 is wetenschappelijke notatie (1 * 10^16)


def repeat_action(action, name):
    def return_function():
        count = int(input(f"Hoeveel {name} wil je aanmaken? "))
        for i in range(count):
            action()
            print(f"{i+1}/{count} {name} toegevoegd!")
        print(f"{count} {name} toegevoegd!")

    return return_function


# dit is om dit script te organiseren
programma_acties = []


class programma_actie:
    def __init__(self, name, execute):
        self.name = name
        self.exec = execute


# initialiseer een nieuwe (lege) database
def init_db():
    commands_text = open("./init_db.sql").read()
    commands = commands_text.strip().split(
        "\n" * 2
    )  # python sqlite3 kan maar 1 "statement" per cursor.execute doen, en de statements zijn gescheden door witregels in ./init_db.sql
    for i, command in enumerate(commands):
        cursor.execute(command)
        print(f"SQL commando {i+1} van de {len(commands)} uitgevoerd...")
    print("Klaar!")


programma_acties.append(programma_actie("Maak tabellen aan", init_db))


def destroy_tables():
    for table_name in ["games", "users", "social"]:
        # format strings + sql is gevaarlijk vanwege sql injectie, maar omdat de invoer hier niet door de gebruiker aangepast kan worden is het semi-ok
        cursor.execute(f"drop table if exists {table_name}")
        print(f"Tabel \"{table_name}\" verwijderd!")


programma_acties.append(
    programma_actie("Verwijder alle tabellen", destroy_tables)
)


def user_amount():
    return len(cursor.execute("select user_id from users").fetchall())


# pak een willekeurige user_id uit de users tabel (gebruikt voor insert_random::games)
def random_player():
    return choice(cursor.execute("select user_id from users").fetchall())[0]


# class om alle functies die willekeurige data invoegen te organiseren (python heeft geen namespaces :( )
class insert_random:
    # omdat veel kolommen in onze database gebruikersinteracties opslaan laat ik ze hier leeg omdat ik geen tijd hem om gebruikersgedrag te simuleren.

    def __init__(self):
        pass

    def users(self):
        user_id = str(uuid4())
        username = random_string()
        email = f"{username}@example.com"
        password_hash = bcrypt.hashpw(
            random_string().encode("utf-8"), bcrypt.gensalt()
        )
        registered = int(time.time() * 1000)

        cursor.execute(
            "insert into users values (?, ?, ?, NULL, ?, ?, \"[]\", FALSE, \"user\", \"{}\", NULL, \"online\") ",
            (user_id, username, email, password_hash, registered)
        )

    def games(self):
        if user_amount() < 2:
            print("Er zijn niet genoeg gebruikers om spellen aan te maken!")
            return

        game_id = str(uuid4())
        timestamp = int(time.time() * 1000)

        cursor.execute(
            "insert into games values (?, NULL, NULL, ?, ?, NULL, ?, NULL, NULL, NULL, FALSE, \"in_progress\", \"default\") ",
            (game_id, random_player(), random_player(), timestamp)
        )

    def friends(self):
        users = list(
            rij[0]
            for rij in cursor.execute("select user_id from users").fetchall()
        )  # cursor.execute.fetchall() stuurt rijen altijd terug als tuples ookal vraag je 1 kolom op, dus dit zet het om naar een list van strings
        # maak een nieuwe rij aan voor elke gebruiker in tabel users als deze nog niet bestaat
        for user in users:
            if cursor.execute(
                "select user_id from social where user_id = ?", [user]
            ).fetchone():
                continue
            cursor.execute(
                "insert into social values (?, NULL, NULL, NULL)", [user]
            )


programma_acties.append(
    programma_actie(
        "Maak nepgebruikers aan",
        repeat_action(insert_random().users, "nepgebruikers")
    )
)
programma_acties.append(
    programma_actie(
        "Maak voorbeeld spellen aan",
        repeat_action(insert_random().games, "spellen")
    )
)
programma_acties.append(
    programma_actie(
        "Vul social tabel met gebruikers",
        insert_random().friends
    )
)


# Verifiëer een user_id of verkrijg een user_id uit een username
def resolve(resolveable):
    user_id = None
    user_id = user_id or cursor.execute(
        "select user_id from users where user_id = ?", [resolveable]
    ).fetchone()
    user_id = user_id or cursor.execute(
        "select user_id from users where username = ?", [resolveable]
    ).fetchone()
    # stuur de eerste kolom van de rij terug omdat het weer een tuple is >:(
    return user_id[0] if user_id else None


def edit_username():
    resolveable = input("Voer een gebruikers-id of gebruikersnaam in: ")
    user_id = resolve(resolveable)
    if not user_id:
        print(
            "Er is geen gebruiker met gebruikers-id of gebruikersnaam \"{resolveable}\""
        )
        return
    new_username = input("Voer een nieuwe gebruikersnaam in: ")
    cursor.execute(
        "update users set username = ? where user_id = ?",
        [new_username, user_id]
    )
    print("Nieuwe gebruikersnaam succesvol toegepast!")


programma_acties.append(
    programma_actie(
        "Pas de gebruikersnaam van een gebruiker aan", edit_username
    )
)


def delete_user():
    resolveable = input("Voer een gebruikers-id of gebruikersnaam in: ")
    user_id = resolve(resolveable)
    if not user_id:
        print(
            "Er is geen gebruiker met gebruikers-id of gebruikersnaam \"{resolveable}\""
        )
        return
    cursor.execute("delete from users where user_id = ?", [user_id])
    print("Gebruiker verwijderd!")


programma_acties.append(
    programma_actie("Verwijder een gebruiker", delete_user)
)


def mark_game_done():
    game_id = input("Voer een game-id in om als klaar te markeren: ")
    if not cursor.execute(
        "select game_id from games where game_id = ?", [game_id]
    ).fetchone():
        print("Kon geen spel vinden met die game_id!")
        return

    outcome = "w"
    starttime = cursor.execute(
        "select timestamp from games where game_id = ?", [game_id]
    ).fetchone()[0]
    duration = int(time.time() * 1000) - starttime
    cursor.execute(
        "update games set outcome = ?, duration = ?, status = \"finished\" where game_id = ?",
        [outcome, duration, game_id]
    )
    print("Spel gemarkeerd als afgelopen!")


programma_acties.append(
    programma_actie("Spel markeren als afgelopen", mark_game_done)
)


def delete_game():
    game_id = input("Voer een game-id in om te verwijderen: ")
    if not cursor.execute(
        "select game_id from games where game_id = ?", [game_id]
    ).fetchone():
        print("Kon geen spel vinden met die game_id!")
        return

    cursor.execute("delete from games where game_id = ?", [game_id])
    print("Spel verwijderd!")


programma_acties.append(programma_actie("Spel verwijderen", delete_game))


# maakt een vriendschap tussen 2 willekeurige user_id's
def make_friendship():
    # Dit is misschien de lelijkste functie die ik ooit heb geschreven, wees gewaarschuwd
    user_1 = random_player()
    user_2 = random_player()
    while user_1 == user_2:  # garandeer dat user_1 != user_2
        user_2 = random_player()

    # [v for v in ? if v] zorgt er voor dat lege list elementen worden verwijderd
    # .fetchone()[0] or "" is omdat deze kolommen ook NULL kunnen zijn, maar ik wil altijd een string
    # .split(",") is omdat het user_id's zijn die gescheden zijn door komma's
    user_1_friend_list = [
        v for v in (
            cursor.execute(
                "select friends from social where user_id = ?", [user_1]
            ).fetchone()[0] or ""
        ).split(",") if v
    ]
    user_2_friend_list = [
        v for v in (
            cursor.execute(
                "select friends from social where user_id = ?", [user_2]
            ).fetchone()[0] or ""
        ).split(",") if v
    ]

    user_1_friend_list.append(user_2)
    user_2_friend_list.append(user_1)

    cursor.execute(
        "update social set friends = ? where user_id = ?",
        [",".join(user_1_friend_list), user_1]
    )
    cursor.execute(
        "update social set friends = ? where user_id = ?",
        [",".join(user_2_friend_list), user_2]
    )

    print(f"Nieuwe vriendschap gemaakt tussen {user_1} en {user_2}")


programma_acties.append(programma_actie("Vriendschap maken", make_friendship))


def reset_friendlist():
    resolveable = input("Voer een gebruikers-id of gebruikersnaam in: ")
    user_id = resolve(resolveable)
    if not user_id:
        print(
            "Er is geen gebruiker met gebruikers-id of gebruikersnaam \"{resolveable}\""
        )
        return
    cursor.execute("delete from social where user_id = ?", [user_id])
    cursor.execute(
        "insert into social values (?, NULL, NULL, NULL)", [user_id]
    )
    print(f"Vriendenlijst van {user_id} geleegd!")


programma_acties.append(
    programma_actie("Vriendenlijst legen", reset_friendlist)
)

for table in ["users", "games", "social"]:
    programma_acties.append(
        programma_actie(
            f"Tabel '{table}' weergeven",
            lambda: print(cursor.execute(f"select * from {table}").fetchall())
        )
    )

programma_acties.append(
    programma_actie("Sla alle wijzigingen op", connection.commit)
)
programma_acties.append(programma_actie("Stop", exit))


def main():
    while True:
        # print witregels
        print("\n" * 2, end="")

        # print alle acties in programma_acties
        for i, actie in enumerate(programma_acties):
            print(f"#{i}: {actie.name}")

        # vraag om keuze
        keuze = int(input("Geef je keuze: "))
        print("\n", end="")

        # kijk of keuze bestaat
        if keuze > len(programma_acties) - 1:
            print("Die actie bestaat niet!")
            continue  # ga door naar de volgende iteratie van de while loop (voer keuze niet uit)

        # voer keuze uit
        programma_acties[keuze].exec()


if __name__ == "__main__":
    main()