"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GenericS3Backend = void 0;
const common_1 = require("@nestjs/common");
const client_s3_1 = require("@aws-sdk/client-s3");
const s3_request_presigner_1 = require("@aws-sdk/s3-request-presigner");
const storage_backend_provider_enum_1 = require("../enums/storage-backend-provider.enum");
let GenericS3Backend = class GenericS3Backend {
    constructor() {
        this.logger = new common_1.Logger(this.constructor.name);
        this.provider = storage_backend_provider_enum_1.StorageBackendProvider.GENERIC_S3;
    }
    buildClient(creds) {
        return new client_s3_1.S3Client({
            endpoint: creds.endpoint,
            region: creds.region,
            credentials: {
                accessKeyId: creds.accessKey,
                secretAccessKey: creds.secretKey,
            },
            forcePathStyle: creds.forcePathStyle ?? true,
        });
    }
    async testConnection(creds) {
        const start = Date.now();
        try {
            const client = this.buildClient(creds);
            await client.send(new client_s3_1.HeadBucketCommand({ Bucket: creds.bucket }));
            return { healthy: true, latencyMs: Date.now() - start };
        }
        catch (err) {
            return {
                healthy: false,
                latencyMs: Date.now() - start,
                error: err?.message ?? String(err),
            };
        }
    }
    async ensureBucket(creds) {
        const client = this.buildClient(creds);
        try {
            await client.send(new client_s3_1.HeadBucketCommand({ Bucket: creds.bucket }));
            return;
        }
        catch (err) {
            const status = err?.$metadata?.httpStatusCode;
            if (status !== 404 && status !== 403) {
                throw err;
            }
        }
        try {
            await client.send(new client_s3_1.CreateBucketCommand({ Bucket: creds.bucket }));
        }
        catch (err) {
            if (err?.name === 'BucketAlreadyOwnedByYou')
                return;
            throw err;
        }
    }
    async getUsage(creds, prefix) {
        const client = this.buildClient(creds);
        let bytes = 0;
        let count = 0;
        let token = undefined;
        const fullPrefix = this.joinPrefix(creds.pathPrefix, prefix);
        do {
            const out = await client.send(new client_s3_1.ListObjectsV2Command({
                Bucket: creds.bucket,
                Prefix: fullPrefix,
                ContinuationToken: token,
            }));
            for (const o of out.Contents ?? []) {
                bytes += o.Size ?? 0;
                count += 1;
            }
            token = out.IsTruncated ? out.NextContinuationToken : undefined;
        } while (token);
        return { bytes, objectCount: count };
    }
    async listObjects(creds, prefix, continuationToken) {
        const client = this.buildClient(creds);
        const fullPrefix = this.joinPrefix(creds.pathPrefix, prefix);
        const out = await client.send(new client_s3_1.ListObjectsV2Command({
            Bucket: creds.bucket,
            Prefix: fullPrefix,
            ContinuationToken: continuationToken,
        }));
        return {
            keys: (out.Contents ?? []).map((c) => c.Key).filter(Boolean),
            continuationToken: out.NextContinuationToken,
            hasMore: !!out.IsTruncated,
        };
    }
    async deleteObjects(creds, keys) {
        if (keys.length === 0)
            return;
        const client = this.buildClient(creds);
        const chunks = [];
        for (let i = 0; i < keys.length; i += 1000) {
            chunks.push(keys.slice(i, i + 1000));
        }
        for (const chunk of chunks) {
            await client.send(new client_s3_1.DeleteObjectsCommand({
                Bucket: creds.bucket,
                Delete: { Objects: chunk.map((Key) => ({ Key })) },
            }));
        }
    }
    async presignDownload(creds, key, ttlSeconds) {
        const client = this.buildClient(creds);
        return (0, s3_request_presigner_1.getSignedUrl)(client, new client_s3_1.GetObjectCommand({ Bucket: creds.bucket, Key: key }), { expiresIn: ttlSeconds });
    }
    toVeleroBSL(creds) {
        return {
            provider: 'aws',
            config: {
                region: creds.region,
                s3ForcePathStyle: String(creds.forcePathStyle ?? true),
                s3Url: creds.endpoint,
            },
            credentialsKey: 'cloud',
            bucket: creds.bucket,
            prefix: creds.pathPrefix,
        };
    }
    toRcloneRemote(creds) {
        return {
            type: 's3',
            provider: 'Other',
            env: {
                type: 's3',
                provider: 'Other',
                endpoint: creds.endpoint,
                region: creds.region,
                access_key_id: creds.accessKey,
                secret_access_key: creds.secretKey,
                force_path_style: String(creds.forcePathStyle ?? true),
            },
        };
    }
    joinPrefix(...parts) {
        return parts
            .filter((p) => !!p && p.length > 0)
            .map((p) => p.replaceAll(/^\/+|\/+$/g, ''))
            .filter((p) => p.length > 0)
            .join('/');
    }
    /**
     * Helper for backends to write a small probe object then delete it,
     * used by health-check write/delete probe step.
     */
    async writeAndDeleteProbe(creds, keySuffix = '.flui-health-probe') {
        const client = this.buildClient(creds);
        const probeKey = this.joinPrefix(creds.pathPrefix, keySuffix);
        await client.send(new client_s3_1.PutObjectCommand({
            Bucket: creds.bucket,
            Key: probeKey,
            Body: 'flui-probe',
        }));
        await client.send(new client_s3_1.DeleteObjectCommand({ Bucket: creds.bucket, Key: probeKey }));
    }
};
exports.GenericS3Backend = GenericS3Backend;
exports.GenericS3Backend = GenericS3Backend = __decorate([
    (0, common_1.Injectable)()
], GenericS3Backend);
