import { Resource, ResourceKind } from "../resource.js";
import { secret } from "../secret.js";
import { handleApiError } from "./api-error.js";
import { createCloudflareApi, } from "./api.js";
export function isSecretsStore(resource) {
    return resource?.[ResourceKind] === "cloudflare::SecretsStore";
}
/**
 * A Cloudflare Secrets Store is a secure, centralized location for storing account-level secrets.
 *
 * @see https://developers.cloudflare.com/secrets-store/
 *
 * @example
 * // Create a basic secrets store
 * const store = await SecretsStore("my-secrets", {
 *   name: "production-secrets"
 * });
 *
 * @example
 * // Create a secrets store with initial secrets
 * const store = await SecretsStore("my-secrets", {
 *   name: "production-secrets",
 *   secrets: {
 *     API_KEY: alchemy.secret(process.env.API_KEY),
 *     DATABASE_URL: alchemy.secret(process.env.DATABASE_URL),
 *     JWT_SECRET: alchemy.secret(process.env.JWT_SECRET)
 *   }
 * });
 *
 * @example
 * // Adopt an existing store if it already exists
 * const existingStore = await SecretsStore("existing-store", {
 *   name: "existing-secrets-store",
 *   adopt: true,
 *   secrets: {
 *     NEW_SECRET: alchemy.secret("new-value")
 *   }
 * });
 *
 * @example
 * // When removing from Alchemy state, keep the store in Cloudflare
 * const preservedStore = await SecretsStore("preserve-store", {
 *   name: "preserved-secrets-store",
 *   delete: false
 * });
 *
 * @example
 * // Use in a Worker binding to access secrets
 * const worker = await Worker("my-worker", {
 *   bindings: {
 *     SECRETS: store
 *   },
 *   code: `
 *     export default {
 *       async fetch(request, env) {
 *         const apiKey = await env.SECRETS.get("API_KEY");
 *         return new Response(apiKey ? "Secret found" : "No secret");
 *       }
 *     }
 *   `
 * });
 */
export async function SecretsStore(name, props = {}) {
    // Convert string values to Secret instances
    const normalizedProps = {
        ...props,
        secrets: props.secrets
            ? Object.fromEntries(Object.entries(props.secrets).map(([key, value]) => [
                key,
                typeof value === "string" ? secret(value) : value,
            ]))
            : undefined,
    };
    return _SecretsStore(name, normalizedProps);
}
(function (SecretsStore) {
    SecretsStore.Default = "default_secrets_store";
})(SecretsStore || (SecretsStore = {}));
const _SecretsStore = Resource("cloudflare::SecretsStore", async function (id, props) {
    const api = await createCloudflareApi(props);
    const name = props.name ?? this.scope.createPhysicalName(id);
    if (this.phase === "update" && this.output.name !== name) {
        this.replace();
    }
    if (this.phase === "delete") {
        const storeId = this.output?.id;
        if (storeId && props.delete !== false) {
            await deleteSecretsStore(api, storeId);
        }
        return this.destroy();
    }
    let storeId = this.phase === "update" ? this.output?.id || "" : "";
    let createdAt = this.phase === "update" ? this.output?.createdAt || Date.now() : Date.now();
    if (this.phase === "update" && storeId) {
        const currentSecrets = props.secrets || {};
        const existingSecretNames = await listSecrets(api, storeId);
        const secretsToDelete = existingSecretNames.filter((name) => !(name in currentSecrets));
        if (secretsToDelete.length > 0) {
            await deleteSecrets(api, storeId, secretsToDelete);
        }
        await insertSecrets(api, storeId, props);
    }
    else {
        // If adopt is true, first check if a store with this name already exists
        if (props.adopt ?? this.scope.adopt) {
            const existingStore = await findSecretsStoreByName(api, name);
            if (existingStore) {
                storeId = existingStore.id;
                createdAt = existingStore.createdAt || Date.now();
            }
            else {
                const { id } = await createSecretsStore(api, {
                    ...props,
                    name,
                });
                createdAt = Date.now();
                storeId = id;
            }
        }
        else {
            // Default behavior: create a new store
            const { id } = await createSecretsStore(api, {
                ...props,
                name,
            });
            createdAt = Date.now();
            storeId = id;
        }
        await insertSecrets(api, storeId, props);
    }
    return {
        id: storeId,
        name: name,
        secrets: props.secrets,
        createdAt: createdAt,
        modifiedAt: Date.now(),
    };
});
export async function createSecretsStore(api, props) {
    const createResponse = await api.post(`/accounts/${api.accountId}/secrets_store/stores`, {
        name: props.name,
    });
    if (!createResponse.ok) {
        await handleApiError(createResponse, "create", "secrets_store", props.name);
    }
    return { id: (await createResponse.json()).result.id };
}
export async function deleteSecretsStore(api, storeId) {
    const deleteResponse = await api.delete(`/accounts/${api.accountId}/secrets_store/stores/${storeId}`);
    if (!deleteResponse.ok && deleteResponse.status !== 404) {
        await handleApiError(deleteResponse, "delete", "secrets_store", storeId);
    }
}
export async function findSecretsStoreByName(api, name) {
    const response = await api.get(`/accounts/${api.accountId}/secrets_store/stores`);
    if (!response.ok) {
        await handleApiError(response, "list", "secrets_store", "all");
    }
    const data = (await response.json());
    const stores = data.result;
    const match = stores.find((store) => store.name === name);
    if (match) {
        return {
            id: match.id,
            createdAt: match.created ? new Date(match.created).getTime() : undefined,
        };
    }
    return null;
}
export async function insertSecrets(api, storeId, props) {
    if (props.secrets && Object.keys(props.secrets).length > 0) {
        const secretEntries = Object.entries(props.secrets);
        const BATCH_SIZE = 100;
        for (let i = 0; i < secretEntries.length; i += BATCH_SIZE) {
            const batch = secretEntries.slice(i, i + BATCH_SIZE);
            const bulkPayload = batch.map(([name, secretValue]) => ({
                name,
                value: secretValue.unencrypted,
                scopes: ["workers"],
            }));
            const bulkResponse = await api.post(`/accounts/${api.accountId}/secrets_store/stores/${storeId}/secrets`, bulkPayload);
            if (!bulkResponse.ok) {
                const errorData = await bulkResponse.json().catch(() => ({
                    errors: [{ message: bulkResponse.statusText }],
                }));
                const errorMessage = errorData.errors?.[0]?.message || bulkResponse.statusText;
                throw new Error(`Error creating secrets batch: ${errorMessage}`);
            }
        }
    }
}
export async function deleteSecrets(api, storeId, secretNames) {
    if (secretNames.length > 0) {
        const deleteResponse = await api.delete(`/accounts/${api.accountId}/secrets_store/stores/${storeId}/secrets`, {
            body: JSON.stringify(secretNames),
            headers: {
                "Content-Type": "application/json",
            },
        });
        if (!deleteResponse.ok && deleteResponse.status !== 404) {
            await handleApiError(deleteResponse, "delete", "secrets", secretNames.join(", "));
        }
    }
}
export async function listSecrets(api, storeId) {
    const response = await api.get(`/accounts/${api.accountId}/secrets_store/stores/${storeId}/secrets`);
    if (!response.ok) {
        await handleApiError(response, "list", "secrets", "all");
    }
    const data = (await response.json());
    return data.result.map((secret) => secret.name);
}
//# sourceMappingURL=secrets-store.js.map