import * as MongoClient from 'mongodb';
import { Client } from './client';
import { UUID } from './global/scalars/UUID';

export class DB {
    private readonly DBDATA = {
        user: 'root',
        pwd: 'TurnenAufZeit',
        host: 'localhost',
        port: 27017,
        source: 'admin',
        db: 'TurnenAufZeit',
    };

    private mongo: MongoClient;
    private db: MongoClient.Db;

    public getDB(): MongoClient.Db {
        return this.db
    }

    public async connect(): MongoClient.Db {
        this.mongo = await new Promise((resolve, reject) => {
            MongoClient.connect(`mongodb://${this.DBDATA.user}:${this.DBDATA.pwd}@${this.DBDATA.host}:${this.DBDATA.port}`, {
                authSource: this.DBDATA.source,
                useNewUrlParser: true,
                useUnifiedTopology: true,
                poolSize: 20,
            }, (err, database) => {
                if (err) {
                    reject(err);
                }
                resolve(database);
            });
        });

        this.db = this.mongo.db(this.DBDATA.db);
    }

    public collection(ops): MongoClient.Collection {
        return this.db.collection(ops);
    }

    public async fetch(collection: string, filter: any = {}, limit?: number, offset?: number): Promise<any[]> {
        if (!limit || limit > 1000) limit = 1001;
        if (limit < 0) limit = -limit;

        if (offset) {
            return new Promise<any[]>((resolve, reject) => this.db.collection(collection).find(filter).skip(offset).limit(limit).toArray((e, d) => {
                if (e) {
                    return reject(e);
                }
                resolve(d);
            }))
        } else {
            return new Promise<any[]>((resolve, reject) => this.db.collection(collection).find(filter).limit(limit).toArray((e, d) => {
                if (e) {
                    return reject(e);
                }
                resolve(d);
            }))
        }
    }

    public async fetchAll(collection: string): Promise<any[]> {
        return new Promise<any[]>((resolve, reject) => this.db.collection(collection).find(true).toArray((e, d) => {
            if (e) {
                return reject(e);
            }
            resolve(d);
        }))
    }

    public async insert(collection: string, doc: any, client?: Client): Promise<any> {
        const d = await this.db.collection(collection).insertOne({ ...doc, _v: 1, _m: { ctime: Date.now(), mtime: Date.now() } })

        if (d.insertedCount !== 1) { return null; }

        const insertOp = {
            id: doc._id,
            create: { ...doc },
            user: client?.getUser()?._id,
            ts: Date.now(),
        }

        delete insertOp.create._id;
        delete insertOp.create._v;

        await this.db.collection(`o_${collection}`).insertOne(insertOp);

        return d?.ops?.[0];
    }

    public async update(collection: string, doc: any, client?: Client): Promise<any> {
        const old = await this.db.collection(collection).findOne({ _id: doc._id });
        if (!old) return null;

        const neu = { ...old, ...doc, _m: { ...old?._m, mtime: Date.now() } };
        const d = await this.db.collection(collection).replaceOne({_id: doc._id}, neu);

        if (d.modifiedCount !== 1 || !client) { return null; }

        const insertOp = {
            id: doc._id,
            old: { ...old },
            update: { ...doc },
            user: client?.getUser()?._id,
            ts: Date.now(),
        }

        delete insertOp.update._id;
        delete insertOp.update._v;

        await this.db.collection(`o_${collection}`).insertOne(insertOp);

        return d?.ops?.[0];
    }

    public async delete(collection: string, id: UUID, client?: Client): Promise<UUID> {
        const old = await this.db.collection(collection).findOne({ _id: id });
        if (!old) { return null; }

        const del = await this.db.collection(collection).deleteOne({ _id: id });
        if (del.deletedCount !== 1) { return null; }

        const insertOp = {
            id: old._id,
            delete: { ...old },
            user: client?.getUser()?._id,
            ts: Date.now(),
        }

        delete insertOp.delete._id;
        delete insertOp.delete._v;

        await this.db.collection(`o_${collection}`).insertOne(insertOp);

        return id;
    }

    public async doOps(collection: string, id: UUID, ops: any, filter: any, client?: Client): Promise<any> {
        const doc: any = await this.fetch(collection, { _id: id });
        if (!doc) return null;

        await db.collection(collection).updateOne({_id: id}, ops, filter);

        const neu = await this.fetch(collection, { _id: id });

        const insertOp = {
            id: doc._id,
            old: { ...doc },
            ops: JSON.stringify(ops),
            filter: JSON.stringify(filter),
            user: client?.getUser()?._id,
            ts: Date.now(),
        }

        this.db.collection(`o_${collection}`).insertOne(insertOp);

        return neu[0];
    }
}

export const db = new DB();