"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
    return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (g && (g = 0, op[0] && (_ = 0)), _) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
    if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
        if (ar || !(i in from)) {
            if (!ar) ar = Array.prototype.slice.call(from, 0, i);
            ar[i] = from[i];
        }
    }
    return to.concat(ar || Array.prototype.slice.call(from));
};
Object.defineProperty(exports, "__esModule", { value: true });
var sqlite3 = require("sqlite3");
var faker_1 = require("@faker-js/faker");
var lodash_1 = require("lodash");
var luxon_1 = require("luxon");
var sqlite_1 = require("sqlite");
var uuid_1 = require("uuid");
var dbPath = "./db/sqlite.db";
// Configurations for test data generation
var startDate = luxon_1.DateTime.fromISO("2024-11-01");
var endDate = luxon_1.DateTime.fromISO("2024-11-30");
var holidays = []; // Example holidays
var existingJobs = [];
// Calendar week usage configs
var weekUsage = {
    44: { minJobs: 4, maxJobs: 6 },
    45: { minJobs: 10, maxJobs: 15 },
    46: { minJobs: 10, maxJobs: 15 },
    47: { minJobs: 3, maxJobs: 5 },
    48: { minJobs: 10, maxJobs: 15 },
};
var printerUsageBias = {
    fastPrints: ["Printer A", "Printer B"],
    largePrints: ["Printer C", "Printer D", "Printer E"],
};
var initDB = function () { return __awaiter(void 0, void 0, void 0, function () {
    return __generator(this, function (_a) {
        console.log("Initializing database connection...");
        return [2 /*return*/, (0, sqlite_1.open)({
                filename: dbPath,
                driver: sqlite3.Database,
            })];
    });
}); };
var createUser = function (isPowerUser) {
    if (isPowerUser === void 0) { isPowerUser = false; }
    var name = [faker_1.faker.person.firstName(), faker_1.faker.person.lastName()];
    var user = {
        id: (0, uuid_1.v4)(),
        github_id: faker_1.faker.number.int(),
        username: "".concat(name[0].slice(0, 2)).concat(name[1].slice(0, 6)).toUpperCase(),
        displayName: "".concat(name[0], " ").concat(name[1]).toUpperCase(),
        email: "".concat(name[0], ".").concat(name[1], "@mercedes-benz.com"),
        role: (0, lodash_1.sample)(["user", "admin"]),
        isPowerUser: isPowerUser,
    };
    console.log("Created user:", user);
    return user;
};
var createPrinter = function () {
    var printer = {
        id: (0, uuid_1.v4)(),
        name: "Printer ".concat(faker_1.faker.number.int({ max: 9 })),
        description: faker_1.faker.lorem.sentence(),
        status: (0, lodash_1.random)(0, 2),
    };
    console.log("Created printer:", printer);
    return printer;
};
var isPrinterAvailable = function (printer, startAt, duration) {
    var endAt = startAt + duration * 60 * 1000; // Convert minutes to milliseconds
    return !existingJobs.some(function (job) {
        var jobStart = job.startAt;
        var jobEnd = job.startAt + job.durationInMinutes * 60 * 1000;
        return (printer.id === job.printerId &&
            ((startAt >= jobStart && startAt < jobEnd) ||
                (endAt > jobStart && endAt <= jobEnd) ||
                (startAt <= jobStart && endAt >= jobEnd)));
    });
};
var weightedSampleUser = function (users) {
    var weights = users.map(function (user) { return (user.isPowerUser ? 3 : 1); });
    var weightedUsers = users.flatMap(function (user, index) { return Array(weights[index]).fill(user); });
    return (0, lodash_1.sample)(weightedUsers);
};
var normalRandom = function (mean, stdDev) {
    var u = 0;
    var v = 0;
    while (u === 0)
        u = Math.random();
    while (v === 0)
        v = Math.random();
    return mean + stdDev * Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
};
var createPrintJob = function (users, printers, startAt) {
    var user = weightedSampleUser(users);
    var printer;
    // Probabilistic determination for large prints
    var isLargePrint = (0, lodash_1.random)(0, 100) < 30; // 30% chance for a large print
    if (isLargePrint) {
        printer = (0, lodash_1.sample)(printers.filter(function (p) { return printerUsageBias.largePrints.includes(p.name); }));
    }
    else {
        printer = (0, lodash_1.sample)(printers.filter(function (p) { return printerUsageBias.fastPrints.includes(p.name); }));
    }
    if (!printer) {
        printer = (0, lodash_1.sample)(printers);
    }
    // Variable duration for print jobs with realistic limits
    var duration = Math.round(normalRandom(isLargePrint ? 240 : 75, 30));
    var minDuration = 15;
    var maxDuration = isLargePrint ? 5760 : 3540; // Maximum duration of 96 hours or 59 minutes in minutes
    if (isLargePrint) {
        duration = Math.min(duration, maxDuration);
    }
    duration = Math.max(duration, minDuration);
    duration = Math.min(duration, maxDuration);
    // Ensure printer availability
    if (!isPrinterAvailable(printer, startAt, duration)) {
        console.log("Printer not available, skipping job creation.");
        return null;
    }
    // Dynamic error probability based on printer status, duration, and other factors
    var aborted = false;
    var abortReason = null;
    var baseErrorRate = Math.max(0, Math.min(5, 100)); // Ensure error rate is between 0% and 100%
    var userErrorModifier = user.isPowerUser ? -2 : 2; // Power users make fewer errors
    var timeErrorModifier = startAt >= luxon_1.DateTime.fromObject({ hour: 14 }).toMillis() && startAt <= luxon_1.DateTime.fromObject({ hour: 17 }).toMillis()
        ? 1
        : 0; // More errors in the afternoon
    var errorRate = baseErrorRate + userErrorModifier + timeErrorModifier;
    if ((0, lodash_1.random)(0, 100) < Math.max(0, Math.min(errorRate, 100))) {
        aborted = true;
        abortReason = generateDynamicAbortReason();
    }
    var printJob = {
        id: (0, uuid_1.v4)(),
        printerId: printer.id,
        userId: user.id,
        startAt: startAt,
        durationInMinutes: duration,
        comments: faker_1.faker.lorem.sentence(),
        aborted: aborted,
        abortReason: abortReason,
    };
    console.log("Created print job:", printJob);
    return printJob;
};
var generateDynamicAbortReason = function () {
    var reasons = [
        "Filament gerissen",
        "Drucker überhitzt",
        "Schichtversatz festgestellt",
        "Düse verstopft",
        "Kalibrierung fehlgeschlagen",
        "E".concat((0, lodash_1.random)(500, 599)),
    ];
    var reason = (0, lodash_1.sample)(reasons);
    // Add typos to simulate human variability
    if ((0, lodash_1.random)(0, 1)) {
        reason = reason.replace("e", (0, lodash_1.random)(0, 1) ? "é" : "e");
    }
    return reason;
};
var generatePrintJobsForWeek = function (users, printers, weekNumber, 
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
db, dryRun) { return __awaiter(void 0, void 0, void 0, function () {
    var weekConfig, totalJobs, startOfWeek, jobsRemaining, day, dayDate, _i, printers_1, printer, jobsForDay, i, timeSlots, selectedSlot, startAt, printJob;
    return __generator(this, function (_a) {
        switch (_a.label) {
            case 0:
                console.log("Generating print jobs for week ".concat(weekNumber, "..."));
                weekConfig = weekUsage[weekNumber];
                totalJobs = (0, lodash_1.random)(weekConfig.minJobs, weekConfig.maxJobs);
                startOfWeek = startDate.plus({ weeks: weekNumber - 44 });
                jobsRemaining = totalJobs;
                day = 0;
                _a.label = 1;
            case 1:
                if (!(day < 7)) return [3 /*break*/, 7];
                dayDate = startOfWeek.plus({ days: day });
                if (dayDate > endDate || jobsRemaining <= 0)
                    return [3 /*break*/, 7];
                if (holidays.includes(dayDate.toISODate()) || dayDate.weekday === 6 || dayDate.weekday === 7) {
                    console.log("Skipping holiday or weekend: ".concat(dayDate.toISODate()));
                    return [3 /*break*/, 6];
                }
                // Update printer status to simulate maintenance or breakdowns
                for (_i = 0, printers_1 = printers; _i < printers_1.length; _i++) {
                    printer = printers_1[_i];
                    if ((0, lodash_1.random)(0, 100) < 5) {
                        // 5% chance per day that a printer goes out of service
                        printer.status = 2; // Status 2 means "out of service"
                        console.log("Printer ".concat(printer.name, " is out of service on ").concat(dayDate.toISODate()));
                    }
                    else if (printer.status === 2) {
                        printer.status = 0; // Printer becomes available again
                    }
                }
                jobsForDay = Math.min(jobsRemaining, (0, lodash_1.random)(1, 3));
                jobsRemaining -= jobsForDay;
                console.log("Generating ".concat(jobsForDay, " print jobs for day ").concat(dayDate.toISODate(), "..."));
                i = 0;
                _a.label = 2;
            case 2:
                if (!(i < jobsForDay)) return [3 /*break*/, 6];
                timeSlots = [
                    { hour: 7, minute: 0 },
                    { hour: 11, minute: 0 },
                    { hour: 13, minute: 0 },
                    { hour: 15, minute: 0 },
                ];
                selectedSlot = (0, lodash_1.sample)(timeSlots);
                startAt = luxon_1.DateTime.fromISO("".concat(dayDate.toISODate(), "T").concat(String(selectedSlot.hour).padStart(2, "0"), ":").concat(String(selectedSlot.minute + (0, lodash_1.random)(0, 30)).padStart(2, "0"), ":00"));
                printJob = createPrintJob(users, printers, startAt.toMillis());
                if (!printJob) return [3 /*break*/, 5];
                if (!!dryRun) return [3 /*break*/, 4];
                return [4 /*yield*/, db.run("INSERT INTO printJob (id, printerId, userId, startAt, durationInMinutes, comments, aborted, abortReason)\n             VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [
                        printJob.id,
                        printJob.printerId,
                        printJob.userId,
                        printJob.startAt,
                        printJob.durationInMinutes,
                        printJob.comments,
                        printJob.aborted ? 1 : 0,
                        printJob.abortReason,
                    ])];
            case 3:
                _a.sent();
                _a.label = 4;
            case 4:
                existingJobs.push(printJob);
                console.log("Inserted print job into database:", printJob.id);
                console.log(JSON.stringify({
                    event: "PrintJobCreated",
                    jobId: printJob.id,
                    printerId: printJob.printerId,
                    userId: printJob.userId,
                    startAt: new Date(printJob.startAt).toISOString(),
                    duration: printJob.durationInMinutes,
                    aborted: printJob.aborted,
                }));
                _a.label = 5;
            case 5:
                i++;
                return [3 /*break*/, 2];
            case 6:
                day++;
                return [3 /*break*/, 1];
            case 7: return [2 /*return*/];
        }
    });
}); };
var generateTestData = function () {
    var args_1 = [];
    for (var _i = 0; _i < arguments.length; _i++) {
        args_1[_i] = arguments[_i];
    }
    return __awaiter(void 0, __spreadArray([], args_1, true), void 0, function (dryRun) {
        var db, users, printers, _a, users_1, user, _b, printers_2, printer, validateData, _c, _d, weekNumber;
        if (dryRun === void 0) { dryRun = false; }
        return __generator(this, function (_e) {
            switch (_e.label) {
                case 0:
                    console.log("Starting test data generation...");
                    return [4 /*yield*/, initDB()];
                case 1:
                    db = _e.sent();
                    users = __spreadArray(__spreadArray([], Array.from({ length: 7 }, function () { return createUser(false); }), true), Array.from({ length: 3 }, function () { return createUser(true); }), true);
                    printers = Array.from({ length: 5 }, createPrinter);
                    if (!!dryRun) return [3 /*break*/, 9];
                    _a = 0, users_1 = users;
                    _e.label = 2;
                case 2:
                    if (!(_a < users_1.length)) return [3 /*break*/, 5];
                    user = users_1[_a];
                    return [4 /*yield*/, db.run("INSERT INTO user (id, github_id, name, displayName, email, role)\n         VALUES (?, ?, ?, ?, ?, ?)", [user.id, user.github_id, user.username, user.displayName, user.email, user.role])];
                case 3:
                    _e.sent();
                    console.log("Inserted user into database:", user.id);
                    _e.label = 4;
                case 4:
                    _a++;
                    return [3 /*break*/, 2];
                case 5:
                    _b = 0, printers_2 = printers;
                    _e.label = 6;
                case 6:
                    if (!(_b < printers_2.length)) return [3 /*break*/, 9];
                    printer = printers_2[_b];
                    return [4 /*yield*/, db.run("INSERT INTO printer (id, name, description, status)\n         VALUES (?, ?, ?, ?)", [printer.id, printer.name, printer.description, printer.status])];
                case 7:
                    _e.sent();
                    console.log("Inserted printer into database:", printer.id);
                    _e.label = 8;
                case 8:
                    _b++;
                    return [3 /*break*/, 6];
                case 9:
                    validateData = function (printJobs, users, printers) {
                        var _loop_1 = function (job) {
                            var userExists = users.some(function (user) { return user.id === job.userId; });
                            var printerExists = printers.some(function (printer) { return printer.id === job.printerId; });
                            if (!userExists || !printerExists) {
                                console.error("Invalid job detected: ".concat(job.id));
                            }
                        };
                        for (var _i = 0, printJobs_1 = printJobs; _i < printJobs_1.length; _i++) {
                            var job = printJobs_1[_i];
                            _loop_1(job);
                        }
                    };
                    _c = 0, _d = Object.keys(weekUsage);
                    _e.label = 10;
                case 10:
                    if (!(_c < _d.length)) return [3 /*break*/, 13];
                    weekNumber = _d[_c];
                    return [4 /*yield*/, generatePrintJobsForWeek(users, printers, Number.parseInt("".concat(weekNumber)), db, dryRun)];
                case 11:
                    _e.sent();
                    console.log("======> \uD83D\uDCC5 Week ".concat(weekNumber));
                    validateData(existingJobs, users, printers);
                    _e.label = 12;
                case 12:
                    _c++;
                    return [3 /*break*/, 10];
                case 13:
                    if (!!dryRun) return [3 /*break*/, 15];
                    return [4 /*yield*/, db.close()];
                case 14:
                    _e.sent();
                    console.log("Database connection closed. Test data generation complete.");
                    return [3 /*break*/, 16];
                case 15:
                    console.log("Dry run complete. No data was written to the database.");
                    _e.label = 16;
                case 16: return [2 /*return*/];
            }
        });
    });
};
generateTestData(process.argv.includes("--dry-run"))
    .then(function () {
    console.log("Test data generation script finished.");
})
    .catch(function (err) {
    console.error("Error generating test data:", err);
});