import { randomBytes } from 'crypto';
import { logSystem } from './log';
import { redisSystem } from './redis';

const createJobId = (): string => {
    const bytes = randomBytes(16);

    // RFC 4122 UUID v4 variant/version bits
    bytes[6] = (bytes[6] & 0x0f) | 0x40;
    bytes[8] = (bytes[8] & 0x3f) | 0x80;

    const hex = bytes.toString('hex');
    return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
};

type MongoJobPayload =
    | {
          op: 'insertOne';
          db: string;
          collection: string;
          document: unknown;
      }
    | {
          op: 'updateOne';
          db: string;
          collection: string;
          filter: Record<string, unknown>;
          update: Record<string, unknown>;
          options?: Record<string, unknown>;
      };

type MongoJob = MongoJobPayload & {
    id: string;
    ts: number;
};

export class MongoSystem {
    private queueKey = process.env.LSRAGE_MONGO_QUEUE || 'mongo:jobs';

    async init(): Promise<void> {
        // No direct Mongo connection in game-server anymore.
        // Only verifies Redis path is available.
        const redis: any = (redisSystem as any).client ?? (redisSystem as any).redis ?? redisSystem;

        if (!redis) {
            throw new Error('Redis client is not available for Mongo queue publishing.');
        }

        logSystem.initLog('MongoQueue', `Enabled (queue=${this.queueKey})`);
    }

    async publish(job: MongoJobPayload): Promise<void> {
        const redis: any = (redisSystem as any).client ?? (redisSystem as any).redis ?? redisSystem;
        const payload: MongoJob = {
            id: createJobId(),
            ts: Date.now(),
            ...job,
        };

        // Support both node-redis (lPush) and ioredis (lpush)
        if (typeof redis.lPush === 'function') {
            await redis.lPush(this.queueKey, JSON.stringify(payload));
        } else if (typeof redis.lpush === 'function') {
            await redis.lpush(this.queueKey, JSON.stringify(payload));
        } else {
            throw new Error('Redis client does not support LPUSH.');
        }
    }

    async insertOne(db: string, collection: string, document: unknown): Promise<void> {
        await this.publish({ op: 'insertOne', db, collection, document });
    }

    async updateOne(
        db: string,
        collection: string,
        filter: Record<string, unknown>,
        update: Record<string, unknown>,
        options?: Record<string, unknown>,
    ): Promise<void> {
        await this.publish({ op: 'updateOne', db, collection, filter, update, options });
    }
}

export const mongoSystem = new MongoSystem();
