import {z} from "zod";
// import dotenv, { config } from "dotenv";
// import {parse, SelectStatement, Statement} from 'pgsql-ast-parser';
import { ToolResponse, ToolSpec, defineToolSpec, logger } from "../utils";
import {
  apptileContextZodSchema,
  makeHeadersWithCookie,
  getMidUrlIntegrationsValue
} from "../../../../utils";
import { getOpenApp } from '../../../../database/cliconfig'
import { getAppConfigsFromFS } from '../../../workspace';
// import { formatCurl, processSql, renderHttp } from "@supabase/sql-to-rest";

// supabaseUrl: `https://${credsData.SUPABASE_PROJECT_REF}.supabase.co`,
// supabaseAnonKey: credsData.SUPABASE_ANON_KEY,
// supabaseProjectRef: credsData.SUPABASE_PROJECT_REF,
// supabaseAuthToken: credsData.SUPABASE_ACCESS_TOKEN,
// supabaseRefreshToken: credsData.SUPABASE_REFRESH_TOKEN,
// apptileSupabaseClientId: credsData.APPTILE_SUPABASE_CLIENT_ID,
// apptileSupabaseClientSecret: credsData.APPTILE_SUPABASE_CLIENT_SECRET

export type SupabaseCreds = {
  SUPABASE_ACCESS_TOKEN: string;
  SUPABASE_REFRESH_TOKEN: string;
  ACCESS_TOKEN_EXPIRES_IN: number;
  SUPABASE_PROJECT_REF: string;
  SUPABASE_ANON_KEY?: string;
  APPTILE_SUPABASE_CLIENT_ID?: string;
  APPTILE_SUPABASE_CLIENT_SECRET?: string;
};

// dotenv.config();

class SupabaseConfigManager {
  // appId to config map
  private configMap = new Map<string, {setAt: number; creds: SupabaseCreds;}>();

  private async fetchSupabaseCredsFromAppIntegrations(appId: string) {
    if (!appId) {
      logger.error("SupabaseConfigManager error: no appId");
      throw new Error("App ID is required");
    }

    const openApp = await getOpenApp(appId);
    if (!openApp) {
      logger.error("SupabaseConfigManager error: no opened app");
      throw new Error("Open app not found");
    }

    const header = makeHeadersWithCookie(openApp.apptilecookie, {});
    logger.info("SupabaseConfigManager Header created: " + JSON.stringify(header));
    // const apptileConfig = {APPTILE_BACKEND_URL: ""};
    const apptileConfig = await getAppConfigsFromFS(appId);
    logger.info("SupabaseConfigManager apptileConfig fetched: " + JSON.stringify(apptileConfig))
    process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
    try {
      const response = await fetch(
        `${apptileConfig.APPTILE_BACKEND_URL}/${getMidUrlIntegrationsValue()}/${appId}/appIntegrations/supabase/credentials`,
        {
          method: "GET",
          headers: { ...header.headers },
        }
      );

      const credsData = await response.json();
      const creds: SupabaseCreds = {
        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: any) {
      logger.error("Fetch failed with error: " + error?.toString());
      throw new Error(error);
    }
  }

  private async updateCredsInAppIntegrations(appId: string, creds: SupabaseCreds) {
    const openApp = await getOpenApp(appId);
    if (!openApp) {
      throw new Error("Open app not found");
    }
    const header = makeHeadersWithCookie(openApp.apptilecookie, {});
    const apptileConfig = {APPTILE_BACKEND_URL: ""};
    // const apptileConfig = await getAppConfigsFromFS(appId);
    process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
    try {
      await fetch(
        `${apptileConfig.APPTILE_BACKEND_URL}/${getMidUrlIntegrationsValue()}/${appId}/appIntegrations/supabase/credentials?isPlatformType=true`,
        {
          method: "POST",
          headers: { ...header.headers, "Content-Type": "application/json" },
          body: JSON.stringify(creds),
        }
      );
      logger.info("Update creds successful");
    } catch (error: any) {
      console.error("Fetch failed with error: ", error);
      throw new Error(error);
    }
  }

  private async refreshAccessToken(appId: string) {
    let entry = this.configMap.get(appId);
    if (!entry) {
      await this.fetchSupabaseCredsFromAppIntegrations(appId);
    }
    entry = this.configMap.get(appId);
    if (!entry?.creds?.SUPABASE_ACCESS_TOKEN) {
      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?.creds.SUPABASE_REFRESH_TOKEN);

      const response = await 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 = await response.json();

      const refreshedCreds: SupabaseCreds = {
        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});
      await this.updateCredsInAppIntegrations(appId, refreshedCreds);
    } catch (error) {
      logger.error("SupabaseConfigManager: Update creds unsuccessful: " + error);
    }
  }

  async getCreds(appId: string): Promise<SupabaseCreds|null> {
    try {
      if (!this.configMap.has(appId)) {
        await this.fetchSupabaseCredsFromAppIntegrations(appId);
      }  

      let entry = this.configMap.get(appId);
      if (entry) {
        try {
          //test call to check if the creds a valid
          logger.info("Doing a test call to check if valid");
          await supabaseSqlQueryRunner(`SELECT schema_name FROM information_schema.schemata`, entry.creds);
        } catch (error) {
          //Refreshing the token if invalid creds
          logger.info('Supabase un-authorized. Refreshing token');
          await this.refreshAccessToken(appId);
          entry = this.configMap.get(appId);
        }
        if (!entry?.creds) {
          logger.error("Could not get valid supabase creds!");
          return null;
        } else {
          return entry.creds;
        }
      } else {
        return null;
      }
    } catch(err: any) {
      logger.error("Error: Failed to retrieve supabase creds" + err)
      return null;
    }
  } 
}

export const supabaseConfigManager = new SupabaseConfigManager();

function formatReturn(result: string, requireUserInput: boolean = false): ToolResponse {
  if (requireUserInput) {
    return {
      content: [
        {
          type: "text",
          text: "user_response_required"
        },
        {
          type: "text",
          text: result
        }
      ]
    };
  } else {
    return {
      content: [
        {
          type: "text",
          text: result
        }
      ]
    };
  }
};

export const configManager = {}; // new SupabaseConfigManager();

async function supabaseSqlQueryRunner(query: string, creds: SupabaseCreds) {
  try {
    const data = await 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 = await data.json();

    if (response.message === "Unauthorized") {
      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."
);

export const supabaseTools: Array<ToolSpec<any>> = [
  defineToolSpec({
    name: "supabase_run_sql_query",
    description: "Run a sql query in the supabase db.",
    inputSchema: {
      apptile_context: apptileContextZodSchema,
      sql_query: z.string().describe("Sql query to run in the database")
    },
    async cb(inputVars) {
      if (!inputVars.apptile_context?.supabase_creds) {
        logger.error("supabase configurations were not passed in the context");
        return supabaseConfigError;
      } else {
        const data = await 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);
  //       }
  //     }
  //   }
  // }),
  defineToolSpec({
    name: "supabase_list_schema_names",
    description: "Get all schema names from supabase db",
    inputSchema: {
      apptile_context: apptileContextZodSchema
    },
    async cb(inputVars) {
      logger.info("Running tool supabase_list_schema_names")
      if (!inputVars.apptile_context?.supabase_creds) {
        logger.info("Supabase config error");
        return supabaseConfigError;
      } else {
        const schemas = await 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);
      } 
    }
  }),
];

export const supabaseReadTools: Array<ToolSpec<any>> = [
  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: apptileContextZodSchema,
    },
    async cb(inputVars) {
      logger.info("Running tool list_tables_with_realtime")
      if (!inputVars.apptile_context?.supabase_creds) {
        logger.info("Supabase config error");
        return supabaseConfigError;
      } else {
        const data = await 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));
      } 
    }
  }),
  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: apptileContextZodSchema,
    },
    async cb(inputVars) {
      logger.info("Running tool supbase_api_creds");
      if (!inputVars.apptile_context?.supabase_creds) {
        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" 
          `
        );
      }
    }
  }), 
  defineToolSpec({
    name: "supabase_describe_schema",
    description: "List all tables of a schema, and list out all columns of each table.",
    inputSchema: {
      apptile_context: apptileContextZodSchema,
      schema_name: z.string().describe("Name of the schema whose tables should be described.")
    },
    async cb(inputVars) {
      logger.info("running tool supabase_describe_schema");
      if (!inputVars.apptile_context?.supabase_creds) {
        logger.info("supabse config error");
        return supabaseConfigError;
      } else {
        type ColumnInfo =  {
          name: string;
          dataType: string;
          isNullable: boolean;
        };

        // Map of tablename to columninfo
        type Schema = Record<string, Array<ColumnInfo>>;

        let result: Schema = {};
        const query = `SELECT table_name FROM information_schema.tables WHERE table_schema='${inputVars.schema_name}'`;
        const tableData = await supabaseSqlQueryRunner(
          query,
          inputVars.apptile_context.supabase_creds
        );
        const tableNames = [];
        for (let table of tableData) {
          tableNames.push(`'${table.table_name}'`);
        }
        const columns = await 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));
      }
    }
  }),
  defineToolSpec({
    name: "supabase_get_table_structure",
    description: "Get the schema for a specific table",
    inputSchema: {
      apptile_context: apptileContextZodSchema,
      table_name: z.string().describe("Name of the table.")
    },
    async cb(inputVars) {
      logger.info("Running tool supabase_get_table_structure");
      if (!inputVars.apptile_context?.supabase_creds) {
        logger.info("Config error for supabase");
        return supabaseConfigError;
      } else {
        type ColumnInfo =  {
          name: string;
          dataType: string;
          isNullable: boolean;
        };

        const columns = await 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: Array<ToolSpec<any>> = [];
