import { eq, or, and, inArray } from "drizzle-orm";
import { featureFlags, featureFlagAccess } from "@db/schema";
import { DrizzleClient } from "@shared/db/DrizzleClient";
import { UserError } from "@shared/lib/errors";

export interface FeatureFlagContext {
    guildId: string;
    userId: string;
    memberRoles: string[];
}

export const featureFlagsService = {
    /** Check whether a feature flag is enabled globally. */
    isFlagEnabled: async (flagName: string): Promise<boolean> => {
        const flag = await DrizzleClient.query.featureFlags.findFirst({
            where: eq(featureFlags.name, flagName),
        });
        return flag?.enabled ?? false;
    },

    /** Check if a guild/user/role has access to a feature flag; returns false if flag is disabled. */
    hasAccess: async (
        flagName: string,
        context: FeatureFlagContext
    ): Promise<boolean> => {
        const flag = await DrizzleClient.query.featureFlags.findFirst({
            where: eq(featureFlags.name, flagName),
        });
        if (!flag || !flag.enabled) return false;

        const access = await DrizzleClient.query.featureFlagAccess.findFirst({
            where: and(
                eq(featureFlagAccess.flagId, flag.id),
                or(
                    eq(featureFlagAccess.guildId, BigInt(context.guildId)),
                    eq(featureFlagAccess.userId, BigInt(context.userId))
                )
            ),
        });

        if (access) return true;

        if (context.memberRoles.length > 0) {
            const roleAccess = await DrizzleClient.query.featureFlagAccess.findFirst({
                where: and(
                    eq(featureFlagAccess.flagId, flag.id),
                    inArray(featureFlagAccess.roleId, context.memberRoles.map(r => BigInt(r)))
                ),
            });
            return !!roleAccess;
        }

        return false;
    },

    /** Create a new feature flag, disabled by default. */
    createFlag: async (name: string, description?: string) => {
        const [flag] = await DrizzleClient.insert(featureFlags).values({
            name,
            description,
            enabled: false,
        }).returning();
        return flag;
    },

    /** Enable or disable a feature flag by name; throws if the flag does not exist. */
    setFlagEnabled: async (name: string, enabled: boolean) => {
        const [flag] = await DrizzleClient.update(featureFlags)
            .set({ enabled, updatedAt: new Date() })
            .where(eq(featureFlags.name, name))
            .returning();
        
        if (!flag) {
            throw new UserError(`Feature flag "${name}" not found`);
        }
        return flag;
    },

    /** Grant a guild, user, or role access to a feature flag. */
    grantAccess: async (
        flagName: string,
        access: { guildId?: string; userId?: string; roleId?: string }
    ) => {
        const flag = await DrizzleClient.query.featureFlags.findFirst({
            where: eq(featureFlags.name, flagName),
        });
        if (!flag) throw new UserError(`Feature flag "${flagName}" not found`);

        const [accessRecord] = await DrizzleClient.insert(featureFlagAccess).values({
            flagId: flag.id,
            guildId: access.guildId ? BigInt(access.guildId) : null,
            userId: access.userId ? BigInt(access.userId) : null,
            roleId: access.roleId ? BigInt(access.roleId) : null,
        }).returning();
        return accessRecord;
    },

    /** Revoke a specific access record by ID; throws if not found. */
    revokeAccess: async (accessId: number) => {
        const [access] = await DrizzleClient.delete(featureFlagAccess)
            .where(eq(featureFlagAccess.id, accessId))
            .returning();
        
        if (!access) {
            throw new UserError(`Access record "${accessId}" not found`);
        }
        return access;
    },

    /** Retrieve a single feature flag by name. */
    getFlag: async (name: string) => {
        return await DrizzleClient.query.featureFlags.findFirst({
            where: eq(featureFlags.name, name),
        });
    },

    /** List all feature flags, ordered by name. */
    listFlags: async () => {
        return await DrizzleClient.query.featureFlags.findMany({
            orderBy: (flags, { asc }) => [asc(flags.name)],
        });
    },

    /** List all access records for a given feature flag. */
    listAccess: async (flagName: string) => {
        const flag = await DrizzleClient.query.featureFlags.findFirst({
            where: eq(featureFlags.name, flagName),
        });
        if (!flag) return [];

        return await DrizzleClient.query.featureFlagAccess.findMany({
            where: eq(featureFlagAccess.flagId, flag.id),
            orderBy: (access, { asc }) => [asc(access.id)],
        });
    },

    /** Delete a feature flag and its associated access records; throws if not found. */
    deleteFlag: async (name: string) => {
        const [flag] = await DrizzleClient.delete(featureFlags)
            .where(eq(featureFlags.name, name))
            .returning();
        
        if (!flag) {
            throw new UserError(`Feature flag "${name}" not found`);
        }
        return flag;
    },
};
