import { and, eq } from "drizzle-orm";
import { randomBytes, scryptSync, timingSafeEqual } from "node:crypto";
import { messages, sessions, userSettings, users } from "../db/schema.js";
import { uuidv7 } from "../db/uuidv7.js";
export const REASONING_EFFORT_VALUES = new Set(["minimal", "low", "medium", "high"]);
function normalizeSttProvider(value) {
    if (value === "gpt")
        return "gpt";
    if (value === "elevenlabs")
        return "elevenlabs";
    return value === "wyoming" ? "wyoming" : "whisper";
}
const HASH_PREFIX = "scrypt";
function normalizeUsername(username) {
    return username.trim().toLowerCase();
}
function parseApiKeys(raw) {
    if (!raw)
        return {};
    try {
        const parsed = JSON.parse(raw);
        if (!parsed || typeof parsed !== "object")
            return {};
        const out = {};
        for (const [key, value] of Object.entries(parsed)) {
            if (typeof value === "string")
                out[key] = value;
        }
        return out;
    }
    catch {
        return {};
    }
}
function parseStringArray(raw) {
    if (!raw)
        return [];
    try {
        const parsed = JSON.parse(raw);
        if (!Array.isArray(parsed))
            return [];
        return parsed.filter((v) => typeof v === "string");
    }
    catch {
        return [];
    }
}
function hashPassword(password) {
    const salt = randomBytes(16).toString("hex");
    const digest = scryptSync(password, salt, 64).toString("hex");
    return `${HASH_PREFIX}$${salt}$${digest}`;
}
function verifyPasswordHash(password, encoded) {
    const [prefix, salt, digest] = encoded.split("$");
    if (prefix !== HASH_PREFIX || !salt || !digest)
        return false;
    const expected = Buffer.from(digest, "hex");
    const actual = scryptSync(password, salt, expected.length);
    if (expected.length !== actual.length)
        return false;
    return timingSafeEqual(expected, actual);
}
export class UserService {
    db;
    constructor(db) {
        this.db = db;
    }
    countUsers() {
        const row = this.db.select({ count: users.id }).from(users).all();
        return row.length;
    }
    findByUsername(username) {
        const normalized = normalizeUsername(username);
        const row = this.db.select().from(users).where(eq(users.username, normalized)).get();
        if (!row)
            return null;
        return {
            id: row.id,
            username: row.username,
            createdAt: row.createdAt,
            updatedAt: row.updatedAt,
        };
    }
    findById(id) {
        const row = this.db.select().from(users).where(eq(users.id, id)).get();
        if (!row)
            return null;
        return {
            id: row.id,
            username: row.username,
            createdAt: row.createdAt,
            updatedAt: row.updatedAt,
        };
    }
    createUser(username, password) {
        const normalized = normalizeUsername(username);
        const now = new Date().toISOString();
        const id = uuidv7();
        this.db.insert(users).values({
            id,
            username: normalized,
            passwordHash: hashPassword(password),
            createdAt: now,
            updatedAt: now,
        }).run();
        this.db.insert(userSettings).values({
            userId: id,
            theme: "system",
            apiKeys: JSON.stringify({}),
            sttProvider: "whisper",
            projectPickerPath: null,
            projectPickerNodeId: null,
            updatedAt: now,
        }).run();
        return this.findById(id);
    }
    verifyCredentials(username, password) {
        const normalized = normalizeUsername(username);
        const row = this.db.select().from(users).where(eq(users.username, normalized)).get();
        if (!row)
            return null;
        if (!verifyPasswordHash(password, row.passwordHash))
            return null;
        return {
            id: row.id,
            username: row.username,
            createdAt: row.createdAt,
            updatedAt: row.updatedAt,
        };
    }
    getSettings(userId) {
        const row = this.db.select().from(userSettings).where(eq(userSettings.userId, userId)).get();
        if (!row) {
            const now = new Date().toISOString();
            this.db.insert(userSettings).values({
                userId,
                theme: "system",
                apiKeys: JSON.stringify({}),
                disabledTools: JSON.stringify([]),
                sttProvider: "whisper",
                chatProvider: "jait",
                jaitBackend: "openai",
                recentModels: JSON.stringify([]),
                selectedModel: null,
                reasoningEffort: null,
                projectPickerPath: null,
                projectPickerNodeId: null,
                updatedAt: now,
            }).run();
            return {
                userId,
                theme: "system",
                apiKeys: {},
                disabledTools: [],
                sttProvider: "whisper",
                chatProvider: "jait",
                jaitBackend: "openai",
                recentModels: [],
                selectedModel: null,
                reasoningEffort: null,
                projectPickerPath: null,
                projectPickerNodeId: null,
                updatedAt: now,
            };
        }
        return {
            userId: row.userId,
            theme: row.theme || "system",
            apiKeys: parseApiKeys(row.apiKeys),
            disabledTools: parseStringArray(row.disabledTools ?? null),
            sttProvider: normalizeSttProvider(typeof row.sttProvider === "string" ? row.sttProvider : null),
            chatProvider: row.chatProvider || "jait",
            jaitBackend: row.jaitBackend || "openai",
            recentModels: parseStringArray(row.recentModels ?? null),
            selectedModel: typeof row.selectedModel === "string" ? row.selectedModel : null,
            reasoningEffort: REASONING_EFFORT_VALUES.has(row.reasoningEffort) ? row.reasoningEffort : null,
            projectPickerPath: typeof row.projectPickerPath === "string" ? row.projectPickerPath : null,
            projectPickerNodeId: typeof row.projectPickerNodeId === "string" ? row.projectPickerNodeId : null,
            updatedAt: row.updatedAt,
        };
    }
    updateSettings(userId, patch) {
        const existing = this.getSettings(userId);
        const theme = patch.theme ?? existing.theme;
        const apiKeys = patch.apiKeys ?? existing.apiKeys;
        const disabledTools = patch.disabledTools ?? existing.disabledTools;
        const sttProvider = patch.sttProvider ?? existing.sttProvider;
        const chatProvider = patch.chatProvider ?? existing.chatProvider;
        const jaitBackend = patch.jaitBackend ?? existing.jaitBackend;
        const recentModels = patch.recentModels ?? existing.recentModels;
        const selectedModel = patch.selectedModel !== undefined
            ? patch.selectedModel
            : existing.selectedModel;
        const reasoningEffort = patch.reasoningEffort !== undefined
            ? patch.reasoningEffort
            : existing.reasoningEffort;
        const projectPickerPath = patch.projectPickerPath !== undefined
            ? patch.projectPickerPath
            : existing.projectPickerPath;
        const projectPickerNodeId = patch.projectPickerNodeId !== undefined
            ? patch.projectPickerNodeId
            : existing.projectPickerNodeId;
        const now = new Date().toISOString();
        this.db
            .update(userSettings)
            .set({
            theme,
            apiKeys: JSON.stringify(apiKeys),
            disabledTools: JSON.stringify(disabledTools),
            sttProvider,
            chatProvider,
            jaitBackend,
            recentModels: JSON.stringify(recentModels),
            selectedModel,
            reasoningEffort,
            projectPickerPath,
            projectPickerNodeId,
            updatedAt: now,
        })
            .where(eq(userSettings.userId, userId))
            .run();
        return this.getSettings(userId);
    }
    bindSessionToUser(userId, sessionId) {
        const session = this.db.select().from(sessions).where(eq(sessions.id, sessionId)).get();
        if (!session)
            return false;
        this.db
            .update(sessions)
            .set({ userId })
            .where(eq(sessions.id, sessionId))
            .run();
        return true;
    }
    clearArchivedSessions(userId) {
        const archived = this.db
            .select({ id: sessions.id })
            .from(sessions)
            .where(and(eq(sessions.userId, userId), eq(sessions.status, "archived")))
            .all();
        if (archived.length === 0)
            return 0;
        const ids = archived.map((row) => row.id);
        for (const id of ids) {
            this.db.delete(messages).where(eq(messages.sessionId, id)).run();
            this.db.delete(sessions).where(and(eq(sessions.id, id), eq(sessions.userId, userId))).run();
        }
        return ids.length;
    }
}
//# sourceMappingURL=users.js.map