const { END, EventStoreDBClient, eventTypeFilter, excludeSystemEvents, FORWARDS, PARK, persistentSubscriptionToAllSettingsFromDefaults, START, streamNameFilter, StreamNotFoundError } = require("@eventstore/db-client");
const event = require('../entity/event').Event;
const Logger = require('../../../utils/logger').Logger;
let logger = new Logger(__filename);

const config = require(`../../../config/config.default.js`).config;

class StreamClient {
    // options for appendToStream
    streamOptions = new Map();
    // persist subscription settings
    persistentSettings = new Map();

    filter = new Map();

    constructor(username, password, ENDPOINT) {
        try {
            this.client = EventStoreDBClient.connectionString(config.event_store.connectUrl);
        } catch (error) {
            logger.error(error);
        }

    }

    // create event
    createEvent(type, data, extra = {}) {
        const eventObj = new event(type, data, extra);
        return eventObj.send();
    }

    // append stream 
    async appendToStream(streamName, event) {
        return await this.client.appendToStream(streamName, event);
    }

    /**
     * expectedRevision: 
     *   1. Any,
     *   2. NoStream
     *   3. StreamExists
     * @param {*} state 
     */
    setExpectedRevison(revision) {
        this.streamOptions.set('expectedRevision', revision);
    }

    /**
     * fromRevision:
     *  1. START
     *  2. BigInt(10)
     *  3. END
     * @param {*} revision 
     */
    setFromRevision(revision = START) {
        this.streamOptions.set('fromRevision', revision);
    }

    /**
     * direction:
     *  1. FORWARDS
     *  2. BACKWARDS
     * @param {*} direction 
     */
    setDirection(direction = FORWARDS) {
        this.streamOptions.set('direction', direction);
    }

    setCredentials(username, password) {
        this.streamOptions.set('credentials', { username, password });
    }

    setMaxCount(maxcount = 10) {
        this.streamOptions.set('maxCount', maxcount);
    }

    setStreamNameFilter(filterArr) {
        this.streamOptions.set('filter', streamNameFilter({
            prefixes: filterArr
        }))
    }

    setStreamExcludeSystemEvents() {
        this.streamOptions.set('filter', excludeSystemEvents())
    }

    setEventTypeFilter(ruleStr) {
        this.streamOptions.set('filter', eventTypeFilter({
            regex: ruleStr
        }))
    }

    // read stream
    async readStream(streamName, options) {
        try {
            let result = [];
            const events = this.client.readStream(streamName, options);

            for await (const resolvedEvent of events) {
                result.push(resolvedEvent.event?.data);
            }

            return { 'code': 200, "result": result };
        } catch (error) {
            if (error instanceof StreamNotFoundError) {
                return { 'code': 400, 'message': "stream not exists " + streamName };
            }
        }
    }

    // read all stream
    async readAllStream(options) {
        let results = [];
        const events = this.client.readAll(options);
        for await (const resolvedEvent of events) {
            // filter system event
            if (resolvedEvent.event?.type.startsWith("$")) {
                continue;
            }
            console.log('resolvedEvent: ', resolvedEvent);
            console.log(resolvedEvent.event?.data);
            results.push(resolvedEvent.event?.data);
        }
        return results;
    }

    /**
     * subscribe stream
     * streamOptions = {
     *  fromRevision: Start | End | a big int (unsigned 64 bit integer) position,
     * }
     * live update 
     * streamOptions = {
     *  fromRevision: END,
     * }
     * 
     * */
    async subscribe(streamName, streamOptions) {
        return this.client.subscribeToStream(streamName, streamOptions)
    }

    /**
     * subscribe all stream
     * streamOptions = {
     *  commit: Start | End | Position BigInt(1056)
     *  prepare: Start | End | Position BigInt(1056),
     * }
     * 
     * live update 
     * streamOptions = {
     *  fromRevision: END,
     * }
     * 
     * 
     * @param {*} callback 
     */
    async subscribeAll() {
        try {
            let checkpoint = START;
            const subscription = this.client.subscribeToAll({ fromPosition: checkpoint }).on('data', (resolvedEvent) => {
                checkpoint = resolvedEvent.event?.revision ?? checkpoint;
            });

            for await (const resolvedEvent of subscription) {
                console.log(
                    `Received event ${resolvedEvent.event?.revision}@${resolvedEvent.event?.streamId}`
                );
                return resolvedEvent;
            }
        } catch (error) {
            throw error;
        }
    }
    async subscribeAllStream(groupName, callback) {
        return this.client.subscribeToAll(groupName);

    }

    /**
     * streamOptions = {
     *  fromRevision : START,
     *  resolveLinkTos : true
     * }
     * 
     * @param {*} eventType 
     * @returns 
     */
    subscribeLinkTo(eventType) {
        const subscriptions = this.client.subscribeToStream("$et-" + eventType, this.streamOptions);
        return subscriptions;
    }

    createPersistentSubscriptionToStream(streamName, groupName) {
        return this.client.createPersistentSubscriptionToStream(
            streamName,
            groupName,
            persistentSubscriptionToAllSettingsFromDefaults(),
            this.streamOptions
        )
    }

    /**
     * nak = Unknown | Park | Retry | Skip
     * 
     * @param {*} streamName 
     * @param {*} groupName 
     */
    async subscribeGroup(streamName, groupName, nakAction = 'PARK') {
        const subscription = this.client.subscribeToPersistentSubscriptionToStream(
            streamName,
            groupName
        );
        try {
            for await (const event of subscription) {
                try {
                    //@TODO deal with the logic
                    await subscription.ack(event);
                } catch (error) {
                    await subscription.nack(nakAction, error.toString(), event);
                }
            }
        } catch (error) {
            console.log(`Subscription was dropped. ${error}`);
        }
    }

    async subscribeAll(groupName, callback) {
        const subscription = this.client.subscribeToPersistentSubscriptionToAll(groupName)
        try {
            for await (const event of subscription) {
                callback();
                await subscription.ack(event);
            }
        } catch (error) {
            console.log(`Subscription was dropped. ${error}`);
        }
    }

    setFilter(filterType, filterArr) {
        switch (filterType) {
            case 'streamName':
                this.filter.set('filter', streamNameFilter({ prefixes: filterArr }));
            case 'eventType':
                this.filter.set('filter', eventTypeFilter({}));
        }
    }

    async updateSubscriptionGroup(streamName, groupName) {
        return await this.client.updatePersistentSubscriptionToStream(
            streamName,
            groupName,
            persistentSubscriptionToAllSettingsFromDefaults(this.persistentSettings)
        )
    }

    async deleteSubscriptionGroup(streamName, groupName) {
        return await this.client.deletePersistentSubscriptionToStream(streamName, groupName);
    }

    async subscribeToPersistentSubscriptionToStream(streamName, groupName) {
        const subscriptoin = this.client.subscribeToPersistentSubscriptionToAll(streamName, groupName);
        try {
            const events = [];
            for await (const event of subscriptoin) {
                try {
                    events.push(event);
                    await subscriptoin.ack(event);
                } catch (error) {
                    await subscriptoin.nack(PARK, error.toString(), event);
                }
            }
            return events;
        } catch (error) {
            console.log(`Subscription was dropped. ${error}`);
        }
    }

    async createPersistentSubscriptionToAll(groupName) {
        return await this.client.createPersistentSubscriptionToAll(
            groupName,
            persistentSubscriptionToAllSettingsFromDefaults(),
            {
                filter: streamNameFilter({ prefixed: ["test"] }),
                credentials: {
                    username: "admin",
                    password: "changeit"
                }
            }
        )
    }

    async deleteSubscriptionGroup(streamName, groupName) {
        return await this.client.deletePersistentSubscriptionToStream(streamName, groupName);
    }
}


exports.StreamClient = StreamClient;