/**
 * @license
 * Copyright 2026 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import assert from 'node:assert';
import {describe, it} from 'node:test';

import {
  bucketizeLatency,
  getEnumValues,
  sanitizeParams,
  stripUnderscoreBeforeNumber,
  transformArgName,
} from '../../src/telemetry/transformation.js';
import {zod} from '../../src/third_party/index.js';

describe('getEnumValues', () => {
  it('resolves values for a bare enum', () => {
    assert.deepStrictEqual(getEnumValues(zod.enum(['a', 'b'])), ['a', 'b']);
  });

  it('resolves values through optional/default wrappers in any order', () => {
    assert.deepStrictEqual(getEnumValues(zod.enum(['a', 'b']).optional()), [
      'a',
      'b',
    ]);
    assert.deepStrictEqual(getEnumValues(zod.enum(['a', 'b']).default('a')), [
      'a',
      'b',
    ]);
    assert.deepStrictEqual(
      getEnumValues(zod.enum(['a', 'b']).default('a').optional()),
      ['a', 'b'],
    );
    assert.deepStrictEqual(
      getEnumValues(zod.enum(['a', 'b']).optional().default('a')),
      ['a', 'b'],
    );
  });

  it('throws for a non-enum type', () => {
    assert.throws(
      () => getEnumValues(zod.string()),
      /Cannot resolve enum values/,
    );
  });
});

describe('bucketizeLatency', () => {
  it('should bucketize values correctly', () => {
    assert.strictEqual(bucketizeLatency(0), 50);
    assert.strictEqual(bucketizeLatency(25), 50);
    assert.strictEqual(bucketizeLatency(50), 50);

    assert.strictEqual(bucketizeLatency(51), 100);
    assert.strictEqual(bucketizeLatency(100), 100);

    assert.strictEqual(bucketizeLatency(101), 250);
    assert.strictEqual(bucketizeLatency(250), 250);

    assert.strictEqual(bucketizeLatency(499), 500);
    assert.strictEqual(bucketizeLatency(500), 500);

    assert.strictEqual(bucketizeLatency(900), 1000);
    assert.strictEqual(bucketizeLatency(1000), 1000);

    assert.strictEqual(bucketizeLatency(2000), 2500);
    assert.strictEqual(bucketizeLatency(2500), 2500);

    assert.strictEqual(bucketizeLatency(4000), 5000);
    assert.strictEqual(bucketizeLatency(5000), 5000);

    assert.strictEqual(bucketizeLatency(6000), 10000);
    assert.strictEqual(bucketizeLatency(10000), 10000);

    assert.strictEqual(bucketizeLatency(10001), 10000);
    assert.strictEqual(bucketizeLatency(99999), 10000);
  });
});

describe('sanitizeParams', () => {
  it('filters out uid and transforms strings and arrays', () => {
    const schema = {
      uid: zod.string(),
      myString: zod.string(),
      myArray: zod.array(zod.string()),
      myNumber: zod.number(),
      myBool: zod.boolean(),
      myEnum: zod.enum(['a', 'b']),
    };

    const params = {
      uid: 'sensitive',
      myString: 'hello',
      myArray: ['one', 'two'],
      myNumber: 42,
      myBool: true,
      myEnum: 'a' as const,
    };

    const sanitized = sanitizeParams(params, schema);

    assert.deepStrictEqual(sanitized, {
      my_string_length: 5,
      my_array_count: 2,
      my_number: 42,
      my_bool: true,
      my_enum: 'a',
    });
  });

  it('bucketizes string lengths correctly', () => {
    const schema = {
      str0: zod.string(),
      str1: zod.string(),
      str3: zod.string(),
      str5: zod.string(),
      str10000: zod.string(),
      str10001: zod.string(),
    };

    const params = {
      str0: '',
      str1: 'a',
      str3: 'abc',
      str5: 'abcde',
      str10000: 'a'.repeat(10000),
      str10001: 'a'.repeat(10001),
    };

    const sanitized = sanitizeParams(params, schema);

    assert.strictEqual(sanitized.str0_length, 0);
    assert.strictEqual(sanitized.str1_length, 1);
    assert.strictEqual(sanitized.str3_length, 5); // snaps to 5
    assert.strictEqual(sanitized.str5_length, 5);
    assert.strictEqual(sanitized.str10000_length, 10000);
    assert.strictEqual(sanitized.str10001_length, 10000); // snaps to 10000
  });

  it('throws error for unsupported types', () => {
    const schema = {
      myObj: zod.object({foo: zod.string()}),
    };
    const params = {
      myObj: {foo: 'bar'},
    };

    assert.throws(
      () => sanitizeParams(params, schema),
      /Unsupported zod type for tool parameter: ZodObject/,
    );
  });

  it('throws error when value is not of equivalent type', () => {
    const schema = {
      myString: zod.string(),
    };
    const params = {
      myString: 123,
    };

    assert.throws(
      () => sanitizeParams(params, schema),
      /parameter myString has type ZodString but value 123 is not of equivalent type/,
    );
  });
});

describe('stripUnderscoreBeforeNumber', () => {
  it('removes underscores immediately preceding numbers', () => {
    assert.strictEqual(
      stripUnderscoreBeforeNumber('list_3p_developer_tools'),
      'list3p_developer_tools',
    );
    assert.strictEqual(
      stripUnderscoreBeforeNumber('make_2g_network_request'),
      'make2g_network_request',
    );
    assert.strictEqual(
      stripUnderscoreBeforeNumber('no_numbers_here'),
      'no_numbers_here',
    );
  });
});

describe('transformArgName', () => {
  it('sanitizes argument names containing underscores before numbers', () => {
    assert.strictEqual(
      transformArgName('ZodNumber', 'my3pParam'),
      'my3p_param',
    );
    assert.strictEqual(
      transformArgName('ZodString', 'my3pParam'),
      'my3p_param_length',
    );
  });
});
