/**
 * Copyright IBM Corp. 2024, 2025
 */
import { AssetValidator } from '../../src/service/validation-service.js';
import { Logger, SchemaHandler, YamlContent } from '@apic/studio-shared';
import type { ValidateFunction, ErrorObject } from 'ajv';

// Mock SchemaHandler specifically for this test while preserving Logger mock from setup.ts
jest.mock('@apic/studio-shared', () => {
  const mockLogger = {
    debug: jest.fn(),
    info: jest.fn(),
    warn: jest.fn(),
    error: jest.fn(),
    log: jest.fn(),
    createChildLogger: jest.fn().mockReturnThis(),
  };

  return {
    Logger: mockLogger,
    LogComponent: jest.fn(() => (target: any) => target),
    Component: {
      Build: 'Build',
    },
    SchemaHandler: jest.fn().mockImplementation(() => {
      return {
        getSchema: jest.fn((kind: string): string | null => {
          // Return different schemas based on test cases
          if (kind === 'InvalidSchema') {
            return 'invalid-json';
          } else if (kind === 'EmptySchema') {
            return null;
          } else if (kind === 'StagedPolicySequence') {
            return JSON.stringify({ type: 'object', properties: {} });
          } else if (kind === 'ValidationFailure') {
            return JSON.stringify({
              type: 'object',
              required: ['requiredProperty'],
              properties: {
                requiredProperty: { type: 'string' },
              },
            });
          } else {
            return JSON.stringify({ type: 'object', properties: {} });
          }
        }),
      };
    }),
  };
});

jest.mock('ajv', () => {
  return {
    Ajv: jest.fn().mockImplementation(() => {
      return {
        addFormat: jest.fn(),
        compile: jest.fn((): ValidateFunction => {
          const validate = ((data: any): boolean => {
            if (data.kind === 'ValidationFailure') {
              validate.errors = [
                {
                  instancePath: '/path/to/error',
                  schemaPath: '#/required',
                  keyword: 'required',
                  message: 'must have required property',
                  params: { missingProperty: 'requiredProperty' },
                } as ErrorObject,
              ];
              return false;
            }
            return true;
          }) as ValidateFunction;
          validate.errors = null;
          return validate;
        }),
      };
    }),
  };
});

jest.mock('ajv-formats', () => {
  return {
    default: jest.fn(() => {}),
  };
});

describe('Asset Validate', () => {
  let assetValidator: AssetValidator;

  beforeEach(() => {
    const schemaHandler = new SchemaHandler();
    assetValidator = new AssetValidator(schemaHandler);
    jest.clearAllMocks();
  });

  test('should return invalid when kind is missing', () => {
    const genObj = {} as YamlContent;

    const result = assetValidator.validateAssets(genObj);

    expect(result.valid).toBe(false);
    expect(result.errors).toHaveLength(1);
    expect(result.errors[0]).toContain(
      `Error validating asset: Cannot read properties of undefined (reading 'namespace')`
    );
    expect(Logger.error).toHaveBeenCalledWith(expect.any(String), expect.any(Error));
  });

  test('should return invalid when schema is not found', () => {
    const genObj = {
      kind: 'EmptySchema',
      apiVersion: 'v1',
      metadata: {
        labels: {
          gatewayTypes: ['test-gateway'],
        },
      },
    } as unknown as YamlContent;

    const result = assetValidator.validateAssets(genObj);

    expect(result.valid).toBe(false);
    expect(result.errors).toHaveLength(1);
    expect(result.errors[0]).toContain('Schema not found for kind');
    expect(Logger.error).toHaveBeenCalledWith(expect.any(String));
  });

  test('should skip validation for StagedPolicySequence', () => {
    const genObj = {
      kind: 'StagedPolicySequence',
      apiVersion: 'v1',
      metadata: {
        labels: {
          gatewayTypes: ['test-gateway'],
        },
      },
    } as unknown as YamlContent;

    const result = assetValidator.validateAssets(genObj);

    expect(result.valid).toBe(true);
    expect(result.errors).toHaveLength(0);
  });

  test('should return invalid when validation fails', () => {
    const genObj = {
      kind: 'ValidationFailure',
      apiVersion: 'v1',
      metadata: {
        labels: {
          gatewayTypes: ['test-gateway'],
        },
      },
    } as unknown as YamlContent;

    const result = assetValidator.validateAssets(genObj);

    expect(result.valid).toBe(false);
    expect(result.errors).toHaveLength(1);
    expect(result.errors[0]).toContain(
      'The asset undefined:undefined:undefined has one or more problems.'
    );
    expect(result.errors[0]).toContain('must have required property');
    expect(result.errors[0]).toContain('Review the error and try publishing again');
    expect(Logger.error).toHaveBeenCalledWith(expect.any(String));
  });

  test('should return valid when validation passes', () => {
    const genObj = {
      kind: 'ValidKind',
      apiVersion: 'v1',
      metadata: {
        labels: {
          gatewayTypes: ['test-gateway'],
        },
      },
    } as unknown as YamlContent;

    const result = assetValidator.validateAssets(genObj);

    expect(result.valid).toBe(true);
    expect(result.errors).toHaveLength(0);
  });

  test('should handle exceptions during validation', () => {
    const genObj = {
      kind: 'InvalidSchema',
      apiVersion: 'v1',
      metadata: {
        labels: {
          gatewayTypes: ['test-gateway'],
        },
      },
    } as unknown as YamlContent;

    // This will cause an exception when trying to parse the schema
    const result = assetValidator.validateAssets(genObj);

    expect(result.valid).toBe(false);
    expect(result.errors).toHaveLength(1);
    expect(result.errors[0]).toContain('Error validating asset');
    expect(Logger.error).toHaveBeenCalledWith(expect.any(String), expect.any(Error));
  });
});
