"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());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.supabaseReadTools = exports.supabaseTools = exports.configManager = exports.supabaseConfigManager = void 0;
const zod_1 = require("zod");
// import dotenv, { config } from "dotenv";
// import {parse, SelectStatement, Statement} from 'pgsql-ast-parser';
const utils_1 = require("../utils");
const utils_2 = require("../../../../utils");
const cliconfig_1 = require("../../../../database/cliconfig");
const workspace_1 = require("../../../workspace");
// dotenv.config();
class SupabaseConfigManager {
    constructor() {
        // appId to config map
        this.configMap = new Map();
    }
    fetchSupabaseCredsFromAppIntegrations(appId) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!appId) {
                utils_1.logger.error("SupabaseConfigManager error: no appId");
                throw new Error("App ID is required");
            }
            const openApp = yield (0, cliconfig_1.getOpenApp)(appId);
            if (!openApp) {
                utils_1.logger.error("SupabaseConfigManager error: no opened app");
                throw new Error("Open app not found");
            }
            const header = (0, utils_2.makeHeadersWithCookie)(openApp.apptilecookie, {});
            utils_1.logger.info("SupabaseConfigManager Header created: " + JSON.stringify(header));
            // const apptileConfig = {APPTILE_BACKEND_URL: ""};
            const apptileConfig = yield (0, workspace_1.getAppConfigsFromFS)(appId);
            utils_1.logger.info("SupabaseConfigManager apptileConfig fetched: " + JSON.stringify(apptileConfig));
            process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
            try {
                const response = yield fetch(`${apptileConfig.APPTILE_BACKEND_URL}/${(0, utils_2.getMidUrlIntegrationsValue)()}/${appId}/appIntegrations/supabase/credentials`, {
                    method: "GET",
                    headers: Object.assign({}, header.headers),
                });
                const credsData = yield response.json();
                const creds = {
                    SUPABASE_PROJECT_REF: credsData.SUPABASE_PROJECT_REF,
                    SUPABASE_ANON_KEY: credsData.SUPABASE_ANON_KEY,
                    SUPABASE_ACCESS_TOKEN: credsData.SUPABASE_ACCESS_TOKEN,
                    SUPABASE_REFRESH_TOKEN: credsData.SUPABASE_REFRESH_TOKEN,
                    APPTILE_SUPABASE_CLIENT_ID: credsData.APPTILE_SUPABASE_CLIENT_ID,
                    APPTILE_SUPABASE_CLIENT_SECRET: credsData.APPTILE_SUPABASE_CLIENT_SECRET,
                    ACCESS_TOKEN_EXPIRES_IN: credsData.ACCESS_TOKEN_EXPIRES_IN
                };
                this.configMap.set(appId, { setAt: Date.now(), creds });
            }
            catch (error) {
                utils_1.logger.error("Fetch failed with error: " + (error === null || error === void 0 ? void 0 : error.toString()));
                throw new Error(error);
            }
        });
    }
    updateCredsInAppIntegrations(appId, creds) {
        return __awaiter(this, void 0, void 0, function* () {
            const openApp = yield (0, cliconfig_1.getOpenApp)(appId);
            if (!openApp) {
                throw new Error("Open app not found");
            }
            const header = (0, utils_2.makeHeadersWithCookie)(openApp.apptilecookie, {});
            const apptileConfig = { APPTILE_BACKEND_URL: "" };
            // const apptileConfig = await getAppConfigsFromFS(appId);
            process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
            try {
                yield fetch(`${apptileConfig.APPTILE_BACKEND_URL}/${(0, utils_2.getMidUrlIntegrationsValue)()}/${appId}/appIntegrations/supabase/credentials?isPlatformType=true`, {
                    method: "POST",
                    headers: Object.assign(Object.assign({}, header.headers), { "Content-Type": "application/json" }),
                    body: JSON.stringify(creds),
                });
                utils_1.logger.info("Update creds successful");
            }
            catch (error) {
                console.error("Fetch failed with error: ", error);
                throw new Error(error);
            }
        });
    }
    refreshAccessToken(appId) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a;
            let entry = this.configMap.get(appId);
            if (!entry) {
                yield this.fetchSupabaseCredsFromAppIntegrations(appId);
            }
            entry = this.configMap.get(appId);
            if (!((_a = entry === null || entry === void 0 ? void 0 : entry.creds) === null || _a === void 0 ? void 0 : _a.SUPABASE_ACCESS_TOKEN)) {
                utils_1.logger.error("SupabaseConfigManager: No refresh token found. Bailing!");
                return;
            }
            try {
                const formData = new URLSearchParams();
                formData.append("grant_type", "refresh_token");
                formData.append("refresh_token", entry === null || entry === void 0 ? void 0 : entry.creds.SUPABASE_REFRESH_TOKEN);
                const response = yield fetch("https://api.supabase.com/v1/oauth/token", {
                    method: "POST",
                    headers: {
                        "Content-Type": "application/x-www-form-urlencoded",
                        Authorization: `Basic ${btoa(`${entry.creds.APPTILE_SUPABASE_CLIENT_ID}:${entry.creds.APPTILE_SUPABASE_CLIENT_SECRET}`)}`,
                    },
                    body: formData.toString(),
                });
                const data = yield response.json();
                const refreshedCreds = {
                    APPTILE_SUPABASE_CLIENT_ID: entry.creds.APPTILE_SUPABASE_CLIENT_ID,
                    APPTILE_SUPABASE_CLIENT_SECRET: entry.creds.APPTILE_SUPABASE_CLIENT_SECRET,
                    SUPABASE_ACCESS_TOKEN: data.access_token,
                    SUPABASE_PROJECT_REF: entry.creds.SUPABASE_PROJECT_REF,
                    SUPABASE_REFRESH_TOKEN: data.refresh_token,
                    SUPABASE_ANON_KEY: entry.creds.SUPABASE_ANON_KEY,
                    ACCESS_TOKEN_EXPIRES_IN: data.expires_in,
                };
                this.configMap.set(appId, { setAt: Date.now(), creds: refreshedCreds });
                yield this.updateCredsInAppIntegrations(appId, refreshedCreds);
            }
            catch (error) {
                utils_1.logger.error("SupabaseConfigManager: Update creds unsuccessful: " + error);
            }
        });
    }
    getCreds(appId) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                if (!this.configMap.has(appId)) {
                    yield this.fetchSupabaseCredsFromAppIntegrations(appId);
                }
                let entry = this.configMap.get(appId);
                if (entry) {
                    try {
                        //test call to check if the creds a valid
                        utils_1.logger.info("Doing a test call to check if valid");
                        yield supabaseSqlQueryRunner(`SELECT schema_name FROM information_schema.schemata`, entry.creds);
                    }
                    catch (error) {
                        //Refreshing the token if invalid creds
                        utils_1.logger.info('Supabase un-authorized. Refreshing token');
                        yield this.refreshAccessToken(appId);
                        entry = this.configMap.get(appId);
                    }
                    if (!(entry === null || entry === void 0 ? void 0 : entry.creds)) {
                        utils_1.logger.error("Could not get valid supabase creds!");
                        return null;
                    }
                    else {
                        return entry.creds;
                    }
                }
                else {
                    return null;
                }
            }
            catch (err) {
                utils_1.logger.error("Error: Failed to retrieve supabase creds" + err);
                return null;
            }
        });
    }
}
exports.supabaseConfigManager = new SupabaseConfigManager();
function formatReturn(result, requireUserInput = false) {
    if (requireUserInput) {
        return {
            content: [
                {
                    type: "text",
                    text: "user_response_required"
                },
                {
                    type: "text",
                    text: result
                }
            ]
        };
    }
    else {
        return {
            content: [
                {
                    type: "text",
                    text: result
                }
            ]
        };
    }
}
;
exports.configManager = {}; // new SupabaseConfigManager();
function supabaseSqlQueryRunner(query, creds) {
    return __awaiter(this, void 0, void 0, function* () {
        try {
            const data = yield fetch(`https://api.supabase.com/v1/projects/${creds.SUPABASE_PROJECT_REF}/database/query`, {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                    Authorization: `Bearer ${creds.SUPABASE_ACCESS_TOKEN}`,
                },
                body: JSON.stringify({
                    query,
                }),
            });
            const response = yield data.json();
            if (response.message === "Unauthorized") {
                utils_1.logger.error("Supabase access token expired.");
            }
            if (response.error) {
                throw new Error(response.error.message);
            }
            return response;
        }
        catch (error) {
            console.error("Error executing SQL query:", error);
            throw error;
        }
    });
}
const supabaseConfigError = formatReturn("Error: This tool is unavailable because supabase creds were not found. User needs to connect to supabase first.");
exports.supabaseTools = [
    (0, utils_1.defineToolSpec)({
        name: "supabase_run_sql_query",
        description: "Run a sql query in the supabase db.",
        inputSchema: {
            apptile_context: utils_2.apptileContextZodSchema,
            sql_query: zod_1.z.string().describe("Sql query to run in the database")
        },
        cb(inputVars) {
            return __awaiter(this, void 0, void 0, function* () {
                var _a;
                if (!((_a = inputVars.apptile_context) === null || _a === void 0 ? void 0 : _a.supabase_creds)) {
                    utils_1.logger.error("supabase configurations were not passed in the context");
                    return supabaseConfigError;
                }
                else {
                    const data = yield supabaseSqlQueryRunner(inputVars.sql_query, inputVars.apptile_context.supabase_creds);
                    return formatReturn(JSON.stringify(data, null, 2));
                }
            });
        }
    }),
    // defineToolSpec({
    //   name: "supabase_enable_rls",
    //   description: "Enable row level security for a table",
    //   inputSchema: {
    //     apptile_context: apptileContextZodSchema,
    //     table_name: z.string().describe("Name of the table on which row level security should be enabled")
    //   },
    //   async cb(inputVars) {
    //     if (!inputVars.apptile_context?.supabase_creds) {
    //       return supabaseConfigError;
    //     } else {
    //       try {
    //         await supabaseSqlQueryRunner(
    //           `ALTER TABLE ${inputVars.table_name} ENABLE ROW LEVEL SECURITY;`,
    //           inputVars.apptile_context.supabase_creds
    //         );
    //         return formatReturn("Row Level Security enabled successfully for table: " + inputVars.table_name);
    //       } catch (err) {
    //         let errorMessage = "Error: Error while running tool enable_rls ";
    //         if (err?.toString) {
    //           errorMessage += err.toString();
    //         } else {
    //           errorMessage += "none";
    //         }
    //         logger.error(errorMessage);
    //         return formatReturn(errorMessage);
    //       }
    //     }
    //   }
    // }),
    // defineToolSpec({
    //   name: "supabase_disable_rls",
    //   description: "Disable row level security for a table",
    //   inputSchema: {
    //     apptile_context: apptileContextZodSchema,
    //     table_name: z.string().describe("Name of the table on which row level security should be disabled")
    //   },
    //   async cb(inputVars) {
    //     if (!inputVars.apptile_context?.supabase_creds) {
    //       return supabaseConfigError;
    //     } else {
    //       try {
    //         await supabaseSqlQueryRunner(
    //           `ALTER TABLE ${inputVars.table_name} DISABLE ROW LEVEL SECURITY;`,
    //           inputVars.apptile_context.supabase_creds
    //         );
    //         return formatReturn("Row Level Security disabled successfully for table: " + inputVars.table_name);
    //       } catch (err) {
    //         let errorMessage = "Error: Error while running tool disable_rls ";
    //         if (err?.toString) {
    //           errorMessage += err.toString();
    //         } else {
    //           errorMessage += "none";
    //         }
    //         logger.error(errorMessage);
    //         return formatReturn(errorMessage);
    //       }
    //     }
    //   }
    // }),
    // defineToolSpec({
    //   name: "supabase_create_rls_policy",
    //   description: "Create a Row Level Security policy for the specified table",
    //   inputSchema: {
    //     apptile_context: apptileContextZodSchema,
    //     table_name: z.string().describe("Name of the table for which the policy should be created."),
    //     policy_details: z.object({
    //       policy_name: z.string().describe("A unique name for the RLS policy."),
    //       policy_definition: z.string().describe("The SQL condition that determines when the policy applies."),
    //       policy_action: z.enum(["ALL", "SELECT", "INSERT", "UPDATE", "DELETE"]).default("ALL").describe("The operation the policy applies to (defaults to ALL)"),
    //       policy_role: z.string().default("authenticated").describe("The database role the policy applies to (defaults to 'authenticated')")
    //     })
    //   },
    //   async cb(inputVars) {
    //     if (!inputVars.apptile_context?.supabase_creds) {
    //       return supabaseConfigError;
    //     } else {
    //       try {
    //         await supabaseSqlQueryRunner(
    //           `CREATE POLICY "${inputVars.policy_details.policy_name}" ON ${inputVars.table_name}
    //             FOR ${inputVars.policy_details.policy_action}
    //             TO ${inputVars.policy_details.policy_role || "authenticated"}
    //             USING (${inputVars.policy_details.policy_definition});`,
    //           inputVars.apptile_context.supabase_creds
    //         );
    //         return formatReturn(`Policy ${inputVars.policy_details.policy_name} created successfully for table: ${inputVars.table_name}`);
    //       } catch (error: any) {
    //         console.error("Error creating RLS policy:", error);
    //         return formatReturn("Error: Error while creating RLS policy: " + error.message);
    //       }
    //     }
    //   }
    // }),
    (0, utils_1.defineToolSpec)({
        name: "supabase_list_schema_names",
        description: "Get all schema names from supabase db",
        inputSchema: {
            apptile_context: utils_2.apptileContextZodSchema
        },
        cb(inputVars) {
            return __awaiter(this, void 0, void 0, function* () {
                var _a;
                utils_1.logger.info("Running tool supabase_list_schema_names");
                if (!((_a = inputVars.apptile_context) === null || _a === void 0 ? void 0 : _a.supabase_creds)) {
                    utils_1.logger.info("Supabase config error");
                    return supabaseConfigError;
                }
                else {
                    const schemas = yield supabaseSqlQueryRunner("SELECT schema_name FROM information_schema.schemata", inputVars.apptile_context.supabase_creds);
                    let result = '';
                    for (let schema of schemas) {
                        result += schema.schema_name + ',\n';
                    }
                    return formatReturn(result);
                }
            });
        }
    }),
];
exports.supabaseReadTools = [
    (0, utils_1.defineToolSpec)({
        name: "list_tables_with_realtime",
        description: `List all tables with realtime enabled. Always call this before generating code that assumes realtime enabled on some table.`,
        inputSchema: {
            apptile_context: utils_2.apptileContextZodSchema,
        },
        cb(inputVars) {
            return __awaiter(this, void 0, void 0, function* () {
                var _a;
                utils_1.logger.info("Running tool list_tables_with_realtime");
                if (!((_a = inputVars.apptile_context) === null || _a === void 0 ? void 0 : _a.supabase_creds)) {
                    utils_1.logger.info("Supabase config error");
                    return supabaseConfigError;
                }
                else {
                    const data = yield supabaseSqlQueryRunner(`SELECT pt.relname AS table_name
          FROM pg_publication p
          JOIN pg_publication_rel pr ON p.oid = pr.prpubid
          JOIN pg_class pt ON pr.prrelid = pt.oid
          WHERE p.pubname = 'supabase_realtime';`, inputVars.apptile_context.supabase_creds);
                    return formatReturn(JSON.stringify(data, null, 2));
                }
            });
        }
    }),
    (0, utils_1.defineToolSpec)({
        name: "supabase_api_creds",
        description: `Get supabase api key and base url for rest apis. Supabase 
    exposes CRUD enpoints for the tables in the database using postgrest. 
    You can call these apis using the credentials obtained from this tool.
    In addition you can use supabase auth for user management using the same creds.
    Curls for the supabase auth endpoints are also included in this.`,
        inputSchema: {
            apptile_context: utils_2.apptileContextZodSchema,
        },
        cb(inputVars) {
            return __awaiter(this, void 0, void 0, function* () {
                var _a;
                utils_1.logger.info("Running tool supbase_api_creds");
                if (!((_a = inputVars.apptile_context) === null || _a === void 0 ? void 0 : _a.supabase_creds)) {
                    utils_1.logger.info("Supabase config error");
                    return supabaseConfigError;
                }
                else {
                    const projectRef = inputVars.apptile_context.supabase_creds.SUPABASE_PROJECT_REF;
                    const supabaseUrl = `https://${projectRef}.supabase.co`;
                    const supabaseAnonKey = inputVars.apptile_context.supabase_creds.SUPABASE_ANON_KEY;
                    return formatReturn(`
            base_url: ${supabaseUrl}/rest/v1
            api_key: ${supabaseAnonKey}
            authentication_curls: 
              signup: 
                description: To perform signup using supabase call this api.
                curl: curl -X POST '${supabaseUrl}/auth/v1/signup' -H "apikey: ${supabaseAnonKey}" -H "Content-Type: application/json" --data '{"email": "user@example.com", "password": "your-secure-password", "data": {"first_name": "John", "last_name": "Doe"}}'
              signin:
                description: To perform signin using supabase call this api.,
                curl: curl -X POST '${supabaseUrl}/auth/v1/token?grant_type=password' -H "apikey: ${supabaseAnonKey}" -H "Content-Type: application/json" --data '{"email": "user@example.com","password": "your-secure-password"}'
              getUser: 
                description: To get the information of the logged in user.
                curl: curl -X GET '${supabaseUrl}/auth/v1/user' -H "apikey: ${supabaseAnonKey}" -H "Authorization: Bearer your-access-token"
              signOut: 
                description: To log the user out use this api
                curl: curl -X POST '${supabaseUrl}/auth/v1/logout' -H "apikey: ${supabaseAnonKey}" -H "Authorization: Bearer your-access-token" 
          `);
                }
            });
        }
    }),
    (0, utils_1.defineToolSpec)({
        name: "supabase_describe_schema",
        description: "List all tables of a schema, and list out all columns of each table.",
        inputSchema: {
            apptile_context: utils_2.apptileContextZodSchema,
            schema_name: zod_1.z.string().describe("Name of the schema whose tables should be described.")
        },
        cb(inputVars) {
            return __awaiter(this, void 0, void 0, function* () {
                var _a;
                utils_1.logger.info("running tool supabase_describe_schema");
                if (!((_a = inputVars.apptile_context) === null || _a === void 0 ? void 0 : _a.supabase_creds)) {
                    utils_1.logger.info("supabse config error");
                    return supabaseConfigError;
                }
                else {
                    let result = {};
                    const query = `SELECT table_name FROM information_schema.tables WHERE table_schema='${inputVars.schema_name}'`;
                    const tableData = yield supabaseSqlQueryRunner(query, inputVars.apptile_context.supabase_creds);
                    const tableNames = [];
                    for (let table of tableData) {
                        tableNames.push(`'${table.table_name}'`);
                    }
                    const columns = yield supabaseSqlQueryRunner(`SELECT column_name, data_type, is_nullable, table_name FROM information_schema.columns WHERE table_name IN (${tableNames.join(', ')})`, inputVars.apptile_context.supabase_creds);
                    if (columns && columns.length) {
                        for (let i = 0; i < columns.length; ++i) {
                            const column = columns[i];
                            if (!result[column.table_name]) {
                                result[column.table_name] = [];
                            }
                            result[column.table_name].push({
                                name: column.column_name,
                                dataType: column.data_type,
                                isNullable: column.is_nullable
                            });
                        }
                    }
                    return formatReturn(JSON.stringify(result, null, 2));
                }
            });
        }
    }),
    (0, utils_1.defineToolSpec)({
        name: "supabase_get_table_structure",
        description: "Get the schema for a specific table",
        inputSchema: {
            apptile_context: utils_2.apptileContextZodSchema,
            table_name: zod_1.z.string().describe("Name of the table.")
        },
        cb(inputVars) {
            return __awaiter(this, void 0, void 0, function* () {
                var _a;
                utils_1.logger.info("Running tool supabase_get_table_structure");
                if (!((_a = inputVars.apptile_context) === null || _a === void 0 ? void 0 : _a.supabase_creds)) {
                    utils_1.logger.info("Config error for supabase");
                    return supabaseConfigError;
                }
                else {
                    const columns = yield supabaseSqlQueryRunner(`SELECT column_name, data_type, is_nullable, table_name FROM information_schema.columns WHERE table_name IN (${inputVars.table_name})`, inputVars.apptile_context.supabase_creds);
                    return formatReturn(JSON.stringify(columns, null, 2));
                }
            });
        }
    }),
    // defineToolSpec({
    //   name: "supabase_sql_to_rest",
    //   description: "Provides the formatCurl function from @supabase/sql-to-rest library. Use it to generate correct api calls when required.",
    //   inputSchema: {
    //     sql_query: z.string().describe("The sql query that will be passed to formatCurl"),
    //     apptile_context: apptileContextZodSchema
    //   },
    //   async cb(inputVars) {
    //     if (!inputVars.apptile_context?.supabase_creds) {
    //       return supabaseConfigError;
    //     } else {
    //       const projectRef = inputVars.apptile_context.supabase_creds.SUPABASE_PROJECT_REF;
    //       const supabaseUrl = `https://${projectRef}.supabase.co/rest/v1`;
    //       // const supabaseAnonKey = inputVars.apptile_context.supabase_creds.SUPABASE_ANON_KEY;
    //       const statement = await processSql(inputVars.sql_query);
    //       const httpRequest = await renderHttp(statement);
    //       const renderedCurl = formatCurl(supabaseUrl, httpRequest);
    //       return {
    //         content: [{
    //           type: "text",
    //           text: renderedCurl
    //         }]
    //       };
    //     }
    //   }
    // })
];
const supabaseApiAccess = [];
