/**
 * Diagnostics module tests — Phase 1: Server Foundation
 *
 * @module diagnostics
 */

import { describe, it, expect } from 'vitest';
import { randomUUID } from 'node:crypto';
import * as repo from './repository.js';
import {
  generateId,
  CreateDebugSessionSchema,
  UpdateDebugSessionSchema,
  IngestTracesSchema,
  IngestLogsSchema,
  type DebugSessionDoc,
  type DebugTraceDoc,
  type DebugLogEntryDoc,
  type CreateDebugSessionInput,
  type UpdateDebugSessionInput,
  type IngestTracesInput,
  type IngestLogsInput,
} from './types.js';

// Test helpers - generateId is now imported from types.ts for DRY compliance
// function generateId(prefix: string): string { ... } // REMOVED - using shared version

function createTestSession(
  productId: string,
  overrides?: Partial<DebugSessionDoc>
): DebugSessionDoc {
  const now = new Date().toISOString();
  return {
    id: generateId('ds'),
    productId,
    targetUserId: `user_${randomUUID()}`,
    status: 'pending',
    collectionLevel: 'debug',
    captureLogs: true,
    captureNetwork: true,
    captureScreenshots: false,
    screenshotOnError: true,
    maxDurationMinutes: 60,
    createdAt: now,
    updatedAt: now,
    expiresAt: new Date(Date.now() + 60 * 60000).toISOString(),
    logCount: 0,
    traceCount: 0,
    screenshotCount: 0,
    createdBy: 'test_admin',
    ...overrides,
  };
}

// ─── Session CRUD Tests ────────────────────────────────────────────────────

describe('Session CRUD', () => {
  // BUG-7 FIX: Generate unique productId per test run for isolation
  const productId = `test_product_${randomUUID().substring(0, 8)}`;

  it('should create a session', async () => {
    const session = createTestSession(productId);
    const created = await repo.createSession(session);
    expect(created.id).toBe(session.id);
    expect(created.status).toBe('pending');
  });

  it('should get a session by id', async () => {
    const session = createTestSession(productId);
    await repo.createSession(session);

    const found = await repo.getSession(session.id);
    expect(found).not.toBeNull();
    expect(found?.id).toBe(session.id);
  });

  it('should return null for non-existent session', async () => {
    const found = await repo.getSession('non_existent_id');
    expect(found).toBeNull();
  });

  it('should update a session', async () => {
    const session = createTestSession(productId);
    await repo.createSession(session);

    // Small delay to ensure different timestamp
    await new Promise(r => setTimeout(r, 10));

    const updated = await repo.updateSession(session.id, { status: 'active' });
    expect(updated).not.toBeNull();
    expect(updated?.status).toBe('active');
    expect(updated?.updatedAt).toBeDefined();
    expect(new Date(updated!.updatedAt).getTime()).toBeGreaterThanOrEqual(
      new Date(session.updatedAt).getTime()
    );
  });

  it('should return null when updating non-existent session', async () => {
    const updated = await repo.updateSession('non_existent', { status: 'active' });
    expect(updated).toBeNull();
  });

  it('should soft delete (cancel) a session', async () => {
    const session = createTestSession(productId);
    await repo.createSession(session);

    const result = await repo.deleteSession(session.id);
    expect(result).toBe(true);

    const found = await repo.getSession(session.id);
    expect(found?.status).toBe('cancelled');
  });

  it('should list sessions with filters', async () => {
    const session1 = createTestSession(productId, { status: 'active' });
    const session2 = createTestSession(productId, { status: 'pending' });
    await repo.createSession(session1);
    await repo.createSession(session2);

    const { sessions } = await repo.listSessions({
      productId,
      status: 'active',
      limit: 10,
      offset: 0,
    });

    expect(sessions.length).toBeGreaterThanOrEqual(1);
    expect(sessions.some(s => s.id === session1.id)).toBe(true);
  });
});

// ─── Trace Ingest Tests ────────────────────────────────────────────────────

describe('Trace Ingest', () => {
  // BUG-7 FIX: Generate unique identifiers per test run for isolation
  const productId = `test_product_${randomUUID().substring(0, 8)}`;
  const sessionId = generateId('ds');

  it('should ingest traces', async () => {
    const traces: DebugTraceDoc[] = [
      {
        id: generateId('tr'),
        pk: `${productId}:${sessionId}`,
        sessionId,
        productId,
        traceId: randomUUID(),
        spanId: randomUUID(),
        name: 'test_operation',
        startTime: new Date().toISOString(),
        durationMs: 100,
        attributes: {},
        status: 'ok',
      },
    ];

    await repo.ingestTraces(productId, sessionId, traces);

    const { traces: found } = await repo.getTraces(productId, sessionId, { limit: 10 });
    expect(found.length).toBeGreaterThanOrEqual(1);
  });

  it('should query traces for a session', async () => {
    const traceId = randomUUID();
    const spanId = randomUUID();

    const traces: DebugTraceDoc[] = [
      {
        id: generateId('tr'),
        pk: `${productId}:${sessionId}`,
        sessionId,
        productId,
        traceId,
        spanId,
        name: 'test_query',
        startTime: new Date().toISOString(),
        durationMs: 50,
        attributes: { key: 'value' },
        status: 'ok',
      },
    ];

    await repo.ingestTraces(productId, sessionId, traces);

    const { traces: found } = await repo.getTraces(productId, sessionId, { limit: 10 });
    expect(found.some(t => t.name === 'test_query')).toBe(true);
  });
});

// ─── Log Ingest Tests ────────────────────────────────────────────────────

describe('Log Ingest', () => {
  // BUG-7 FIX: Generate unique identifiers per test run for isolation
  const productId = `test_product_${randomUUID().substring(0, 8)}`;
  const sessionId = generateId('ds');

  it('should ingest logs', async () => {
    const logs: DebugLogEntryDoc[] = [
      {
        id: generateId('log'),
        pk: `${productId}:${sessionId}`,
        sessionId,
        productId,
        level: 'info',
        message: 'Test log message',
        timestamp: new Date().toISOString(),
        module: 'TestModule',
        context: {},
      },
    ];

    await repo.ingestLogs(productId, sessionId, logs);

    const { logs: found } = await repo.getLogs(productId, sessionId, { limit: 10 });
    expect(found.length).toBeGreaterThanOrEqual(1);
  });

  it('should filter logs by level', async () => {
    const logs: DebugLogEntryDoc[] = [
      {
        id: generateId('log'),
        pk: `${productId}:${sessionId}`,
        sessionId,
        productId,
        level: 'error',
        message: 'Error message',
        timestamp: new Date().toISOString(),
        module: 'TestModule',
        context: {},
      },
      {
        id: generateId('log'),
        pk: `${productId}:${sessionId}`,
        sessionId,
        productId,
        level: 'info',
        message: 'Info message',
        timestamp: new Date().toISOString(),
        module: 'TestModule',
        context: {},
      },
    ];

    await repo.ingestLogs(productId, sessionId, logs);

    const { logs: found } = await repo.getLogs(productId, sessionId, {
      level: 'error',
      limit: 10,
    });

    expect(found.every(l => l.level === 'error')).toBe(true);
  });

  it('should search logs by message content', async () => {
    const logs: DebugLogEntryDoc[] = [
      {
        id: generateId('log'),
        pk: `${productId}:${sessionId}`,
        sessionId,
        productId,
        level: 'info',
        message: 'Searchable unique message content',
        timestamp: new Date().toISOString(),
        module: 'TestModule',
        context: {},
      },
    ];

    await repo.ingestLogs(productId, sessionId, logs);

    const { logs: found } = await repo.getLogs(productId, sessionId, {
      search: 'unique message',
      limit: 10,
    });

    expect(found.some(l => l.message.includes('unique message'))).toBe(true);
  });
});

// ─── Schema Validation Tests ───────────────────────────────────────────────

describe('Schema Validation', () => {
  it('should validate CreateDebugSessionSchema', () => {
    const input: CreateDebugSessionInput = {
      productId: 'test_product',
      targetUserId: 'user_123',
      collectionLevel: 'debug',
      captureLogs: true,
      captureNetwork: true,
      captureScreenshots: false,
      screenshotOnError: true,
      maxDurationMinutes: 60,
    };

    const result = CreateDebugSessionSchema.parse(input);
    expect(result.productId).toBe('test_product');
    expect(result.maxDurationMinutes).toBe(60);
  });

  it('should validate UpdateDebugSessionSchema', () => {
    const input: UpdateDebugSessionInput = {
      status: 'active',
      collectionLevel: 'trace',
    };

    const result = UpdateDebugSessionSchema.parse(input);
    expect(result.status).toBe('active');
  });

  it('should validate IngestTracesSchema', () => {
    const input: IngestTracesInput = {
      sessionId: 'ds_123',
      traces: [
        {
          traceId: randomUUID(),
          spanId: randomUUID(),
          name: 'test',
          startTime: new Date().toISOString(),
          status: 'ok',
          attributes: {},
        },
      ],
    };

    const result = IngestTracesSchema.parse(input);
    expect(result.traces).toHaveLength(1);
  });

  it('should validate IngestLogsSchema', () => {
    const input: IngestLogsInput = {
      sessionId: 'ds_123',
      logs: [
        {
          level: 'info',
          message: 'Test message',
          timestamp: new Date().toISOString(),
          module: 'Test',
          context: {},
        },
      ],
    };

    const result = IngestLogsSchema.parse(input);
    expect(result.logs).toHaveLength(1);
  });

  it('should reject invalid log level', () => {
    expect(() =>
      IngestLogsSchema.parse({
        sessionId: 'ds_123',
        logs: [
          {
            level: 'invalid_level',
            message: 'Test',
            timestamp: new Date().toISOString(),
            module: 'Test',
            context: {},
          },
        ],
      })
    ).toThrow();
  });
});
