/**
 * @license
 * Copyright (c) ZesTest 2025-2030. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 *
 * This file may contain AI-assisted code.
 * See LICENSE and NOTICE files for details.
 */

/**
 * Tests for `src/core/config/schema.ts` — PramanConfigSchema (Zod).
 *
 * @remarks
 * Verifies schema validation, defaults, type narrowing, and rejection
 * of invalid values. Uses safeParse for non-throwing validation.
 */
import { describe, expect, it } from 'vitest';

import { PramanConfigSchema } from '#core/config/schema.js';

describe('PramanConfigSchema', () => {
  // ── Valid inputs ─────────────────────────────────────────────────────
  describe('valid inputs', () => {
    it('accepts empty object and applies all defaults', () => {
      const result = PramanConfigSchema.safeParse({});
      expect(result.success).toBe(true);
      if (result.success) {
        expect(result.data.logLevel).toBe('info');
        expect(result.data.ui5WaitTimeout).toBe(30_000);
        expect(result.data.controlDiscoveryTimeout).toBe(10_000);
        expect(result.data.interactionStrategy).toBe('ui5-native');
        expect(result.data.discoveryStrategies).toEqual(['direct-id', 'recordreplay']);
        expect(result.data.skipStabilityWait).toBe(false);
        expect(result.data.preferVisibleControls).toBe(true);
        expect(result.data.ignoreAutoWaitUrls).toEqual([]);
      }
    });

    it('accepts valid minimal config with overrides', () => {
      const result = PramanConfigSchema.safeParse({
        logLevel: 'debug',
        ui5WaitTimeout: 5000,
      });
      expect(result.success).toBe(true);
      if (result.success) {
        expect(result.data.logLevel).toBe('debug');
        expect(result.data.ui5WaitTimeout).toBe(5000);
      }
    });

    it('accepts full config with all sections', () => {
      const fullConfig = {
        logLevel: 'verbose',
        ui5WaitTimeout: 60_000,
        controlDiscoveryTimeout: 15_000,
        interactionStrategy: 'ui5-native',
        discoveryStrategies: ['direct-id', 'recordreplay'],
        skipStabilityWait: true,
        preferVisibleControls: false,
        ignoreAutoWaitUrls: ['**/walkme/**', '**/analytics/**'],
        auth: {
          strategy: 'btp-saml',
          baseUrl: 'https://sap.example.com',
          username: 'user',

          password: 'test-credential',
          client: '200',
          language: 'DE',
        },
        ai: {
          provider: 'openai',
          apiKey: 'sk-test',
          model: 'gpt-4o',
          temperature: 0.5,
          maxTokens: 4000,
        },
        telemetry: {
          openTelemetry: true,
          exporter: 'jaeger',
          endpoint: 'https://jaeger.example.com/v1/traces',
          serviceName: 'my-tests',
        },
        selectors: {
          defaultTimeout: 5000,
          preferVisibleControls: false,
          skipStabilityWait: true,
        },
      };
      const result = PramanConfigSchema.safeParse(fullConfig);
      expect(result.success).toBe(true);
    });

    it('applies auth section defaults', () => {
      const result = PramanConfigSchema.safeParse({
        auth: { baseUrl: 'https://sap.example.com' },
      });
      expect(result.success).toBe(true);
      if (result.success) {
        expect(result.data.auth?.strategy).toBe('basic');
        expect(result.data.auth?.client).toBe('100');
        expect(result.data.auth?.language).toBe('EN');
      }
    });

    it('applies telemetry section defaults', () => {
      const result = PramanConfigSchema.safeParse({
        telemetry: {},
      });
      expect(result.success).toBe(true);
      if (result.success) {
        expect(result.data.telemetry?.openTelemetry).toBe(false);
        expect(result.data.telemetry?.exporter).toBe('otlp');
        expect(result.data.telemetry?.serviceName).toBe('playwright-praman');
        expect(result.data.telemetry?.protocol).toBe('http');
        expect(result.data.telemetry?.metrics).toBe(false);
        expect(result.data.telemetry?.batchTimeout).toBe(5_000);
        expect(result.data.telemetry?.maxQueueSize).toBe(2_048);
        expect(result.data.telemetry?.resourceAttributes).toEqual({});
      }
    });

    it('accepts OTLP telemetry with endpoint when enabled', () => {
      const result = PramanConfigSchema.safeParse({
        telemetry: {
          openTelemetry: true,
          endpoint: 'http://localhost:4318',
        },
      });
      expect(result.success).toBe(true);
    });

    it('accepts Azure Monitor telemetry with connectionString when enabled', () => {
      const result = PramanConfigSchema.safeParse({
        telemetry: {
          openTelemetry: true,
          exporter: 'azure-monitor',
          connectionString: 'InstrumentationKey=00000000-0000-0000-0000-000000000000',
        },
      });
      expect(result.success).toBe(true);
    });

    it('rejects openTelemetry true without endpoint (OTLP)', () => {
      const result = PramanConfigSchema.safeParse({
        telemetry: { openTelemetry: true },
      });
      expect(result.success).toBe(false);
    });

    it('rejects Azure Monitor without connectionString', () => {
      const result = PramanConfigSchema.safeParse({
        telemetry: {
          openTelemetry: true,
          exporter: 'azure-monitor',
        },
      });
      expect(result.success).toBe(false);
    });

    it('accepts Jaeger with endpoint when enabled', () => {
      const result = PramanConfigSchema.safeParse({
        telemetry: {
          openTelemetry: true,
          exporter: 'jaeger',
          endpoint: 'http://localhost:4318',
        },
      });
      expect(result.success).toBe(true);
    });

    it('accepts custom resourceAttributes', () => {
      const result = PramanConfigSchema.safeParse({
        telemetry: {
          resourceAttributes: { 'deployment.environment': 'staging' },
        },
      });
      expect(result.success).toBe(true);
      if (result.success) {
        expect(result.data.telemetry?.resourceAttributes).toEqual({
          'deployment.environment': 'staging',
        });
      }
    });

    it('applies ai section defaults', () => {
      const result = PramanConfigSchema.safeParse({
        ai: {},
      });
      expect(result.success).toBe(true);
      if (result.success) {
        expect(result.data.ai?.provider).toBe('azure-openai');
        expect(result.data.ai?.temperature).toBe(0.3);
      }
    });

    it('accepts discoveryStrategies reordering', () => {
      const result = PramanConfigSchema.safeParse({
        discoveryStrategies: ['recordreplay', 'direct-id'],
      });
      expect(result.success).toBe(true);
      if (result.success) {
        expect(result.data.discoveryStrategies).toEqual(['recordreplay', 'direct-id']);
      }
    });

    it('accepts all three discovery strategies', () => {
      const result = PramanConfigSchema.safeParse({
        discoveryStrategies: ['direct-id', 'recordreplay', 'registry'],
      });
      expect(result.success).toBe(true);
    });

    it('accepts single discovery strategy', () => {
      const result = PramanConfigSchema.safeParse({
        discoveryStrategies: ['recordreplay'],
      });
      expect(result.success).toBe(true);
      if (result.success) {
        expect(result.data.discoveryStrategies).toEqual(['recordreplay']);
      }
    });

    it('accepts duplicate discovery strategies (wasteful but valid)', () => {
      const result = PramanConfigSchema.safeParse({
        discoveryStrategies: ['direct-id', 'direct-id'],
      });
      expect(result.success).toBe(true);
    });

    it('applies opa5 defaults when provided as empty object', () => {
      const result = PramanConfigSchema.safeParse({ opa5: {} });
      expect(result.success).toBe(true);
      if (result.success) {
        expect(result.data.opa5?.interactionTimeout).toBe(5_000);
        expect(result.data.opa5?.autoWait).toBe(true);
        expect(result.data.opa5?.debug).toBe(false);
      }
    });

    it('accepts valid full opa5 config', () => {
      const result = PramanConfigSchema.safeParse({
        opa5: { interactionTimeout: 8_000, autoWait: false, debug: true },
      });
      expect(result.success).toBe(true);
      if (result.success) {
        expect(result.data.opa5?.interactionTimeout).toBe(8_000);
        expect(result.data.opa5?.autoWait).toBe(false);
        expect(result.data.opa5?.debug).toBe(true);
      }
    });

    it('opa5 is absent by default', () => {
      const result = PramanConfigSchema.safeParse({});
      expect(result.success).toBe(true);
      if (result.success) {
        expect(result.data.opa5).toBeUndefined();
      }
    });

    it('accepts opa5 even when interactionStrategy is not opa5', () => {
      const result = PramanConfigSchema.safeParse({
        interactionStrategy: 'ui5-native',
        opa5: { interactionTimeout: 3_000 },
      });
      expect(result.success).toBe(true);
    });

    it('applies selectors section defaults', () => {
      const result = PramanConfigSchema.safeParse({
        selectors: {},
      });
      expect(result.success).toBe(true);
      if (result.success) {
        // eslint-disable-next-line @typescript-eslint/no-deprecated, sonarjs/deprecation -- testing deprecated field
        expect(result.data.selectors?.defaultTimeout).toBe(10_000);
        expect(result.data.selectors?.preferVisibleControls).toBe(true);
        expect(result.data.selectors?.skipStabilityWait).toBe(false);
      }
    });
  });

  // ── Invalid inputs ───────────────────────────────────────────────────
  describe('invalid inputs', () => {
    it('rejects invalid logLevel', () => {
      const result = PramanConfigSchema.safeParse({ logLevel: 'trace' });
      expect(result.success).toBe(false);
    });

    it('rejects NaN timeout', () => {
      const result = PramanConfigSchema.safeParse({ ui5WaitTimeout: Number.NaN });
      expect(result.success).toBe(false);
    });

    it('rejects negative timeout', () => {
      const result = PramanConfigSchema.safeParse({ ui5WaitTimeout: -1 });
      expect(result.success).toBe(false);
    });

    it('rejects float timeout', () => {
      const result = PramanConfigSchema.safeParse({ ui5WaitTimeout: 1.5 });
      expect(result.success).toBe(false);
    });

    it('rejects zero timeout', () => {
      const result = PramanConfigSchema.safeParse({ controlDiscoveryTimeout: 0 });
      expect(result.success).toBe(false);
    });

    it('rejects invalid auth baseUrl', () => {
      const result = PramanConfigSchema.safeParse({
        auth: { baseUrl: 'not-a-url' },
      });
      expect(result.success).toBe(false);
    });

    it('rejects unknown top-level fields (strict mode)', () => {
      const result = PramanConfigSchema.safeParse({ unknownField: true });
      expect(result.success).toBe(false);
    });

    it('rejects ai temperature above 2', () => {
      const result = PramanConfigSchema.safeParse({
        ai: { temperature: 3 },
      });
      expect(result.success).toBe(false);
    });

    it('rejects ai temperature below 0', () => {
      const result = PramanConfigSchema.safeParse({
        ai: { temperature: -0.1 },
      });
      expect(result.success).toBe(false);
    });

    it('rejects invalid interactionStrategy', () => {
      const result = PramanConfigSchema.safeParse({
        interactionStrategy: 'selenium',
      });
      expect(result.success).toBe(false);
    });

    it('rejects removed interactionStrategy "playwright"', () => {
      const result = PramanConfigSchema.safeParse({ interactionStrategy: 'playwright' });
      expect(result.success).toBe(false);
    });

    it('rejects removed interactionStrategy "hybrid"', () => {
      const result = PramanConfigSchema.safeParse({ interactionStrategy: 'hybrid' });
      expect(result.success).toBe(false);
    });

    it('rejects empty discoveryStrategies array', () => {
      const result = PramanConfigSchema.safeParse({ discoveryStrategies: [] });
      expect(result.success).toBe(false);
    });

    it('rejects invalid discovery strategy names', () => {
      const result = PramanConfigSchema.safeParse({ discoveryStrategies: ['cache'] });
      expect(result.success).toBe(false);
    });

    it('rejects non-array discoveryStrategies', () => {
      const result = PramanConfigSchema.safeParse({ discoveryStrategies: 'direct-id' });
      expect(result.success).toBe(false);
    });

    it('rejects negative opa5 interactionTimeout', () => {
      const result = PramanConfigSchema.safeParse({ opa5: { interactionTimeout: -1 } });
      expect(result.success).toBe(false);
    });

    it('rejects zero opa5 interactionTimeout', () => {
      const result = PramanConfigSchema.safeParse({ opa5: { interactionTimeout: 0 } });
      expect(result.success).toBe(false);
    });

    it('rejects float opa5 interactionTimeout', () => {
      const result = PramanConfigSchema.safeParse({ opa5: { interactionTimeout: 1.5 } });
      expect(result.success).toBe(false);
    });

    it('rejects invalid auth strategy', () => {
      const result = PramanConfigSchema.safeParse({
        auth: { baseUrl: 'https://sap.example.com', strategy: 'oauth2' },
      });
      expect(result.success).toBe(false);
    });

    it('rejects invalid telemetry exporter', () => {
      const result = PramanConfigSchema.safeParse({
        telemetry: { exporter: 'prometheus' },
      });
      expect(result.success).toBe(false);
    });

    it('rejects invalid telemetry protocol', () => {
      const result = PramanConfigSchema.safeParse({
        telemetry: { protocol: 'websocket' },
      });
      expect(result.success).toBe(false);
    });

    it('rejects non-integer batchTimeout', () => {
      const result = PramanConfigSchema.safeParse({
        telemetry: { batchTimeout: 1.5 },
      });
      expect(result.success).toBe(false);
    });

    it('rejects zero maxQueueSize', () => {
      const result = PramanConfigSchema.safeParse({
        telemetry: { maxQueueSize: 0 },
      });
      expect(result.success).toBe(false);
    });
  });

  // ── Validation error structure ───────────────────────────────────────
  describe('error structure', () => {
    it('provides path information in validation errors', () => {
      const result = PramanConfigSchema.safeParse({
        auth: { baseUrl: 'not-url' },
      });
      expect(result.success).toBe(false);
      if (!result.success) {
        const paths = result.error.issues.map((issue) => issue.path.join('.'));
        expect(paths).toContain('auth.baseUrl');
      }
    });

    it('provides error code in validation errors', () => {
      const result = PramanConfigSchema.safeParse({ logLevel: 'invalid' });
      expect(result.success).toBe(false);
      if (!result.success) {
        const firstIssue = result.error.issues[0];
        expect(firstIssue).toBeDefined();
        expect(firstIssue?.code).toBeDefined();
      }
    });
  });
});
