module sample_data;

import ddbc;
import db;
import data_utils;
import slf4d;

import std.random;
import std.algorithm;
import std.array;
import std.datetime;

private const STUDENT_NAMES = [
    "Andrew",
    "Richard",
    "Klaus",
    "John",
    "Wilson",
    "Grace",
    "Sarah",
    "Rebecca",
    "Lily",
    "Thomas",
    "Michael",
    "Jennifer",
    "Robert",
    "Christopher",
    "Margaret",
    "Mordecai",
    "Rigby",
    "Walter",
    "Roy",
    "Cindy"
];

void insertSampleData() {
    Connection conn = getDb();
    conn.setAutoCommit(false);
    scope(exit) {
        conn.commit();
        conn.close();
    }

    deleteAllData(conn);

    addUser(conn, "sample-user-A", "test", false, false);
    addUser(conn, "sample-user-B", "test", true, false);
    addUser(conn, "sample-user-C", "test", false, false);
    addUser(conn, "sample-user-D", "test", false, false);

    ulong adminUserId = addUser(conn, "test", "test", false, true);
    ulong normalUserId = addUser(conn, "test2", "test", false, false);
    Random rand = Random(0);
    addClassroomComplianceSampleData(rand, adminUserId, conn);
    addClassroomComplianceSampleData(rand, normalUserId, conn);
    info("Inserted sample data.");
}

void addClassroomComplianceSampleData(ref Random rand, ulong adminUserId, Connection conn) {
    const SysTime now = Clock.currTime();
    const Date today = Date(now.year, now.month, now.day);

    for (ushort i = 1; i <= 6; i++) {
        ulong classId = addClass(conn, "2024-2025", i, adminUserId);
        bool classHasAssignedDesks = uniform01(rand) < 0.5;
        size_t count = uniform(10, STUDENT_NAMES.length, rand);
        auto studentsToAdd = randomSample(STUDENT_NAMES, count, rand);
        ushort deskNumber = 1;
        foreach (name; studentsToAdd) {
            bool removed = uniform01(rand) < 0.1;
            ushort assignedDeskNumber = 0;
            if (classHasAssignedDesks) {
                assignedDeskNumber = deskNumber++;
            }
            ulong studentId = addStudent(conn, name, classId, assignedDeskNumber, removed);
            
            // Add entries for the last N days
            for (int n = 0; n < 30; n++) {
                Date entryDate = today - days(n);
                bool missingEntry = uniform01(rand) < 0.05;
                if (missingEntry) continue;
                
                addEntry(conn, classId, studentId, entryDate, rand);
            }
        }
    }
}

ulong addUser(Connection conn, string username, string password, bool locked, bool admin) {
    import std.digest.sha;
    string passwordHash = cast(string) sha256Of(password).toHexString().idup;
    return insertOne(
        conn,
        "INSERT INTO auth_user (username, password_hash, is_locked, is_admin) VALUES (?, ?, ?, ?) RETURNING id",
        username, passwordHash, locked, admin
    );
}

ulong addClass(Connection conn, string schoolYear, ushort number, ulong userId) {
    return insertOne(
        conn,
        "INSERT INTO classroom_compliance_class (number, school_year, user_id) VALUES (?, ?, ?) RETURNING id",
        number, schoolYear, userId
    );
}

ulong addStudent(Connection conn, string name, ulong classId, ushort deskNumber, bool removed) {
    return insertOne(
        conn,
        "INSERT INTO classroom_compliance_student
        (name, class_id, desk_number, removed)
        VALUES (?, ?, ?, ?) RETURNING id",
        name, classId, deskNumber, removed
    );
}

void addEntry(
    Connection conn,
    ulong classId,
    ulong studentId,
    Date date,
    ref Random rand
) {
    bool absent = uniform01(rand) < 0.05;
    bool hasComment = uniform01(rand) < 0.25;
    string comment = hasComment ? "This is a sample comment." : "";

    const entryQuery = "
    INSERT INTO classroom_compliance_entry
    (class_id, student_id, date, absent, comment)
    VALUES (?, ?, ?, ?, ?)";
    PreparedStatement ps = conn.prepareStatement(entryQuery);
    scope(exit) ps.close();
    ps.setUlong(1, classId);
    ps.setUlong(2, studentId);
    ps.setDate(3, date);
    ps.setBoolean(4, absent);
    ps.setString(5, comment);
    ps.executeUpdate();

    bool hasChecklistItems = uniform01(rand) < 0.75;
    if (!hasChecklistItems) return;

    const checklistItemQuery = "
    INSERT INTO classroom_compliance_entry_checklist_item
    (class_id, student_id, date, item, checked, category)
    VALUES (?, ?, ?, ?, ?, ?)";
    PreparedStatement ckPs = conn.prepareStatement(checklistItemQuery);
    scope(exit) ckPs.close();
    const categories = ["Classroom Readiness", "Behavior"];
    foreach (category; categories) {
        for (int i = 0; i < 6; i++) {
            import std.conv : to;
            string item = "Checklist item " ~ (i+1).to!string;
            bool checked = uniform01(rand) < 0.25;
            ckPs.setUlong(1, classId);
            ckPs.setUlong(2, studentId);
            ckPs.setDate(3, date);
            ckPs.setString(4, item);
            ckPs.setBoolean(5, checked);
            ckPs.setString(6, category);
            ckPs.executeUpdate();
        }
    }
}

void deleteAllData(Connection conn) {
    Statement stmt = conn.createStatement();
    scope(exit) stmt.close();

    const tables = [
        "announcement",
        "classroom_compliance_class_note",
        "classroom_compliance_entry_checklist_item",
        "classroom_compliance_entry",
        "classroom_compliance_student",
        "classroom_compliance_class",
        "auth_user"
    ];
    foreach (tableName; tables) {
        int rows = stmt.executeUpdate("DELETE FROM " ~ tableName);
        infoF!"Deleted %d rows from %s."(rows, tableName);
    }
}
