import { assertEquals, assertExists } from 'jsr:@std/assert';
import { afterAll, beforeAll, describe, it } from 'jsr:@std/testing/bdd';
import {
  S3Client,
  ListBucketsCommand,
  CreateBucketCommand,
  DeleteBucketCommand,
  PutObjectCommand,
  GetObjectCommand,
  ListObjectsV2Command,
  CopyObjectCommand,
  DeleteObjectCommand,
} from '@aws-sdk/client-s3';
import { createTestContainer, getTestPorts, TEST_ACCESS_KEY, TEST_SECRET_KEY } from './helpers/server.helper.ts';
import { ObjectStorageContainer } from '../ts/index.ts';

const PORT_INDEX = 4;
const ports = getTestPorts(PORT_INDEX);
const BUCKET = 's3-test-bucket';

describe('S3 SDK compatibility', { sanitizeResources: false, sanitizeOps: false }, () => {
  let container: ObjectStorageContainer;
  let s3: S3Client;

  beforeAll(async () => {
    container = createTestContainer(PORT_INDEX);
    await container.start();
    s3 = new S3Client({
      endpoint: `http://localhost:${ports.objstPort}`,
      region: 'us-east-1',
      credentials: {
        accessKeyId: TEST_ACCESS_KEY,
        secretAccessKey: TEST_SECRET_KEY,
      },
      forcePathStyle: true,
    });
  });

  afterAll(async () => {
    s3.destroy();
    await container.stop();
  });

  it('should list buckets (empty)', async () => {
    const response = await s3.send(new ListBucketsCommand({}));
    assertEquals((response.Buckets || []).length, 0);
  });

  it('should create bucket via S3', async () => {
    await s3.send(new CreateBucketCommand({ Bucket: BUCKET }));
    const response = await s3.send(new ListBucketsCommand({}));
    const names = (response.Buckets || []).map((b) => b.Name);
    assertEquals(names.includes(BUCKET), true);
  });

  it('should put object via S3', async () => {
    await s3.send(
      new PutObjectCommand({
        Bucket: BUCKET,
        Key: 'test.txt',
        Body: new TextEncoder().encode('Hello from S3 SDK'),
        ContentType: 'text/plain',
      }),
    );
  });

  it('should get object via S3', async () => {
    const response = await s3.send(
      new GetObjectCommand({ Bucket: BUCKET, Key: 'test.txt' }),
    );
    const body = await response.Body!.transformToString();
    assertEquals(body, 'Hello from S3 SDK');
  });

  it('should list objects via S3', async () => {
    const response = await s3.send(
      new ListObjectsV2Command({ Bucket: BUCKET }),
    );
    const keys = (response.Contents || []).map((o) => o.Key);
    assertEquals(keys.includes('test.txt'), true);
  });

  it('should copy object via S3', async () => {
    await s3.send(
      new CopyObjectCommand({
        Bucket: BUCKET,
        CopySource: `${BUCKET}/test.txt`,
        Key: 'copy.txt',
      }),
    );
    const response = await s3.send(
      new GetObjectCommand({ Bucket: BUCKET, Key: 'copy.txt' }),
    );
    const body = await response.Body!.transformToString();
    assertEquals(body, 'Hello from S3 SDK');
  });

  it('should delete object via S3', async () => {
    await s3.send(new DeleteObjectCommand({ Bucket: BUCKET, Key: 'test.txt' }));
    await s3.send(new DeleteObjectCommand({ Bucket: BUCKET, Key: 'copy.txt' }));
    const response = await s3.send(
      new ListObjectsV2Command({ Bucket: BUCKET }),
    );
    assertEquals((response.Contents || []).length, 0);
  });

  it('should reject requests with wrong credentials', async () => {
    const badS3 = new S3Client({
      endpoint: `http://localhost:${ports.objstPort}`,
      region: 'us-east-1',
      credentials: {
        accessKeyId: 'wrongkey',
        secretAccessKey: 'wrongsecret',
      },
      forcePathStyle: true,
    });
    let threw = false;
    try {
      await badS3.send(new ListBucketsCommand({}));
    } catch {
      threw = true;
    }
    badS3.destroy();
    assertEquals(threw, true);
  });

  it('cleanup: delete bucket', async () => {
    await s3.send(new DeleteBucketCommand({ Bucket: BUCKET }));
    const response = await s3.send(new ListBucketsCommand({}));
    assertEquals((response.Buckets || []).length, 0);
  });
});
