"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const datasource_1 = require("../definitions/datasource");
const google_auth_library_1 = require("google-auth-library");
const utils_1 = require("./utils");
const constants_1 = require("../constants");
const google_spreadsheet_1 = require("google-spreadsheet");
const environment_1 = __importDefault(require("../environment"));
var GoogleSheetsModule;
(function (GoogleSheetsModule) {
    const { getGlobalDB } = require("@devlego/backend-core/tenancy");
    const { getScopedConfig } = require("@devlego/backend-core/db");
    const { Configs } = require("@devlego/backend-core/constants");
    const fetch = require("node-fetch");
    const SCHEMA = {
        plus: true,
        auth: {
            type: "google",
        },
        relationships: false,
        docs: "https://developers.google.com/sheets/api/quickstart/nodejs",
        description: "Create and collaborate on online spreadsheets in real-time and from any device. ",
        friendlyName: "Google Sheets",
        type: "Spreadsheet",
        datasource: {
            spreadsheetId: {
                display: "Google Sheet URL",
                type: datasource_1.DatasourceFieldTypes.STRING,
                required: true,
            },
        },
        query: {
            create: {
                type: datasource_1.QueryTypes.FIELDS,
                fields: {
                    sheet: {
                        type: datasource_1.DatasourceFieldTypes.STRING,
                        required: true,
                    },
                    row: {
                        type: datasource_1.QueryTypes.JSON,
                        required: true,
                    },
                },
            },
            read: {
                type: datasource_1.QueryTypes.FIELDS,
                fields: {
                    sheet: {
                        type: datasource_1.DatasourceFieldTypes.STRING,
                        required: true,
                    },
                },
            },
            update: {
                type: datasource_1.QueryTypes.FIELDS,
                fields: {
                    sheet: {
                        type: datasource_1.DatasourceFieldTypes.STRING,
                        required: true,
                    },
                    rowIndex: {
                        type: datasource_1.DatasourceFieldTypes.STRING,
                        required: true,
                    },
                    row: {
                        type: datasource_1.QueryTypes.JSON,
                        required: true,
                    },
                },
            },
            delete: {
                type: datasource_1.QueryTypes.FIELDS,
                fields: {
                    sheet: {
                        type: datasource_1.DatasourceFieldTypes.STRING,
                        required: true,
                    },
                    rowIndex: {
                        type: datasource_1.DatasourceFieldTypes.NUMBER,
                        required: true,
                    },
                },
            },
        },
    };
    class GoogleSheetsIntegration {
        constructor(config) {
            this.tables = {};
            this.schemaErrors = {};
            this.config = config;
            const spreadsheetId = this.cleanSpreadsheetUrl(this.config.spreadsheetId);
            this.client = new google_spreadsheet_1.GoogleSpreadsheet(spreadsheetId);
        }
        getBindingIdentifier() {
            return "";
        }
        getStringConcat(parts) {
            return "";
        }
        /**
         * Pull the spreadsheet ID out from a valid google sheets URL
         * @param spreadsheetId - the URL or standard spreadsheetId of the google sheet
         * @returns spreadsheet Id of the google sheet
         */
        cleanSpreadsheetUrl(spreadsheetId) {
            if (!spreadsheetId) {
                throw new Error("You must set a spreadsheet ID in your configuration to fetch tables.");
            }
            const parts = spreadsheetId.split("/");
            return parts.length > 5 ? parts[5] : spreadsheetId;
        }
        fetchAccessToken(payload) {
            return __awaiter(this, void 0, void 0, function* () {
                const response = yield fetch("https://www.googleapis.com/oauth2/v4/token", {
                    method: "POST",
                    body: JSON.stringify(Object.assign(Object.assign({}, payload), { grant_type: "refresh_token" })),
                    headers: {
                        "Content-Type": "application/json",
                    },
                });
                const json = yield response.json();
                if (response.status !== 200) {
                    throw new Error(`Error authenticating with google sheets. ${json.error_description}`);
                }
                return json;
            });
        }
        connect() {
            return __awaiter(this, void 0, void 0, function* () {
                try {
                    // Initialise oAuth client
                    const db = getGlobalDB();
                    let googleConfig = yield getScopedConfig(db, {
                        type: Configs.GOOGLE,
                    });
                    if (!googleConfig) {
                        googleConfig = {
                            clientID: environment_1.default.GOOGLE_CLIENT_ID,
                            clientSecret: environment_1.default.GOOGLE_CLIENT_SECRET,
                        };
                    }
                    const oauthClient = new google_auth_library_1.OAuth2Client({
                        clientId: googleConfig.clientID,
                        clientSecret: googleConfig.clientSecret,
                    });
                    const tokenResponse = yield this.fetchAccessToken({
                        client_id: googleConfig.clientID,
                        client_secret: googleConfig.clientSecret,
                        refresh_token: this.config.auth.refreshToken,
                    });
                    oauthClient.setCredentials({
                        refresh_token: this.config.auth.refreshToken,
                        access_token: tokenResponse.access_token,
                    });
                    this.client.useOAuth2Client(oauthClient);
                    yield this.client.loadInfo();
                }
                catch (err) {
                    console.error("Error connecting to google sheets", err);
                    throw err;
                }
            });
        }
        buildSchema(datasourceId) {
            return __awaiter(this, void 0, void 0, function* () {
                yield this.connect();
                const sheets = yield this.client.sheetsByIndex;
                const tables = {};
                for (let sheet of sheets) {
                    // must fetch rows to determine schema
                    yield sheet.getRows();
                    // build schema
                    const schema = {};
                    // build schema from headers
                    for (let header of sheet.headerValues) {
                        schema[header] = {
                            name: header,
                            type: constants_1.FieldTypes.STRING,
                        };
                    }
                    // create tables
                    tables[sheet.title] = {
                        _id: (0, utils_1.buildExternalTableId)(datasourceId, sheet.title),
                        name: sheet.title,
                        primary: ["rowNumber"],
                        schema,
                    };
                }
                this.tables = tables;
            });
        }
        query(json) {
            return __awaiter(this, void 0, void 0, function* () {
                const sheet = json.endpoint.entityId;
                const handlers = {
                    [constants_1.DataSourceOperation.CREATE]: () => this.create({ sheet, row: json.body }),
                    [constants_1.DataSourceOperation.READ]: () => this.read({ sheet }),
                    [constants_1.DataSourceOperation.UPDATE]: () => {
                        var _a, _b, _c;
                        return this.update({
                            // exclude the header row and zero index
                            rowIndex: ((_c = (_b = (_a = json.extra) === null || _a === void 0 ? void 0 : _a.idFilter) === null || _b === void 0 ? void 0 : _b.equal) === null || _c === void 0 ? void 0 : _c.rowNumber) - 2,
                            sheet,
                            row: json.body,
                        });
                    },
                    [constants_1.DataSourceOperation.DELETE]: () => {
                        var _a, _b, _c;
                        return this.delete({
                            // exclude the header row and zero index
                            rowIndex: ((_c = (_b = (_a = json.extra) === null || _a === void 0 ? void 0 : _a.idFilter) === null || _b === void 0 ? void 0 : _b.equal) === null || _c === void 0 ? void 0 : _c.rowNumber) - 2,
                            sheet,
                        });
                    },
                    [constants_1.DataSourceOperation.CREATE_TABLE]: () => { var _a; return this.createTable((_a = json === null || json === void 0 ? void 0 : json.table) === null || _a === void 0 ? void 0 : _a.name); },
                    [constants_1.DataSourceOperation.UPDATE_TABLE]: () => this.updateTable(json.table),
                    [constants_1.DataSourceOperation.DELETE_TABLE]: () => { var _a; return this.deleteTable((_a = json === null || json === void 0 ? void 0 : json.table) === null || _a === void 0 ? void 0 : _a.name); },
                };
                const internalQueryMethod = handlers[json.endpoint.operation];
                return yield internalQueryMethod();
            });
        }
        buildRowObject(headers, values, rowNumber) {
            const rowObject = { rowNumber };
            for (let i = 0; i < headers.length; i++) {
                rowObject._id = rowNumber;
                rowObject[headers[i]] = values[i];
            }
            return rowObject;
        }
        createTable(name) {
            return __awaiter(this, void 0, void 0, function* () {
                try {
                    yield this.connect();
                    const sheet = yield this.client.addSheet({ title: name });
                    return sheet;
                }
                catch (err) {
                    console.error("Error creating new table in google sheets", err);
                    throw err;
                }
            });
        }
        updateTable(table) {
            return __awaiter(this, void 0, void 0, function* () {
                try {
                    yield this.connect();
                    const sheet = yield this.client.sheetsByTitle[table.name];
                    yield sheet.loadHeaderRow();
                    if (table._rename) {
                        const headers = [];
                        for (let header of sheet.headerValues) {
                            if (header === table._rename.old) {
                                headers.push(table._rename.updated);
                            }
                            else {
                                headers.push(header);
                            }
                        }
                        yield sheet.setHeaderRow(headers);
                    }
                    else {
                        let newField = Object.keys(table.schema).find(key => !sheet.headerValues.includes(key));
                        yield sheet.setHeaderRow([...sheet.headerValues, newField]);
                    }
                }
                catch (err) {
                    console.error("Error updating table in google sheets", err);
                    throw err;
                }
            });
        }
        deleteTable(sheet) {
            return __awaiter(this, void 0, void 0, function* () {
                try {
                    yield this.connect();
                    const sheetToDelete = yield this.client.sheetsByTitle[sheet];
                    return yield sheetToDelete.delete();
                }
                catch (err) {
                    console.error("Error deleting table in google sheets", err);
                    throw err;
                }
            });
        }
        create(query) {
            return __awaiter(this, void 0, void 0, function* () {
                try {
                    yield this.connect();
                    const sheet = yield this.client.sheetsByTitle[query.sheet];
                    const rowToInsert = typeof query.row === "string" ? JSON.parse(query.row) : query.row;
                    const row = yield sheet.addRow(rowToInsert);
                    return [
                        this.buildRowObject(sheet.headerValues, row._rawData, row._rowNumber),
                    ];
                }
                catch (err) {
                    console.error("Error writing to google sheets", err);
                    throw err;
                }
            });
        }
        read(query) {
            return __awaiter(this, void 0, void 0, function* () {
                try {
                    yield this.connect();
                    const sheet = yield this.client.sheetsByTitle[query.sheet];
                    const rows = yield sheet.getRows();
                    const headerValues = sheet.headerValues;
                    const response = [];
                    for (let row of rows) {
                        response.push(this.buildRowObject(headerValues, row._rawData, row._rowNumber));
                    }
                    return response;
                }
                catch (err) {
                    console.error("Error reading from google sheets", err);
                    throw err;
                }
            });
        }
        update(query) {
            return __awaiter(this, void 0, void 0, function* () {
                try {
                    yield this.connect();
                    const sheet = yield this.client.sheetsByTitle[query.sheet];
                    const rows = yield sheet.getRows();
                    const row = rows[query.rowIndex];
                    if (row) {
                        const updateValues = query.row;
                        for (let key in updateValues) {
                            row[key] = updateValues[key];
                        }
                        yield row.save();
                        return [
                            this.buildRowObject(sheet.headerValues, row._rawData, row._rowNumber),
                        ];
                    }
                    else {
                        throw new Error("Row does not exist.");
                    }
                }
                catch (err) {
                    console.error("Error reading from google sheets", err);
                    throw err;
                }
            });
        }
        delete(query) {
            return __awaiter(this, void 0, void 0, function* () {
                yield this.connect();
                const sheet = yield this.client.sheetsByTitle[query.sheet];
                const rows = yield sheet.getRows();
                const row = rows[query.rowIndex];
                if (row) {
                    yield row.delete();
                    return [{ deleted: query.rowIndex }];
                }
                else {
                    throw new Error("Row does not exist.");
                }
            });
        }
    }
    module.exports = {
        schema: SCHEMA,
        integration: GoogleSheetsIntegration,
    };
})(GoogleSheetsModule || (GoogleSheetsModule = {}));
