import { eq } from "drizzle-orm";
import { guildSettings } from "@db/schema";
import { DrizzleClient } from "@shared/db/DrizzleClient";
import { UserError } from "@shared/lib/errors";

export interface GuildSettingsData {
    guildId: string;
    studentRoleId?: string;
    visitorRoleId?: string;
    colorRoleIds?: string[];
    welcomeChannelId?: string;
    welcomeMessage?: string;
    feedbackChannelId?: string;
    terminalChannelId?: string;
    terminalMessageId?: string;
    moderationLogChannelId?: string;
    moderationDmOnWarn?: boolean;
    moderationAutoTimeoutThreshold?: number;
    featureOverrides?: Record<string, boolean>;
}

export const guildSettingsService = {
    /** Retrieve guild settings by guild ID, or null if none exist. */
    getSettings: async (guildId: string): Promise<GuildSettingsData | null> => {
        const settings = await DrizzleClient.query.guildSettings.findFirst({
            where: eq(guildSettings.guildId, BigInt(guildId)),
        });

        if (!settings) return null;

        return {
            guildId: settings.guildId.toString(),
            studentRoleId: settings.studentRoleId?.toString(),
            visitorRoleId: settings.visitorRoleId?.toString(),
            colorRoleIds: settings.colorRoleIds ?? [],
            welcomeChannelId: settings.welcomeChannelId?.toString(),
            welcomeMessage: settings.welcomeMessage ?? undefined,
            feedbackChannelId: settings.feedbackChannelId?.toString(),
            terminalChannelId: settings.terminalChannelId?.toString(),
            terminalMessageId: settings.terminalMessageId?.toString(),
            moderationLogChannelId: settings.moderationLogChannelId?.toString(),
            moderationDmOnWarn: settings.moderationDmOnWarn ?? true,
            moderationAutoTimeoutThreshold: settings.moderationAutoTimeoutThreshold ?? undefined,
            featureOverrides: settings.featureOverrides ?? {},
        };
    },

    /** Create or fully replace guild settings via upsert. */
    upsertSettings: async (data: Partial<GuildSettingsData> & { guildId: string }) => {
        const values: typeof guildSettings.$inferInsert = {
            guildId: BigInt(data.guildId),
            studentRoleId: data.studentRoleId ? BigInt(data.studentRoleId) : null,
            visitorRoleId: data.visitorRoleId ? BigInt(data.visitorRoleId) : null,
            colorRoleIds: data.colorRoleIds ?? [],
            welcomeChannelId: data.welcomeChannelId ? BigInt(data.welcomeChannelId) : null,
            welcomeMessage: data.welcomeMessage ?? null,
            feedbackChannelId: data.feedbackChannelId ? BigInt(data.feedbackChannelId) : null,
            terminalChannelId: data.terminalChannelId ? BigInt(data.terminalChannelId) : null,
            terminalMessageId: data.terminalMessageId ? BigInt(data.terminalMessageId) : null,
            moderationLogChannelId: data.moderationLogChannelId ? BigInt(data.moderationLogChannelId) : null,
            moderationDmOnWarn: data.moderationDmOnWarn ?? true,
            moderationAutoTimeoutThreshold: data.moderationAutoTimeoutThreshold ?? null,
            featureOverrides: data.featureOverrides ?? {},
            updatedAt: new Date(),
        };

        const [result] = await DrizzleClient.insert(guildSettings)
            .values(values)
            .onConflictDoUpdate({
                target: guildSettings.guildId,
                set: values,
            })
            .returning();

        return result;
    },

    /** Update a single guild setting by key name; throws if the key is unknown or settings do not exist. */
    updateSetting: async (
        guildId: string,
        key: string,
        value: string | string[] | boolean | number | Record<string, boolean> | null
    ) => {
        const keyMap: Record<string, keyof typeof guildSettings.$inferSelect> = {
            studentRole: "studentRoleId",
            visitorRole: "visitorRoleId",
            colorRoles: "colorRoleIds",
            welcomeChannel: "welcomeChannelId",
            welcomeMessage: "welcomeMessage",
            feedbackChannel: "feedbackChannelId",
            terminalChannel: "terminalChannelId",
            terminalMessage: "terminalMessageId",
            moderationLogChannel: "moderationLogChannelId",
            moderationDmOnWarn: "moderationDmOnWarn",
            moderationAutoTimeoutThreshold: "moderationAutoTimeoutThreshold",
            featureOverrides: "featureOverrides",
        };

        const column = keyMap[key];
        if (!column) {
            throw new UserError(`Unknown setting: ${key}`);
        }

        const updates: Record<string, unknown> = { updatedAt: new Date() };

        if (column === "colorRoleIds" && Array.isArray(value)) {
            updates[column] = value;
        } else if (column === "featureOverrides" && typeof value === "object" && value !== null) {
            updates[column] = value;
        } else if (column === "moderationDmOnWarn" && typeof value === "boolean") {
            updates[column] = value;
        } else if (column === "moderationAutoTimeoutThreshold" && typeof value === "number") {
            updates[column] = value;
        } else if (typeof value === "string") {
            updates[column] = BigInt(value);
        } else if (value === null) {
            updates[column] = null;
        } else {
            updates[column] = value;
        }

        const [result] = await DrizzleClient.update(guildSettings)
            .set(updates)
            .where(eq(guildSettings.guildId, BigInt(guildId)))
            .returning();

        if (!result) {
            throw new UserError(`No settings found for guild ${guildId}. Use /settings set to create settings first.`);
        }

        return result;
    },

    /** Delete all settings for a guild. */
    deleteSettings: async (guildId: string) => {
        const [result] = await DrizzleClient.delete(guildSettings)
            .where(eq(guildSettings.guildId, BigInt(guildId)))
            .returning();

        return result;
    },

    /** Add a color role to the guild's allowed list; no-ops if already present. */
    addColorRole: async (guildId: string, roleId: string) => {
        const settings = await guildSettingsService.getSettings(guildId);
        const colorRoleIds = settings?.colorRoleIds ?? [];

        if (colorRoleIds.includes(roleId)) {
            return settings;
        }

        colorRoleIds.push(roleId);
        return await guildSettingsService.upsertSettings({ guildId, colorRoleIds });
    },

    /** Remove a color role from the guild's allowed list. */
    removeColorRole: async (guildId: string, roleId: string) => {
        const settings = await guildSettingsService.getSettings(guildId);
        if (!settings) return null;

        const colorRoleIds = (settings.colorRoleIds ?? []).filter(id => id !== roleId);
        return await guildSettingsService.upsertSettings({ guildId, colorRoleIds });
    },
};
