import { ServiceBubble } from '../../../types/service-bubble-class.js';
import { CredentialType } from '@bubblelab/shared-schemas';
import { SnowflakeParamsSchema, SnowflakeResultSchema, } from './snowflake.schema.js';
import { parseSnowflakeCredential, generateSnowflakeJWT, getSnowflakeBaseUrl, getCellByName, } from './snowflake.utils.js';
/**
 * Snowflake Service Bubble
 *
 * Snowflake data warehouse integration using the SQL REST API with key-pair authentication.
 *
 * Features:
 * - Execute arbitrary SQL statements
 * - List databases, schemas, and tables
 * - Describe table column definitions
 * - Key-pair (RSA) JWT authentication — no token expiry
 *
 * Credential fields (multi-field):
 * - account (required): Account identifier (ORGNAME-ACCOUNTNAME from URL)
 * - username (required): Snowflake login username
 * - privateKey (required): RSA private key in PEM format
 * - privateKeyPassword (optional): Passphrase if key is encrypted
 * - warehouse (optional): Default warehouse
 * - database (optional): Default database
 * - schema (optional): Default schema
 * - role (optional): Default role
 */
export class SnowflakeBubble extends ServiceBubble {
    static type = 'service';
    static service = 'snowflake';
    static authType = 'apikey';
    static bubbleName = 'snowflake';
    static schema = SnowflakeParamsSchema;
    static resultSchema = SnowflakeResultSchema;
    static shortDescription = 'Snowflake data warehouse integration for SQL queries and metadata';
    static longDescription = `
    Snowflake data warehouse integration using the SQL REST API with key-pair authentication.

    Features:
    - Execute arbitrary SQL statements against Snowflake
    - List databases, schemas, and tables
    - Describe table column definitions
    - Key-pair (RSA) JWT authentication — no token expiry, no refresh needed

    Use cases:
    - Run SQL queries to fetch data from Snowflake warehouses
    - Explore database metadata (databases, schemas, tables, columns)
    - Automate data pipeline operations
    - Build reports from Snowflake data
  `;
    static alias = 'snowflake';
    constructor(params = {
        operation: 'list_databases',
    }, context, instanceId) {
        super(params, context, instanceId);
    }
    async testCredential() {
        const credential = this.chooseCredential();
        if (!credential) {
            throw new Error('Snowflake credentials are required');
        }
        const creds = parseSnowflakeCredential(credential);
        const jwt = generateSnowflakeJWT(creds);
        const baseUrl = getSnowflakeBaseUrl(creds.account);
        // Test by running SELECT CURRENT_USER()
        const response = await fetch(`${baseUrl}/api/v2/statements`, {
            method: 'POST',
            headers: {
                Authorization: `Bearer ${jwt}`,
                'Content-Type': 'application/json',
                Accept: 'application/json',
                'X-Snowflake-Authorization-Token-Type': 'KEYPAIR_JWT',
            },
            body: JSON.stringify({
                statement: 'SELECT CURRENT_USER()',
                timeout: 30,
                ...(creds.warehouse && { warehouse: creds.warehouse }),
                ...(creds.role && { role: creds.role }),
            }),
        });
        if (!response.ok) {
            const text = await response.text();
            throw new Error(`Snowflake credential validation failed (${response.status}): ${text}`);
        }
        return true;
    }
    chooseCredential() {
        const { credentials } = this.params;
        if (!credentials || typeof credentials !== 'object') {
            return undefined;
        }
        return credentials[CredentialType.SNOWFLAKE_CRED];
    }
    /**
     * Execute a SQL statement via the Snowflake SQL API.
     */
    async executeStatement(creds, statement, options) {
        const jwt = generateSnowflakeJWT(creds);
        const baseUrl = getSnowflakeBaseUrl(creds.account);
        const body = {
            statement,
            timeout: options?.timeout ?? 60,
        };
        // Apply context: explicit params > credential defaults
        const database = options?.database ?? creds.database;
        const schema = options?.schema ?? creds.schema;
        const warehouse = options?.warehouse ?? creds.warehouse;
        const role = options?.role ?? creds.role;
        if (database)
            body.database = database;
        if (schema)
            body.schema = schema;
        if (warehouse)
            body.warehouse = warehouse;
        if (role)
            body.role = role;
        const response = await fetch(`${baseUrl}/api/v2/statements`, {
            method: 'POST',
            headers: {
                Authorization: `Bearer ${jwt}`,
                'Content-Type': 'application/json',
                Accept: 'application/json',
                'X-Snowflake-Authorization-Token-Type': 'KEYPAIR_JWT',
            },
            body: JSON.stringify(body),
        });
        if (!response.ok) {
            const errorText = await response.text();
            let errorMessage;
            try {
                const errorJson = JSON.parse(errorText);
                errorMessage =
                    errorJson.message || errorJson.error || `HTTP ${response.status}`;
            }
            catch {
                errorMessage = errorText || `HTTP ${response.status}`;
            }
            throw new Error(`Snowflake API error (${response.status}): ${errorMessage}`);
        }
        const result = (await response.json());
        // Handle async execution (202 status)
        if (response.status === 202) {
            throw new Error('Query is still executing asynchronously. Increase the timeout or use a simpler query.');
        }
        return {
            resultSetMetaData: result.resultSetMetaData || {
                numRows: 0,
                rowType: [],
            },
            data: result.data || [],
            statementHandle: result.statementHandle || '',
        };
    }
    async performAction(context) {
        void context;
        const { operation } = this.params;
        try {
            const credential = this.chooseCredential();
            if (!credential) {
                throw new Error('Snowflake credentials are required');
            }
            const creds = parseSnowflakeCredential(credential);
            const result = await (async () => {
                const parsedParams = this.params;
                switch (operation) {
                    case 'execute_sql':
                        return await this.executeSql(creds, parsedParams);
                    case 'list_databases':
                        return await this.listDatabases(creds);
                    case 'list_schemas':
                        return await this.listSchemas(creds, parsedParams);
                    case 'list_tables':
                        return await this.listTables(creds, parsedParams);
                    case 'describe_table':
                        return await this.describeTable(creds, parsedParams);
                    default:
                        throw new Error(`Unsupported operation: ${operation}`);
                }
            })();
            return result;
        }
        catch (error) {
            return {
                operation,
                success: false,
                error: error instanceof Error ? error.message : 'Unknown error occurred',
            };
        }
    }
    async executeSql(creds, params) {
        const result = await this.executeStatement(creds, params.statement, {
            database: params.database,
            schema: params.schema,
            warehouse: params.warehouse,
            role: params.role,
            timeout: params.timeout,
        });
        return {
            operation: 'execute_sql',
            success: true,
            columns: result.resultSetMetaData.rowType.map((col) => ({
                name: col.name,
                type: col.type,
                nullable: col.nullable,
            })),
            rows: result.data,
            num_rows: result.resultSetMetaData.numRows,
            statement_handle: result.statementHandle,
            error: '',
        };
    }
    async listDatabases(creds) {
        const result = await this.executeStatement(creds, 'SHOW DATABASES');
        const rowType = result.resultSetMetaData.rowType;
        const databases = result.data.map((row) => ({
            name: getCellByName(row, rowType, 'name') ?? '',
            owner: getCellByName(row, rowType, 'owner'),
            created_on: getCellByName(row, rowType, 'created_on'),
        }));
        return {
            operation: 'list_databases',
            success: true,
            databases,
            error: '',
        };
    }
    async listSchemas(creds, params) {
        const result = await this.executeStatement(creds, `SHOW SCHEMAS IN DATABASE "${params.database}"`);
        const rowType = result.resultSetMetaData.rowType;
        const schemas = result.data.map((row) => ({
            name: getCellByName(row, rowType, 'name') ?? '',
            database_name: getCellByName(row, rowType, 'database_name'),
            owner: getCellByName(row, rowType, 'owner'),
            created_on: getCellByName(row, rowType, 'created_on'),
        }));
        return {
            operation: 'list_schemas',
            success: true,
            schemas,
            error: '',
        };
    }
    async listTables(creds, params) {
        const result = await this.executeStatement(creds, `SHOW TABLES IN SCHEMA "${params.database}"."${params.schema}"`);
        const rowType = result.resultSetMetaData.rowType;
        const tables = result.data.map((row) => {
            const rowsStr = getCellByName(row, rowType, 'rows');
            return {
                name: getCellByName(row, rowType, 'name') ?? '',
                database_name: getCellByName(row, rowType, 'database_name'),
                schema_name: getCellByName(row, rowType, 'schema_name'),
                kind: getCellByName(row, rowType, 'kind'),
                rows: rowsStr ? parseInt(rowsStr, 10) : undefined,
                created_on: getCellByName(row, rowType, 'created_on'),
            };
        });
        return {
            operation: 'list_tables',
            success: true,
            tables,
            error: '',
        };
    }
    async describeTable(creds, params) {
        const result = await this.executeStatement(creds, `DESCRIBE TABLE "${params.database}"."${params.schema}"."${params.table}"`);
        const rowType = result.resultSetMetaData.rowType;
        const columns = result.data.map((row) => {
            const nullableStr = getCellByName(row, rowType, 'null?');
            const pkStr = getCellByName(row, rowType, 'primary key');
            return {
                name: getCellByName(row, rowType, 'name') ?? '',
                type: getCellByName(row, rowType, 'type') ?? '',
                nullable: nullableStr ? nullableStr.toUpperCase() === 'Y' : undefined,
                default: getCellByName(row, rowType, 'default') ?? null,
                primary_key: pkStr ? pkStr.toUpperCase() === 'Y' : undefined,
                comment: getCellByName(row, rowType, 'comment') ?? null,
            };
        });
        return {
            operation: 'describe_table',
            success: true,
            columns,
            error: '',
        };
    }
}
//# sourceMappingURL=snowflake.js.map