const { FEATURE_TAGGED, FEATURE_UNTAGGED } = require('../event-type');
const { featureSchema, nameSchema, querySchema } = require('./feature-schema');
const { tagSchema } = require('./tag-schema');
const NameExistsError = require('../error/name-exists-error');
const NotFoundError = require('../error/notfound-error');
const {
    FEATURE_ARCHIVED,
    FEATURE_CREATED,
    FEATURE_REVIVED,
    FEATURE_UPDATED,
    FEATURE_STALE_ON,
    FEATURE_STALE_OFF,
    TAG_CREATED,
} = require('../event-type');

class FeatureToggleService {
    constructor({ featureToggleStore, tagStore, eventStore }, { getLogger }) {
        this.featureToggleStore = featureToggleStore;
        this.tagStore = tagStore;
        this.eventStore = eventStore;
        this.logger = getLogger('services/feature-toggle-service.js');
    }

    async getFeatures(query, fields) {
        const preppedQuery = await this.prepQuery(query);
        return this.featureToggleStore.getFeatures(preppedQuery, fields);
    }

    paramToArray(param) {
        if (!param) {
            return param;
        }
        return Array.isArray(param) ? param : [param];
    }

    async prepQuery({ tag, project, namePrefix }) {
        if (!tag && !project && !namePrefix) {
            return null;
        }
        const tagQuery = this.paramToArray(tag);
        const projectQuery = this.paramToArray(project);
        const query = await querySchema.validateAsync({
            tag: tagQuery,
            project: projectQuery,
            namePrefix,
        });
        if (query.tag) {
            query.tag = query.tag.map(q => q.split(':'));
        }
        return query;
    }

    async getArchivedFeatures() {
        return this.featureToggleStore.getArchivedFeatures();
    }

    async addArchivedFeature(feature) {
        await this.featureToggleStore.addArchivedFeature(feature);
    }

    async getFeature(name) {
        return this.featureToggleStore.getFeature(name);
    }

    async createFeatureToggle(value, userName) {
        await this.validateName(value);
        const featureData = await featureSchema.validateAsync(value);
        const feature = await this.featureToggleStore.createFeature(
            featureData,
        );
        await this.eventStore.store({
            type: FEATURE_CREATED,
            createdBy: userName,
            data: featureData,
        });
        return feature;
    }

    async updateToggle(updatedFeature, userName) {
        await this.featureToggleStore.getFeature(updatedFeature.name);
        const value = await featureSchema.validateAsync(updatedFeature);
        await this.featureToggleStore.updateFeature(value);
        const tags =
            (await this.featureToggleStore.getAllTagsForFeature(
                updatedFeature.name,
            )) || [];
        await this.eventStore.store({
            type: FEATURE_UPDATED,
            createdBy: userName,
            data: updatedFeature,
            tags,
        });
    }

    async archiveToggle(name, userName) {
        await this.featureToggleStore.getFeature(name);
        await this.featureToggleStore.archiveFeature(name);
        const tags =
            (await this.featureToggleStore.getAllTagsForFeature(name)) || [];
        await this.eventStore.store({
            type: FEATURE_ARCHIVED,
            createdBy: userName,
            data: { name },
            tags,
        });
    }

    async reviveToggle(name, userName) {
        await this.featureToggleStore.reviveFeature({ name });
        const tags =
            (await this.featureToggleStore.getAllTagsForFeature(name)) || [];

        await this.eventStore.store({
            type: FEATURE_REVIVED,
            createdBy: userName,
            data: { name },
            tags,
        });
    }

    async toggle(featureName, userName) {
        const feature = await this.featureToggleStore.getFeature(featureName);
        const toggle = !feature.enabled;
        return this.updateField(feature.name, 'enabled', toggle, userName);
    }

    /** Tag related  */
    async listTags(featureName) {
        return this.featureToggleStore.getAllTagsForFeature(featureName);
    }

    async addTag(featureName, tag, userName) {
        await nameSchema.validateAsync({ name: featureName });
        const validatedTag = await tagSchema.validateAsync(tag);
        await this.createTagIfNeeded(validatedTag, userName);
        await this.featureToggleStore.tagFeature(featureName, validatedTag);
        await this.eventStore.store({
            type: FEATURE_TAGGED,
            createdBy: userName,
            data: {
                featureName,
                tag: validatedTag,
            },
        });
        return validatedTag;
    }

    async createTagIfNeeded(tag, userName) {
        try {
            await this.tagStore.getTag(tag.type, tag.value);
        } catch (error) {
            if (error instanceof NotFoundError) {
                await this.tagStore.createTag(tag);
                await this.eventStore.store({
                    type: TAG_CREATED,
                    createdBy: userName,
                    data: {
                        tag,
                    },
                });
            }
        }
    }

    async removeTag(featureName, tag, userName) {
        await this.featureToggleStore.untagFeature(featureName, tag);
        await this.eventStore.store({
            type: FEATURE_UNTAGGED,
            createdBy: userName,
            data: {
                featureName,
                tag,
            },
        });
    }

    /** Validations  */
    async validateName({ name }) {
        await nameSchema.validateAsync({ name });
        await this.validateUniqueFeatureName(name);
        return name;
    }

    async validateUniqueFeatureName(name) {
        let msg;
        try {
            const feature = await this.featureToggleStore.hasFeature(name);
            msg = feature.archived
                ? 'An archived toggle with that name already exists'
                : 'A toggle with that name already exists';
        } catch (error) {
            return;
        }
        throw new NameExistsError(msg);
    }

    async updateField(featureName, field, value, userName) {
        const feature = await this.featureToggleStore.getFeature(featureName);
        feature[field] = value;
        await this.featureToggleStore.updateFeature(feature);
        const tags =
            (await this.featureToggleStore.getAllTagsForFeature(featureName)) ||
            [];

        await this.eventStore.store({
            type: FEATURE_UPDATED,
            createdBy: userName,
            data: feature,
            tags,
        });
        return feature;
    }

    async updateStale(featureName, isStale, userName) {
        const feature = await this.featureToggleStore.getFeature(featureName);
        feature.stale = isStale;
        await this.featureToggleStore.updateFeature(feature);
        const tags =
            (await this.featureToggleStore.getAllTagsForFeature(featureName)) ||
            [];

        await this.eventStore.store({
            type: isStale ? FEATURE_STALE_ON : FEATURE_STALE_OFF,
            createdBy: userName,
            data: feature,
            tags,
        });
        return feature;
    }
}

module.exports = FeatureToggleService;
