/**
 * Tests for @bytelyst/devops/server.
 */
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import {
  collectDevopsInfo,
  dependencyCheck,
  getBuildInfo,
  getConfigInfo,
  getRuntimeInfo,
  readServiceVersion,
} from '../server.js';

describe('@bytelyst/devops/server', () => {
  let envBackup: Record<string, string | undefined>;

  beforeEach(() => {
    envBackup = { ...process.env };
  });

  afterEach(() => {
    for (const k of Object.keys(process.env)) {
      if (!(k in envBackup)) delete process.env[k];
    }
    Object.assign(process.env, envBackup);
  });

  describe('getBuildInfo', () => {
    it('reads BYTELYST_* env vars', () => {
      process.env.BYTELYST_COMMIT_SHA = 'abcdef0';
      process.env.BYTELYST_COMMIT_SHA_FULL = 'abcdef0123456789';
      process.env.BYTELYST_BRANCH = 'main';
      process.env.BYTELYST_BUILT_AT = '2026-01-01T00:00:00Z';
      const info = getBuildInfo();
      expect(info.commitSha).toBe('abcdef0');
      expect(info.commitShaFull).toBe('abcdef0123456789');
      expect(info.branch).toBe('main');
      expect(info.builtAt).toBe('2026-01-01T00:00:00Z');
    });

    it('derives short SHA from full SHA when only full is set', () => {
      delete process.env.BYTELYST_COMMIT_SHA;
      process.env.BYTELYST_COMMIT_SHA_FULL = '1234567890abcdef';
      const info = getBuildInfo();
      expect(info.commitSha).toBe('1234567');
    });

    it('returns null fields when env vars are missing', () => {
      delete process.env.BYTELYST_COMMIT_SHA;
      delete process.env.BYTELYST_COMMIT_SHA_FULL;
      delete process.env.BYTELYST_BRANCH;
      delete process.env.BYTELYST_BUILT_AT;
      const info = getBuildInfo();
      expect(info.commitSha).toBeNull();
      expect(info.branch).toBeNull();
      expect(info.builtAt).toBeNull();
    });
  });

  describe('getRuntimeInfo', () => {
    it('returns process metadata', () => {
      const info = getRuntimeInfo();
      expect(info.nodeVersion).toMatch(/^v\d/);
      expect(info.platform).toBeTruthy();
      expect(info.pid).toBeGreaterThan(0);
      expect(info.memoryMb).toBeGreaterThan(0);
      expect(info.uptimeSeconds).toBeGreaterThanOrEqual(0);
      expect(info.uptimeHuman).toMatch(/^\d/);
      expect(new Date(info.startedAt).toString()).not.toBe('Invalid Date');
    });

    it('humanizes uptime correctly', () => {
      const info = getRuntimeInfo();
      // Some format containing s/m/h/d
      expect(info.uptimeHuman).toMatch(/^\d+(s|m|h|d)/);
    });
  });

  describe('getConfigInfo', () => {
    it('lists safe env keys, excluding secrets', () => {
      process.env.PRODUCT_ID = 'test';
      process.env.PLATFORM_API_URL = 'http://x';
      process.env.PLATFORM_JWT_SECRET = 'shh'; // should be excluded by SECRET pattern
      process.env.SOMETHING_ELSE = 'not-listed'; // not in prefix list
      const info = getConfigInfo({
        productId: 'test',
        serviceName: 'svc',
        serviceVersion: '1.0.0',
      });
      expect(info.envKeys).toContain('PRODUCT_ID');
      expect(info.envKeys).toContain('PLATFORM_API_URL');
      expect(info.envKeys).not.toContain('PLATFORM_JWT_SECRET');
      expect(info.envKeys).not.toContain('SOMETHING_ELSE');
    });

    it('uses NODE_ENV or defaults to development', () => {
      process.env.NODE_ENV = 'production';
      let info = getConfigInfo({ productId: 'x', serviceName: 'y', serviceVersion: '0.0.0' });
      expect(info.nodeEnv).toBe('production');

      delete process.env.NODE_ENV;
      info = getConfigInfo({ productId: 'x', serviceName: 'y', serviceVersion: '0.0.0' });
      expect(info.nodeEnv).toBe('development');
    });

    it('respects custom safeEnvPrefixes', () => {
      process.env.MY_CUSTOM_VAR = 'x';
      const info = getConfigInfo({
        productId: 'x',
        serviceName: 'y',
        serviceVersion: '0.0.0',
        safeEnvPrefixes: ['MY_CUSTOM_'],
      });
      expect(info.envKeys).toEqual(['MY_CUSTOM_VAR']);
    });
  });

  describe('readServiceVersion', () => {
    it('returns a non-empty version string', () => {
      // Walks up from current file location and finds a package.json.
      const v = readServiceVersion(import.meta.url);
      expect(v).toMatch(/^\d/);
    });

    it('falls back to "0.0.0" when nothing is found', () => {
      delete process.env.npm_package_version;
      // Walk up from /tmp (no package.json above it inside the test fs)
      const v = readServiceVersion('/no-such-dir/file.js');
      expect(v).toBe('0.0.0');
    });
  });

  describe('dependencyCheck', () => {
    it('marks ok=true with latency when check resolves', async () => {
      const status = await dependencyCheck('test', async () => {
        await new Promise(r => setTimeout(r, 5));
      });
      expect(status.ok).toBe(true);
      expect(status.name).toBe('test');
      expect(status.latencyMs).toBeGreaterThanOrEqual(0);
      expect(status.detail).toBeUndefined();
    });

    it('marks ok=false with detail when check rejects', async () => {
      const status = await dependencyCheck('test', async () => {
        throw new Error('boom');
      });
      expect(status.ok).toBe(false);
      expect(status.detail).toBe('boom');
    });

    it('times out long-running checks', async () => {
      const status = await dependencyCheck('slow', () => new Promise(r => setTimeout(r, 1000)), 50);
      expect(status.ok).toBe(false);
      expect(status.detail).toMatch(/timed out/);
    });
  });

  describe('collectDevopsInfo', () => {
    it('returns full payload with all sections', async () => {
      const info = await collectDevopsInfo({
        productId: 'p',
        serviceName: 's',
        serviceVersion: '0.0.0',
      });
      expect(info.build).toBeDefined();
      expect(info.runtime).toBeDefined();
      expect(info.config).toBeDefined();
      expect(info.config.productId).toBe('p');
    });

    it('populates dependencies when checks are provided', async () => {
      const info = await collectDevopsInfo({
        productId: 'p',
        serviceName: 's',
        serviceVersion: '0.0.0',
        dependencyChecks: [
          () => dependencyCheck('a', async () => undefined),
          () =>
            dependencyCheck('b', async () => {
              throw new Error('fail');
            }),
        ],
      });
      expect(info.dependencies).toHaveLength(2);
      expect(info.dependencies?.[0]?.name).toBe('a');
      expect(info.dependencies?.[0]?.ok).toBe(true);
      expect(info.dependencies?.[1]?.ok).toBe(false);
    });

    it('passes through extra fields', async () => {
      const info = await collectDevopsInfo({
        productId: 'p',
        serviceName: 's',
        serviceVersion: '0.0.0',
        extra: { customField: 'value', count: 42 },
      });
      expect(info.extra).toEqual({ customField: 'value', count: 42 });
    });

    it('does not leak secret env values in config.envKeys', async () => {
      process.env.PLATFORM_FAKE_SECRET = 'super-secret';
      const info = await collectDevopsInfo({
        productId: 'p',
        serviceName: 's',
        serviceVersion: '0.0.0',
      });
      // Key is in PLATFORM_ prefix BUT matches SECRET pattern → excluded.
      expect(info.config.envKeys).not.toContain('PLATFORM_FAKE_SECRET');
    });
  });
});
