// (C) 2007-2020 GoodData Corporation
import { getIn, handlePolling, getAllPagesByOffsetLimit } from "./util";
import { ITimezone, IColor, IColorPalette, IFeatureFlags } from "./interfaces";
import { IStyleSettingsResponse, IFeatureFlagsResponse } from "./apiResponsesInterfaces";
import { XhrModule, ApiResponse } from "./xhr";

const DEFAULT_PALETTE = [
    { r: 0x2b, g: 0x6b, b: 0xae },
    { r: 0x69, g: 0xaa, b: 0x51 },
    { r: 0xee, g: 0xb1, b: 0x4c },
    { r: 0xd5, g: 0x3c, b: 0x38 },
    { r: 0x89, g: 0x4d, b: 0x94 },
    { r: 0x73, g: 0x73, b: 0x73 },
    { r: 0x44, g: 0xa9, b: 0xbe },
    { r: 0x96, g: 0xbd, b: 0x5f },
    { r: 0xfd, g: 0x93, b: 0x69 },
    { r: 0xe1, g: 0x5d, b: 0x86 },
    { r: 0x7c, g: 0x6f, b: 0xad },
    { r: 0xa5, g: 0xa5, b: 0xa5 },
    { r: 0x7a, g: 0xa6, b: 0xd5 },
    { r: 0x82, g: 0xd0, b: 0x8d },
    { r: 0xff, g: 0xd2, b: 0x89 },
    { r: 0xf1, g: 0x84, b: 0x80 },
    { r: 0xbf, g: 0x90, b: 0xc6 },
    { r: 0xbf, g: 0xbf, b: 0xbf },
];

const isProjectCreated = (project: any) => {
    // TODO
    const projectState = project.content.state;

    return projectState === "ENABLED" || projectState === "DELETED";
};

export interface IProjectConfigSettingItem {
    settingItem: {
        key: string;
        links: {
            self: string;
        };
        source: string;
        value: string;
    };
}
export interface IProjectConfigResponse {
    settings: {
        items: IProjectConfigSettingItem[];
    };
}

// Parses string values to boolean, number and string
export const parseSettingItemValue = (value: string): boolean | number | string => {
    if (value === "true") {
        return true;
    }
    if (value === "false") {
        return false;
    }
    const nr = Number(value);
    if (nr.toString() === value) {
        return nr;
    }
    return value;
};

/**
 * Functions for working with projects
 *
 * @class project
 * @module project
 */
export class ProjectModule {
    constructor(private xhr: XhrModule) {}

    /**
     * Get current project id
     *
     * @method getCurrentProjectId
     * @return {String} current project identifier
     */
    public getCurrentProjectId() {
        return this.xhr
            .get("/gdc/app/account/bootstrap")
            .then(r => r.getData())
            .then(this.getCurrentProjectIdInBootstrap);
    }

    /**
     * Return current project id in bootstrap
     * @method getCurrentProjectIdInBootstrap
     * @param bootstrapData - data was got from bootstrap resource
     */
    public getCurrentProjectIdInBootstrap(bootstrapData: any): string | null {
        const currentProject = bootstrapData.bootstrapResource.current.project;
        // handle situation in which current project is missing (e.g. new user)
        if (!currentProject) {
            return null;
        }

        return bootstrapData.bootstrapResource.current.project.links.self.split("/").pop();
    }

    /**
     * Fetches projects available for the user represented by the given profileId
     *
     * @method getProjects
     * @param {String} profileId - User profile identifier
     * @return {Array} An Array of projects
     */
    public getProjects(profileId: string) {
        return getAllPagesByOffsetLimit(
            this.xhr,
            `/gdc/account/profile/${profileId}/projects`,
            "projects",
        ).then((result: any) => result.map((p: any) => p.project));
    }

    /**
     * Fetches all datasets for the given project
     *
     * @method getDatasets
     * @param {String} projectId - GD project identifier
     * @return {Array} An array of objects containing datasets metadata
     */
    public getDatasets(projectId: string) {
        return this.xhr
            .get(`/gdc/md/${projectId}/query/datasets`)
            .then(r => r.getData())
            .then(getIn("query.entries"));
    }

    /**
     * Fetches a chart color palette for a project represented by the given
     * projectId parameter.
     *
     * @method getColorPalette
     * @param {String} projectId - A project identifier
     * @return {Array} An array of objects with r, g, b fields representing a project's
     * color palette
     */
    public getColorPalette(projectId: string) {
        return this.xhr
            .get(`/gdc/projects/${projectId}/styleSettings`)
            .then(r => r.getData())
            .then(
                (result: any) => {
                    return result.styleSettings.chartPalette.map((c: any) => {
                        return {
                            r: c.fill.r,
                            g: c.fill.g,
                            b: c.fill.b,
                        };
                    });
                },
                err => {
                    if (err.status === 200) {
                        return DEFAULT_PALETTE;
                    }

                    throw new Error(err.statusText);
                },
            );
    }

    /**
     * Fetches a chart color palette for a project represented by the given
     * projectId parameter.
     *
     * @method getColorPaletteWithGuids
     * @param {String} projectId - A project identifier
     * @return {Array} An array of objects representing a project's
     * color palette with color guid or undefined
     */
    public getColorPaletteWithGuids(projectId: string): Promise<IColorPalette | undefined> {
        return this.xhr
            .get(`/gdc/projects/${projectId}/styleSettings`)
            .then((apiResponse: ApiResponse) => {
                return apiResponse.getData();
            })
            .then((result: IStyleSettingsResponse) => {
                if (result && result.styleSettings) {
                    return result.styleSettings.chartPalette;
                } else {
                    return undefined;
                }
            });
    }

    /**
     * Sets given colors as a color palette for a given project.
     *
     * @method setColorPalette
     * @param {String} projectId - GD project identifier
     * @param {Array} colors - An array of colors that we want to use within the project.
     * Each color should be an object with r, g, b fields. // TODO really object?
     */
    public setColorPalette(projectId: string, colors: IColor[]): Promise<ApiResponse> {
        return this.xhr.put(`/gdc/projects/${projectId}/styleSettings`, {
            body: {
                styleSettings: {
                    chartPalette: colors.map((fill, idx: number) => {
                        return { fill, guid: `guid${idx}` };
                    }),
                },
            },
        });
    }

    /**
     * Gets current timezone and its offset. Example output:
     *
     *     {
     *         id: 'Europe/Prague',
     *         displayName: 'Central European Time',
     *         currentOffsetMs: 3600000
     *     }
     *
     * @method getTimezone
     * @param {String} projectId - GD project identifier
     */
    public getTimezone(projectId: string): Promise<ITimezone> {
        const bootstrapUrl = `/gdc/app/account/bootstrap?projectId=${projectId}`;

        return this.xhr
            .get(bootstrapUrl)
            .then(r => r.getData())
            .then((result: any) => {
                return result.bootstrapResource.current.timezone;
            });
    }

    public setTimezone(projectId: string, timezone: ITimezone) {
        const timezoneServiceUrl = `/gdc/md/${projectId}/service/timezone`;
        const data = {
            service: { timezone },
        };

        return this.xhr
            .post(timezoneServiceUrl, {
                body: data,
            })
            .then(r => r.getData());
    }

    /**
     * Create project
     * Note: returns a promise which is resolved when the project creation is finished
     *
     * @experimental
     * @method createProject
     * @param {String} title
     * @param {String} authorizationToken
     * @param {Object} options for project creation (summary, projectTemplate, ...)
     * @return {Object} created project object
     */
    public createProject(title: string, authorizationToken: string, options: any = {}) {
        const {
            summary,
            projectTemplate,
            driver = "Pg",
            environment = "TESTING",
            guidedNavigation = 1,
        } = options;

        return this.xhr
            .post("/gdc/projects", {
                body: JSON.stringify({
                    project: {
                        content: {
                            guidedNavigation,
                            driver,
                            authorizationToken,
                            environment,
                        },
                        meta: {
                            title,
                            summary,
                            projectTemplate,
                        },
                    },
                }),
            })
            .then(r => r.getData())
            .then((project: any) =>
                handlePolling(
                    this.xhr.get.bind(this.xhr),
                    project.uri,
                    (response: any) => {
                        // TODO project response
                        return isProjectCreated(response.project);
                    },
                    options,
                ),
            );
    }

    /**
     * Delete project
     *
     * @method deleteProject
     * @param {String} projectId
     */
    public deleteProject(projectId: string) {
        return this.xhr.del(`/gdc/projects/${projectId}`);
    }

    /**
     * Gets aggregated feature flags for given project and current user
     *
     * @method getFeatureFlags
     * @param {String} projectId - A project identifier
     * @return {IFeatureFlags} Hash table of feature flags and theirs values where feature flag is as key
     */
    public getFeatureFlags(projectId: string): Promise<IFeatureFlags> {
        return this.xhr
            .get(`/gdc/app/projects/${projectId}/featureFlags`)
            .then((apiResponse: ApiResponse) => {
                return apiResponse.getData();
            })
            .then((result: IFeatureFlagsResponse) => {
                if (result && result.featureFlags) {
                    return result.featureFlags;
                }
                return {};
            });
    }

    /**
     * Gets project config including project specific feature flags
     *
     * @param {String} projectId - A project identifier
     * @return {IProjectConfigSettingItem[]} An array of project config setting items
     */
    public getConfig(projectId: string): Promise<IProjectConfigSettingItem[]> {
        return this.xhr
            .get(`/gdc/app/projects/${projectId}/config`)
            .then((apiResponse: ApiResponse) => {
                const projectConfig = apiResponse.getData();
                return projectConfig;
            })
            .then((result: IProjectConfigResponse) => {
                if (result && result.settings && result.settings.items) {
                    return result.settings.items;
                }
                return [];
            });
    }

    /**
     * Gets project specific feature flags
     *
     * @param {String} projectId - A project identifier
     * @param {String} source - optional filter settingItems with specific source
     * @return {IFeatureFlags} Hash table of feature flags and theirs values where feature flag is as key
     */
    public getProjectFeatureFlags(projectId: string, source?: string): Promise<IFeatureFlags> {
        return this.getConfig(projectId).then((settingItems: IProjectConfigSettingItem[]) => {
            const filteredSettingItems = source
                ? settingItems.filter(settingItem => settingItem.settingItem.source === source)
                : settingItems;
            const featureFlags: IFeatureFlags = {};
            filteredSettingItems.forEach(settingItem => {
                featureFlags[settingItem.settingItem.key] = parseSettingItemValue(
                    settingItem.settingItem.value,
                );
            });
            return featureFlags;
        });
    }
}
