import { Resource, ResourceKind } from "../resource.js";
import { secret } from "../secret.js";
import { withExponentialBackoff } from "../util/retry.js";
import { AccountApiToken } from "./account-api-token.js";
import { handleApiError } from "./api-error.js";
import { extractCloudflareResult } from "./api-response.js";
import { createCloudflareApi } from "./api.js";
import { isBucket } from "./bucket.js";
/**
 * Check if a resource is a LogPushJob
 */
export function isLogPushJob(resource) {
    return resource?.[ResourceKind] === "cloudflare::LogPushJob";
}
/**
 * Creates and manages Cloudflare LogPush Jobs for streaming logs to external
 * destinations.
 *
 * LogPush jobs can be scoped to either an account or a zone, and support
 * various log datasets like HTTP requests, firewall events, DNS logs, and more.
 *
 * @example
 * // Basic HTTP request logs to S3 (zone-level)
 * const httpLogs = await LogPushJob("http-logs", {
 *   zone: "example.com",
 *   dataset: "http_requests",
 *   destination: "s3://my-bucket/logs?region=us-west-2",
 *   name: "HTTP Request Logs"
 * });
 *
 * @example
 * // Account-level firewall events with filtering using a string destination
 * const blockedRequests = await LogPushJob("blocked-requests", {
 *   dataset: "firewall_events",
 *   destination: "r2://my-bucket/firewall-logs?account-id=xxx&access-key-id=xxx&secret-access-key=xxx",
 *   filter:
 *     '{"where":{"and":[{"key":"Action","operator":"eq","value":"block"}]}}',
 *   maxUploadBytes: 100 * 1024 * 1024, // 100MB batches
 *   maxUploadIntervalSeconds: 300 // 5 minutes
 * });
 *
 * @example
 * // Stream logs to R2 using R2Bucket resource (requires R2_ACCESS_KEY_ID and R2_SECRET_ACCESS_KEY env vars)
 * const bucket = await R2Bucket("logs-bucket", {
 *   name: "my-logs-bucket"
 * });
 *
 * const logsToR2 = await LogPushJob("http-to-r2", {
 *   dataset: "http_requests",
 *   destination: bucket  // Automatically constructs R2 URL with env credentials
 * });
 *
 * @example
 * // Or use a direct destination string with embedded credentials
 * const logsToR2 = await LogPushJob("http-to-r2", {
 *   dataset: "http_requests",
 *   destination: `r2://my-logs-bucket/logs/{DATE}?account-id=${accountId}&access-key-id=${R2_ACCESS_KEY_ID}&secret-access-key=${R2_SECRET_ACCESS_KEY}`
 * });
 *
 * @example
 * // High-volume analytics with custom output format
 * const analyticsLogs = await LogPushJob("analytics-logs", {
 *   zone: myZone,
 *   dataset: "http_requests",
 *   destination: "s3://analytics-bucket/logs?region=eu-west-1",
 *   sample: 0.01, // 1% sampling for high-volume sites
 *   maxUploadRecords: 100000, // 100k records per batch
 *   outputOptions: {
 *     outputType: "ndjson",
 *     timestampFormat: "unixnano",
 *     fieldNames: [
 *       "ClientIP",
 *       "ClientRequestHost",
 *       "EdgeResponseStatus",
 *       "EdgeStartTimestamp"
 *     ],
 *     sampleRate: 0.1 // Additional 10% sampling within the batch
 *   }
 * });
 *
 * @see https://developers.cloudflare.com/logs/get-started/enable-destinations/
 * @see https://developers.cloudflare.com/api/resources/logpush/
 */
export function LogPushJob(id, props) {
    return _LogPushJob(id, {
        ...props,
        destination: typeof props.destination === "string"
            ? secret(props.destination)
            : isBucket(props.destination)
                ? props.destination
                : props.destination,
    });
}
const _LogPushJob = Resource("cloudflare::LogPushJob", async function (id, props) {
    const api = await createCloudflareApi(props);
    const isZoneScoped = !!props.zone;
    let zoneId;
    let accountId;
    if (isZoneScoped) {
        zoneId = typeof props.zone === "string" ? props.zone : props.zone.id;
    }
    else {
        accountId = api.accountId;
    }
    const basePath = isZoneScoped
        ? `/zones/${zoneId}/logpush/jobs`
        : `/accounts/${accountId}/logpush/jobs`;
    if (this.phase === "delete") {
        if (this.output?.id && props.delete !== false) {
            const deleteResponse = await api.delete(`${basePath}/${this.output.id}`);
            if (!deleteResponse.ok && deleteResponse.status !== 404) {
                await handleApiError(deleteResponse, "delete", "logpush_job", String(this.output.id));
            }
        }
        return this.destroy();
    }
    let destination;
    let apiToken;
    if (isBucket(props.destination)) {
        const bucket = props.destination;
        let accessKeyId = process.env.R2_ACCESS_KEY_ID;
        let secretAccessKey = process.env.R2_SECRET_ACCESS_KEY;
        if (!accessKeyId || !secretAccessKey) {
            try {
                apiToken = await AccountApiToken("token", {
                    accountId: props.accountId,
                    apiKey: props.apiKey,
                    apiToken: props.apiToken,
                    baseUrl: props.baseUrl,
                    profile: props.profile,
                    email: props.email,
                    policies: [
                        {
                            effect: "allow",
                            permissionGroups: [
                                "Workers R2 Storage Write",
                                "Workers R2 Storage Read",
                                "Workers R2 Storage Bucket Item Read",
                                "Workers R2 Storage Bucket Item Write",
                            ],
                            resources: {
                                [`com.cloudflare.edge.r2.bucket.${api.accountId}_${bucket.jurisdiction ?? "default"}_${bucket.name}`]: "*",
                            },
                        },
                    ],
                });
                accessKeyId = apiToken.accessKeyId.unencrypted;
                secretAccessKey = apiToken.secretAccessKey.unencrypted;
            }
            catch (err) {
                const message = [
                    `Cannot create LogPushJob "${id}" because credentials for destination R2 bucket "${bucket.name}" could not be created. This may be because of an authentication error.`,
                ];
                if (api.credentials.type === "api-token") {
                    message.push("Please ensure that your API token has the correct permissions, or use a global API key and email instead.");
                }
                else if (api.credentials.type === "oauth") {
                    message.push("Please use a global API key and email, or create an API token with the necessary permissions.");
                }
                message.push("See https://alchemy.run/guides/cloudflare/ for more details.");
                throw new Error(message.join("\n"), { cause: err });
            }
        }
        destination = `r2://${bucket.name}/logs/{DATE}?${new URLSearchParams({
            "account-id": api.accountId,
            "access-key-id": accessKeyId,
            "secret-access-key": secretAccessKey,
        }).toString()}`;
    }
    else {
        destination = props.destination.unencrypted;
    }
    const jobConfig = {
        dataset: props.dataset,
        destination_conf: destination,
        ...(props.name && { name: props.name }),
        ...(props.enabled !== undefined && { enabled: props.enabled }),
        ...(props.frequency && { frequency: props.frequency }),
        ...(props.kind !== undefined && { kind: props.kind }),
        ...(props.maxUploadBytes !== undefined && {
            max_upload_bytes: props.maxUploadBytes,
        }),
        ...(props.maxUploadIntervalSeconds !== undefined && {
            max_upload_interval_seconds: props.maxUploadIntervalSeconds,
        }),
        ...(props.maxUploadRecords !== undefined && {
            max_upload_records: props.maxUploadRecords,
        }),
        ...(props.ownershipChallenge && {
            ownership_challenge: props.ownershipChallenge,
        }),
    };
    if (props.outputOptions) {
        jobConfig.output_options = {
            ...(props.outputOptions.outputType && {
                output_type: props.outputOptions.outputType,
            }),
            ...(props.outputOptions.timestampFormat && {
                timestamp_format: props.outputOptions.timestampFormat,
            }),
            ...(props.outputOptions.fieldNames && {
                field_names: props.outputOptions.fieldNames,
            }),
            ...(props.outputOptions.fieldDelimiter && {
                field_delimiter: props.outputOptions.fieldDelimiter,
            }),
            ...(props.outputOptions.sampleRate !== undefined && {
                sample_rate: props.outputOptions.sampleRate,
            }),
            ...(props.outputOptions.batchPrefix && {
                batch_prefix: props.outputOptions.batchPrefix,
            }),
            ...(props.outputOptions.batchSuffix && {
                batch_suffix: props.outputOptions.batchSuffix,
            }),
            ...(props.outputOptions.cve202144228 !== undefined && {
                "CVE-2021-44228": props.outputOptions.cve202144228,
            }),
            ...(props.outputOptions.recordDelimiter && {
                record_delimiter: props.outputOptions.recordDelimiter,
            }),
            ...(props.outputOptions.recordPrefix && {
                record_prefix: props.outputOptions.recordPrefix,
            }),
            ...(props.outputOptions.recordSuffix && {
                record_suffix: props.outputOptions.recordSuffix,
            }),
            ...(props.outputOptions.recordTemplate && {
                record_template: props.outputOptions.recordTemplate,
            }),
        };
    }
    const jobData = await withExponentialBackoff(async () => {
        if (this.phase === "update" && this.output?.id) {
            return await extractCloudflareResult(`update logpush job ${this.output.id}`, api.put(`${basePath}/${this.output.id}`, jobConfig));
        }
        else {
            return await extractCloudflareResult(`create logpush job for dataset ${props.dataset}`, api.post(basePath, jobConfig));
        }
    }, 
    // if we just recently created an api token for R2, we can retry on 1002 which are caused by token propagation
    (error) => {
        return error.errorData?.[0]?.code === 1002;
    }, 30, 100);
    return {
        type: "logpush_job",
        id: jobData.id,
        accountId: api.accountId,
        dataset: jobData.dataset ?? props.dataset,
        destination: secret(jobData.destination_conf ?? destination),
        name: jobData.name ?? undefined,
        enabled: jobData.enabled ?? props.enabled ?? true,
        frequency: jobData.frequency ?? undefined,
        filter: props.filter,
        maxUploadBytes: jobData.max_upload_bytes ?? undefined,
        maxUploadIntervalSeconds: jobData.max_upload_interval_seconds ?? undefined,
        maxUploadRecords: jobData.max_upload_records ?? undefined,
        outputOptions: jobData.output_options
            ? {
                outputType: jobData.output_options.output_type,
                timestampFormat: jobData.output_options.timestamp_format,
                fieldNames: jobData.output_options.field_names,
                fieldDelimiter: jobData.output_options.field_delimiter ?? undefined,
                sampleRate: jobData.output_options.sample_rate ?? undefined,
                batchPrefix: jobData.output_options.batch_prefix ?? undefined,
                batchSuffix: jobData.output_options.batch_suffix ?? undefined,
                cve202144228: jobData.output_options["CVE-2021-44228"] ?? undefined,
                recordDelimiter: jobData.output_options.record_delimiter ?? undefined,
                recordPrefix: jobData.output_options.record_prefix ?? undefined,
                recordSuffix: jobData.output_options.record_suffix ?? undefined,
                recordTemplate: jobData.output_options.record_template ?? undefined,
            }
            : undefined,
        kind: jobData.kind || undefined,
        errorMessage: jobData.error_message ?? undefined,
        lastComplete: jobData.last_complete ?? undefined,
        lastError: jobData.last_error ?? undefined,
        createdAt: this.output?.createdAt ?? Date.now(),
        modifiedAt: Date.now(),
    };
});
//# sourceMappingURL=logpush-job.js.map