import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import {
  OBJECT_KEY_REGEX,
  presignDownload,
  presignUpload,
  B2InvalidObjectKeyError,
  type B2Config,
  _B2_INTERNALS,
} from './client';

const FIXTURE_CFG: B2Config = {
  keyId: 'AKIA-FIXTURE',
  secret: 'sec-fixture',
  bucket: 'wulf-audits',
  region: 'us-west-002',
  endpoint: 's3.us-west-002.backblazeb2.com',
};

describe('OBJECT_KEY_REGEX', () => {
  it('accepts the production shape', () => {
    expect(
      OBJECT_KEY_REGEX.test(
        'ba03268b-5528-4dde-ad76-867523446ecd/unknown-server/eventlogs_20251202_173301.json.gz'
      )
    ).toBe(true);
    expect(
      OBJECT_KEY_REGEX.test(
        'site_uuid_short/MISYS-SQL/eventlogs_20260502_120000.json.gz'
      )
    ).toBe(true);
  });

  it('rejects path traversal', () => {
    expect(OBJECT_KEY_REGEX.test('../etc/passwd')).toBe(false);
    expect(OBJECT_KEY_REGEX.test('site/../../escape/eventlogs_1.json.gz')).toBe(false);
  });

  it('rejects wrong shapes', () => {
    expect(OBJECT_KEY_REGEX.test('site/host/something.json.gz')).toBe(false); // missing eventlogs_ prefix
    expect(OBJECT_KEY_REGEX.test('eventlogs_1.json.gz')).toBe(false); // missing prefix dirs
    expect(OBJECT_KEY_REGEX.test('site/host/eventlogs_1.json')).toBe(false); // missing .gz
    expect(OBJECT_KEY_REGEX.test('site host/x/eventlogs_1.json.gz')).toBe(false); // space in client id
  });
});

describe('presignDownload + presignUpload', () => {
  const realDate = Date;
  beforeEach(() => {
    // Pin time so signatures are deterministic.
    const fixed = new Date('2026-05-02T20:00:00.000Z');
    vi.stubGlobal(
      'Date',
      class extends realDate {
        constructor(...args: unknown[]) {
          if (args.length === 0) {
            super(fixed.getTime());
          } else {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            super(...(args as [any]));
          }
        }
        static now() {
          return fixed.getTime();
        }
      } as unknown as DateConstructor
    );
  });
  afterEach(() => {
    vi.unstubAllGlobals();
  });

  it('produces a stable presigned GET URL', () => {
    const url = presignDownload(
      'site/host/eventlogs_20260502_120000.json.gz',
      600,
      FIXTURE_CFG
    );
    expect(url).toContain('https://s3.us-west-002.backblazeb2.com/wulf-audits/');
    expect(url).toContain('X-Amz-Algorithm=AWS4-HMAC-SHA256');
    expect(url).toContain('X-Amz-Credential=AKIA-FIXTURE');
    expect(url).toContain('X-Amz-Date=20260502T200000Z');
    expect(url).toContain('X-Amz-Expires=600');
    expect(url).toContain('X-Amz-SignedHeaders=host');
    expect(url).toMatch(/X-Amz-Signature=[a-f0-9]{64}$/);
  });

  it('produces a presigned PUT URL with PUT method scope', () => {
    const url = presignUpload(
      'site/host/eventlogs_20260502_120000.json.gz',
      1800,
      FIXTURE_CFG
    );
    expect(url).toContain('X-Amz-Expires=1800');
    expect(url).toMatch(/X-Amz-Signature=[a-f0-9]{64}$/);
  });

  it('rejects path-traversal object keys', () => {
    expect(() =>
      presignDownload('../etc/eventlogs_1.json.gz', 600, FIXTURE_CFG)
    ).toThrow(B2InvalidObjectKeyError);
  });

  it('different methods produce different signatures (sanity check)', () => {
    const get = presignDownload(
      'site/host/eventlogs_20260502_120000.json.gz',
      600,
      FIXTURE_CFG
    );
    const put = presignUpload(
      'site/host/eventlogs_20260502_120000.json.gz',
      600,
      FIXTURE_CFG
    );
    const sigGet = get.split('X-Amz-Signature=')[1];
    const sigPut = put.split('X-Amz-Signature=')[1];
    expect(sigGet).not.toBe(sigPut);
  });
});

describe('deriveSigningKey', () => {
  it('produces a 32-byte HMAC-SHA256 chain', () => {
    const k = _B2_INTERNALS.deriveSigningKey(
      'sec-fixture',
      '20260502',
      'us-west-002',
      's3'
    );
    expect(k.length).toBe(32);
  });
});
