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

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

import {
  applyToExistingMetrics,
  generateToolMetrics,
  validateEnumHomogeneity,
} from '../../src/telemetry/metricsRegistry.js';
import {zod} from '../../src/third_party/index.js';
import {ToolCategory} from '../../src/tools/categories.js';
import type {ToolDefinition} from '../../src/tools/ToolDefinition.js';

describe('metricsRegistry', () => {
  describe('validateEnumHomogeneity', () => {
    it('should return the primitive type of a homogeneous enum', () => {
      const result = validateEnumHomogeneity(['a', 'b', 'c']);
      assert.strictEqual(result, 'string');

      const result2 = validateEnumHomogeneity([1, 2, 3]);
      assert.strictEqual(result2, 'number');
    });

    it('should throw for heterogeneous enum types', () => {
      assert.throws(() => {
        validateEnumHomogeneity(['a', 1, 'c']);
      }, /Heterogeneous enum types found/);
    });
  });

  describe('generateToolMetrics', () => {
    it('should map tools correctly and apply transformations', () => {
      const mockTool: ToolDefinition = {
        name: 'test_tool',
        description: 'test description',
        annotations: {
          category: ToolCategory.INPUT,
          readOnlyHint: true,
        },
        schema: {
          argStr: zod.string(),
          uid: zod.string(), // Should be blocked
        },
        blockedByDialog: false,
        verifyFilesSchema: [],
        handler: async () => {
          // no-op
        },
      };

      const metrics = generateToolMetrics([mockTool]);
      assert.strictEqual(metrics.length, 1);
      assert.strictEqual(metrics[0].name, 'test_tool');
      assert.strictEqual(metrics[0].args.length, 1); // uid is blocked
      assert.strictEqual(metrics[0].args[0].name, 'arg_str_length');
      assert.strictEqual(metrics[0].args[0].argType, 'number');
    });

    it('should handle enums correctly', () => {
      const mockTool: ToolDefinition = {
        name: 'enum_tool',
        description: 'test description',
        annotations: {
          category: ToolCategory.INPUT,
          readOnlyHint: true,
        },
        schema: {
          argEnum: zod.enum(['foo', 'bar']),
        },
        blockedByDialog: false,
        verifyFilesSchema: [],
        handler: async () => {
          // no-op
        },
      };

      const metrics = generateToolMetrics([mockTool]);
      assert.strictEqual(metrics.length, 1);
      assert.strictEqual(metrics[0].args[0].name, 'arg_enum');
      assert.strictEqual(metrics[0].args[0].argType, 'string');
    });

    it('should handle enums wrapped in optional and default', () => {
      const mockTool: ToolDefinition = {
        name: 'wrapped_enum_tool',
        description: 'test description',
        annotations: {
          category: ToolCategory.INPUT,
          readOnlyHint: true,
        },
        schema: {
          argEnum: zod.enum(['foo', 'bar']).default('foo').optional(),
        },
        blockedByDialog: false,
        verifyFilesSchema: [],
        handler: async () => {
          // no-op
        },
      };

      const metrics = generateToolMetrics([mockTool]);
      assert.strictEqual(metrics.length, 1);
      assert.strictEqual(metrics[0].args[0].name, 'arg_enum');
      assert.strictEqual(metrics[0].args[0].argType, 'string');
    });

    it('should sanitize tool names containing underscores before numbers', () => {
      const mockTool: ToolDefinition = {
        name: 'list_3p_developer_tools',
        description: 'test description',
        annotations: {
          category: ToolCategory.THIRD_PARTY,
          readOnlyHint: true,
        },
        schema: {},
        blockedByDialog: false,
        verifyFilesSchema: [],
        handler: async () => {
          // no-op
        },
      };

      const metrics = generateToolMetrics([mockTool]);
      assert.strictEqual(metrics.length, 1);
      assert.strictEqual(metrics[0].name, 'list3p_developer_tools');
    });
  });

  describe('applyToExistingMetrics', () => {
    it('should return the same metrics if existing and update are the same', () => {
      const existing = [{name: 'foo', args: []}];
      const update = [{name: 'foo', args: []}];
      const result = applyToExistingMetrics(existing, update);
      const expected = [{name: 'foo', args: []}];
      assert.deepStrictEqual(result, expected);
    });

    it('should append new entries to the end of the array', () => {
      const existing = [{name: 'foo', args: []}];
      const update = [
        {name: 'foo', args: []},
        {name: 'bar', args: []},
      ];
      const result = applyToExistingMetrics(existing, update);
      const expected = [
        {name: 'foo', args: []},
        {name: 'bar', args: []},
      ];
      assert.deepStrictEqual(result, expected);
    });

    it('should mark missing entries as deprecated and preserve their order', () => {
      const existing = [
        {name: 'foo', args: []},
        {name: 'bar', args: []},
      ];
      const update = [{name: 'foo', args: []}];
      const result = applyToExistingMetrics(existing, update);
      const expected = [
        {name: 'foo', args: []},
        {name: 'bar', args: [], isDeprecated: true},
      ];
      assert.deepStrictEqual(result, expected);
    });

    it('should handle adding new entries and deprecating old ones simultaneously', () => {
      const existing = [
        {name: 'foo', args: []},
        {name: 'bar', args: []},
      ];
      const update = [
        {name: 'bar', args: []},
        {name: 'baz', args: []},
      ];
      const result = applyToExistingMetrics(existing, update);
      const expected = [
        {name: 'foo', args: [], isDeprecated: true},
        {name: 'bar', args: []},
        {name: 'baz', args: []},
      ];
      assert.deepStrictEqual(result, expected);
    });

    it('should append new arguments to the back', () => {
      const existing = [
        {name: 'foo', args: [{name: 'arg_a', argType: 'string'}]},
      ];
      const update = [
        {
          name: 'foo',
          args: [
            {name: 'arg_a', argType: 'string'},
            {name: 'arg_b', argType: 'string'},
          ],
        },
      ];
      const result = applyToExistingMetrics(existing, update);
      const expected = [
        {
          name: 'foo',
          args: [
            {name: 'arg_a', argType: 'string'},
            {name: 'arg_b', argType: 'string'},
          ],
        },
      ];
      assert.deepStrictEqual(result, expected);
    });

    it('should mark removed arguments as deprecated', () => {
      const existing = [
        {
          name: 'foo',
          args: [
            {name: 'arg_a', argType: 'string'},
            {name: 'arg_b', argType: 'string'},
          ],
        },
      ];
      const update = [
        {name: 'foo', args: [{name: 'arg_a', argType: 'string'}]},
      ];
      const result = applyToExistingMetrics(existing, update);
      const expected = [
        {
          name: 'foo',
          args: [
            {name: 'arg_a', argType: 'string'},
            {name: 'arg_b', argType: 'string', isDeprecated: true},
          ],
        },
      ];
      assert.deepStrictEqual(result, expected);
    });

    it('should not change args if they are the same', () => {
      const existing = [
        {name: 'foo', args: [{name: 'arg_a', argType: 'string'}]},
      ];
      const update = [
        {name: 'foo', args: [{name: 'arg_a', argType: 'string'}]},
      ];
      const result = applyToExistingMetrics(existing, update);
      const expected = [
        {name: 'foo', args: [{name: 'arg_a', argType: 'string'}]},
      ];
      assert.deepStrictEqual(result, expected);
    });

    it('should handle adding and removing arguments simultaneously', () => {
      const existing = [
        {
          name: 'foo',
          args: [
            {name: 'arg_a', argType: 'string'},
            {name: 'arg_b', argType: 'string'},
          ],
        },
      ];
      const update = [
        {
          name: 'foo',
          args: [
            {name: 'arg_b', argType: 'string'},
            {name: 'arg_c', argType: 'string'},
          ],
        },
      ];
      const result = applyToExistingMetrics(existing, update);
      const expected = [
        {
          name: 'foo',
          args: [
            {name: 'arg_a', argType: 'string', isDeprecated: true},
            {name: 'arg_b', argType: 'string'},
            {name: 'arg_c', argType: 'string'},
          ],
        },
      ];
      assert.deepStrictEqual(result, expected);
    });

    it('should handle tool and argument changes simultaneously', () => {
      const existing = [
        {name: 'foo', args: [{name: 'arg_a', argType: 'string'}]},
        {name: 'bar', args: []},
      ];
      const update = [
        {name: 'foo', args: [{name: 'arg_b', argType: 'string'}]},
        {name: 'baz', args: []},
      ];
      const result = applyToExistingMetrics(existing, update);
      const expected = [
        {
          name: 'foo',
          args: [
            {name: 'arg_a', argType: 'string', isDeprecated: true},
            {name: 'arg_b', argType: 'string'},
          ],
        },
        {name: 'bar', args: [], isDeprecated: true},
        {name: 'baz', args: []},
      ];
      assert.deepStrictEqual(result, expected);
    });
  });
});
