/**
 * Promotion Pipeline Tests (TDD)
 *
 * Comprehensive test suite for the automated promotion pipeline.
 * Tests all stages: validate → test → approve → deploy
 * Includes approval gates, rollback, and audit trail.
 *
 * Target coverage: >90%
 */

import * as fs from 'fs';
import * as path from 'path';
import { promisify } from 'util';
import { DatabaseService } from '../src/lib/database-service';
import {
  PromotionPipeline,
  PromotionRequest,
  PromotionResult,
  PromotionStage,
} from '../src/services/promotion-pipeline';

const fsWriteFile = promisify(fs.writeFile);
const fsMkdir = promisify(fs.mkdir);
const fsRmdir = promisify(fs.rmdir);
const fsUnlink = promisify(fs.unlink);
const fsChmod = promisify(fs.chmod);
const fsReaddir = promisify(fs.readdir);

// Test paths
const TEST_DB_PATH = '/tmp/test-promotion-pipeline.db';
const TEST_SKILLS_DIR = '/tmp/test-promotion-pipeline-skills';
const TEST_STAGING_DIR = path.join(TEST_SKILLS_DIR, 'staging');
const TEST_PRODUCTION_DIR = path.join(TEST_SKILLS_DIR, 'production');

/**
 * Helper: Create test skill directory structure
 */
async function createTestSkillDirectory(skillName: string, version: string): Promise<string> {
  const skillPath = path.join(TEST_STAGING_DIR, skillName);
  await fsMkdir(skillPath, { recursive: true });

  // Create SKILL.md with valid frontmatter
  const skillMdContent = `---
name: ${skillName}
version: ${version}
description: Test skill for promotion pipeline
author: Test Author
dependencies: []
tags: [test, promotion]
---

# ${skillName}

This is a test skill for the promotion pipeline.

## Usage

Run with execute.sh
`;

  await fsWriteFile(path.join(skillPath, 'SKILL.md'), skillMdContent);

  // Create execute.sh (required executable)
  const executeScript = `#!/bin/bash
set -euo pipefail

echo "Executing ${skillName} version ${version}"
exit 0
`;

  await fsWriteFile(path.join(skillPath, 'execute.sh'), executeScript);
  await fsChmod(path.join(skillPath, 'execute.sh'), 0o755);

  // Create test.sh (required for testing stage)
  const testScript = `#!/bin/bash
set -euo pipefail

echo "Testing ${skillName} version ${version}"

# Simulate successful tests
if [[ "${skillName}" == "failing-skill" ]]; then
  echo "Test failed for ${skillName}" >&2
  exit 1
fi

echo "All tests passed"
exit 0
`;

  await fsWriteFile(path.join(skillPath, 'test.sh'), testScript);
  await fsChmod(path.join(skillPath, 'test.sh'), 0o755);

  return skillPath;
}

/**
 * Helper: Clean up test directories
 */
async function cleanupTestDirs(): Promise<void> {
  try {
    await fsRmdir(TEST_SKILLS_DIR, { recursive: true });
  } catch (err) {
    // Ignore cleanup errors
  }
  try {
    await fsUnlink(TEST_DB_PATH);
  } catch (err) {
    // Ignore cleanup errors
  }
}

/**
 * Helper: Initialize test database
 */
async function initializeTestDatabase(dbService: DatabaseService): Promise<void> {
  const adapter = dbService.getAdapter('sqlite');

  // Create all required tables
  await adapter.query(`
    CREATE TABLE IF NOT EXISTS promotions (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      skill_id TEXT NOT NULL UNIQUE,
      from_version TEXT NOT NULL,
      to_version TEXT NOT NULL,
      status TEXT NOT NULL DEFAULT 'pending',
      approved_by TEXT,
      requested_by TEXT NOT NULL,
      reason TEXT,
      created_at TEXT NOT NULL,
      updated_at TEXT NOT NULL
    )
  `);

  await adapter.query(`
    CREATE TABLE IF NOT EXISTS promotion_stages (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      promotion_id INTEGER NOT NULL,
      stage TEXT NOT NULL,
      status TEXT NOT NULL DEFAULT 'pending',
      result_message TEXT,
      duration_ms INTEGER,
      confidence_score REAL,
      started_at TEXT,
      completed_at TEXT,
      FOREIGN KEY (promotion_id) REFERENCES promotions(id)
    )
  `);

  await adapter.query(`
    CREATE TABLE IF NOT EXISTS promotion_audit (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      promotion_id INTEGER NOT NULL,
      action TEXT NOT NULL,
      actor TEXT NOT NULL,
      timestamp TEXT NOT NULL,
      details TEXT,
      FOREIGN KEY (promotion_id) REFERENCES promotions(id)
    )
  `);

  await adapter.query(`
    CREATE TABLE IF NOT EXISTS promotion_rollbacks (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      promotion_id INTEGER NOT NULL,
      from_version TEXT NOT NULL,
      to_version TEXT NOT NULL,
      reason TEXT,
      rolled_back_by TEXT NOT NULL,
      timestamp TEXT NOT NULL,
      FOREIGN KEY (promotion_id) REFERENCES promotions(id)
    )
  `);
}

describe('PromotionPipeline', () => {
  let dbService: DatabaseService;
  let pipeline: PromotionPipeline;

  beforeAll(async () => {
    await cleanupTestDirs();
    await fsMkdir(TEST_STAGING_DIR, { recursive: true });
    await fsMkdir(TEST_PRODUCTION_DIR, { recursive: true });

    dbService = new DatabaseService({
      type: 'sqlite',
      path: TEST_DB_PATH,
    });

    await initializeTestDatabase(dbService);

    pipeline = new PromotionPipeline(dbService, {
      stagingDir: TEST_STAGING_DIR,
      productionDir: TEST_PRODUCTION_DIR,
      autoApprovalConfidenceThreshold: 0.9,
    });
  });

  afterAll(async () => {
    await cleanupTestDirs();
  });

  describe('Stage 1: Validation', () => {
    it('should validate skill structure and frontmatter', async () => {
      const skillPath = await createTestSkillDirectory('test-skill-1', '1.0.0');

      const request: PromotionRequest = {
        skillId: 'test-skill-1',
        fromVersion: '0.0.0',
        toVersion: '1.0.0',
        requestedBy: 'test-user',
        reason: 'Initial release',
      };

      const result = await pipeline.validateStage(skillPath, request);

      expect(result.passed).toBe(true);
      expect(result.stage).toBe('validate');
      expect(result.confidence).toBeGreaterThanOrEqual(0.8);
      expect(result.errors).toHaveLength(0);
    });

    it('should reject skill without SKILL.md', async () => {
      await fsMkdir(path.join(TEST_STAGING_DIR, 'no-skillmd'), { recursive: true });

      const skillPath = path.join(TEST_STAGING_DIR, 'no-skillmd');
      const request: PromotionRequest = {
        skillId: 'no-skillmd',
        fromVersion: '0.0.0',
        toVersion: '1.0.0',
        requestedBy: 'test-user',
        reason: 'Test',
      };

      const result = await pipeline.validateStage(skillPath, request);

      expect(result.passed).toBe(false);
      expect(result.errors.length).toBeGreaterThan(0);
      expect(result.errors.some(e => e.includes('SKILL.md'))).toBe(true);
    });

    it('should reject skill without execute.sh', async () => {
      await fsMkdir(path.join(TEST_STAGING_DIR, 'no-execute'), { recursive: true });

      const skillPath = path.join(TEST_STAGING_DIR, 'no-execute');
      await fsWriteFile(
        path.join(skillPath, 'SKILL.md'),
        '---\nname: no-execute\nversion: 1.0.0\n---\n'
      );

      const request: PromotionRequest = {
        skillId: 'no-execute',
        fromVersion: '0.0.0',
        toVersion: '1.0.0',
        requestedBy: 'test-user',
        reason: 'Test',
      };

      const result = await pipeline.validateStage(skillPath, request);

      expect(result.passed).toBe(false);
      expect(result.errors.some(e => e.includes('execute.sh'))).toBe(true);
    });

    it('should detect invalid semantic version', async () => {
      const skillPath = await createTestSkillDirectory('invalid-version', 'not-a-version');

      const request: PromotionRequest = {
        skillId: 'invalid-version',
        fromVersion: '0.0.0',
        toVersion: 'not-a-version',
        requestedBy: 'test-user',
        reason: 'Test',
      };

      const result = await pipeline.validateStage(skillPath, request);

      expect(result.passed).toBe(false);
      expect(result.errors.some(e => e.includes('version'))).toBe(true);
    });
  });

  describe('Stage 2: Testing', () => {
    it('should execute tests and pass', async () => {
      const skillPath = await createTestSkillDirectory('passing-skill', '1.0.0');

      const request: PromotionRequest = {
        skillId: 'passing-skill',
        fromVersion: '0.0.0',
        toVersion: '1.0.0',
        requestedBy: 'test-user',
        reason: 'Test',
      };

      const result = await pipeline.testStage(skillPath, request);

      expect(result.passed).toBe(true);
      expect(result.stage).toBe('test');
      expect(result.testsPassed).toBe(true);
      expect(result.coverage).toBeDefined();
    });

    it('should execute tests and fail', async () => {
      const skillPath = await createTestSkillDirectory('failing-skill', '1.0.0');

      const request: PromotionRequest = {
        skillId: 'failing-skill',
        fromVersion: '0.0.0',
        toVersion: '1.0.0',
        requestedBy: 'test-user',
        reason: 'Test',
      };

      const result = await pipeline.testStage(skillPath, request);

      expect(result.passed).toBe(false);
      expect(result.testsPassed).toBe(false);
      expect(result.errors.length).toBeGreaterThan(0);
    });

    it('should handle missing test.sh gracefully', async () => {
      const skillPath = path.join(TEST_STAGING_DIR, 'no-tests');
      await fsMkdir(skillPath, { recursive: true });

      const skillMdContent = `---
name: no-tests
version: 1.0.0
description: Test
---
`;

      await fsWriteFile(path.join(skillPath, 'SKILL.md'), skillMdContent);
      await fsWriteFile(
        path.join(skillPath, 'execute.sh'),
        '#!/bin/bash\necho "test"\n'
      );
      await fsChmod(path.join(skillPath, 'execute.sh'), 0o755);

      const request: PromotionRequest = {
        skillId: 'no-tests',
        fromVersion: '0.0.0',
        toVersion: '1.0.0',
        requestedBy: 'test-user',
        reason: 'Test',
      };

      const result = await pipeline.testStage(skillPath, request);

      expect(result.passed).toBe(true); // No tests = pass
      expect(result.testsPassed).toBe(true);
    });
  });

  describe('Stage 3: Approval Gate', () => {
    it('should auto-approve when confidence >0.90', async () => {
      const skillPath = await createTestSkillDirectory('auto-approve-skill', '1.0.0');

      const request: PromotionRequest = {
        skillId: 'auto-approve-skill',
        fromVersion: '0.0.0',
        toVersion: '1.0.0',
        requestedBy: 'test-user',
        reason: 'Test',
      };

      const validation = await pipeline.validateStage(skillPath, request);
      const testing = await pipeline.testStage(skillPath, request);

      // Confidence > 0.90 should auto-approve
      const approval = await pipeline.approvalStage(request, [
        { stage: 'validate', confidence: 0.95, passed: true },
        { stage: 'test', confidence: 0.92, passed: true },
      ]);

      expect(approval.approved).toBe(true);
      expect(approval.autoApproved).toBe(true);
      expect(approval.approvedBy).toBe('system');
    });

    it('should require manual approval when confidence <0.90', async () => {
      const request: PromotionRequest = {
        skillId: 'manual-approve-skill',
        fromVersion: '0.0.0',
        toVersion: '1.0.0',
        requestedBy: 'test-user',
        reason: 'Test',
      };

      const approval = await pipeline.approvalStage(request, [
        { stage: 'validate', confidence: 0.85, passed: true },
        { stage: 'test', confidence: 0.88, passed: true },
      ]);

      expect(approval.approved).toBe(false);
      expect(approval.autoApproved).toBe(false);
      expect(approval.requiresManualApproval).toBe(true);
    });

    it('should reject if any stage failed', async () => {
      const request: PromotionRequest = {
        skillId: 'failed-skill',
        fromVersion: '0.0.0',
        toVersion: '1.0.0',
        requestedBy: 'test-user',
        reason: 'Test',
      };

      const approval = await pipeline.approvalStage(request, [
        { stage: 'validate', confidence: 0.95, passed: true },
        { stage: 'test', confidence: 0.0, passed: false },
      ]);

      expect(approval.approved).toBe(false);
      expect(approval.autoApproved).toBe(false);
    });

    it('should support manual approval override', async () => {
      const request: PromotionRequest = {
        skillId: 'manual-override',
        fromVersion: '0.0.0',
        toVersion: '1.0.0',
        requestedBy: 'test-user',
        reason: 'Test',
      };

      const approval = await pipeline.approveManually(request, 'approver-user', 'Approved for testing');

      expect(approval.approved).toBe(true);
      expect(approval.autoApproved).toBe(false);
      expect(approval.approvedBy).toBe('approver-user');
      expect(approval.approvalReason).toBe('Approved for testing');
    });
  });

  describe('Stage 4: Deployment', () => {
    it('should deploy approved promotion to production', async () => {
      const skillPath = await createTestSkillDirectory('deploy-skill', '2.0.0');

      const request: PromotionRequest = {
        skillId: 'deploy-skill',
        fromVersion: '1.0.0',
        toVersion: '2.0.0',
        requestedBy: 'test-user',
        reason: 'Production release',
      };

      const deployment = await pipeline.deployStage(skillPath, request);

      expect(deployment.success).toBe(true);
      expect(deployment.stage).toBe('deploy');
      expect(deployment.productionPath).toBeDefined();

      // Verify file was moved to production
      const productionPath = path.join(TEST_PRODUCTION_DIR, 'deploy-skill');
      expect(fs.existsSync(productionPath)).toBe(true);
    });

    it('should make deployment atomic', async () => {
      const skillPath = await createTestSkillDirectory('atomic-deploy', '1.0.0');

      const request: PromotionRequest = {
        skillId: 'atomic-deploy',
        fromVersion: '0.0.0',
        toVersion: '1.0.0',
        requestedBy: 'test-user',
        reason: 'Test atomic',
      };

      const deployment = await pipeline.deployStage(skillPath, request);

      expect(deployment.success).toBe(true);

      // Skill should be in production
      const prodPath = path.join(TEST_PRODUCTION_DIR, 'atomic-deploy');
      expect(fs.existsSync(prodPath)).toBe(true);

      // Original staging should be removed
      expect(fs.existsSync(skillPath)).toBe(false);
    });
  });

  describe('Full Promotion Workflow', () => {
    it('should complete full workflow: validate → test → approve → deploy', async () => {
      const skillPath = await createTestSkillDirectory('full-workflow', '1.0.0');

      const request: PromotionRequest = {
        skillId: 'full-workflow',
        fromVersion: '0.0.0',
        toVersion: '1.0.0',
        requestedBy: 'test-user',
        reason: 'Complete workflow test',
      };

      const result = await pipeline.promote(request, skillPath);

      expect(result.success).toBe(true);
      expect(result.skillId).toBe('full-workflow');
      expect(result.toVersion).toBe('1.0.0');
      expect(result.promotedAt).toBeDefined();

      // Verify production deployment
      const productionPath = path.join(TEST_PRODUCTION_DIR, 'full-workflow');
      expect(fs.existsSync(productionPath)).toBe(true);
    });

    it('should fail workflow if validation fails', async () => {
      const skillPath = path.join(TEST_STAGING_DIR, 'bad-validate');
      await fsMkdir(skillPath, { recursive: true });
      await fsWriteFile(path.join(skillPath, 'SKILL.md'), 'invalid');

      const request: PromotionRequest = {
        skillId: 'bad-validate',
        fromVersion: '0.0.0',
        toVersion: '1.0.0',
        requestedBy: 'test-user',
        reason: 'Test',
      };

      const result = await pipeline.promote(request, skillPath);

      expect(result.success).toBe(false);
      expect(result.failedStage).toBe('validate');
      expect(result.error).toBeDefined();
    });

    it('should fail workflow if tests fail', async () => {
      const skillPath = await createTestSkillDirectory('failing-skill', '1.0.0');

      const request: PromotionRequest = {
        skillId: 'failing-skill',
        fromVersion: '0.0.0',
        toVersion: '1.0.0',
        requestedBy: 'test-user',
        reason: 'Test',
      };

      const result = await pipeline.promote(request, skillPath);

      expect(result.success).toBe(false);
      expect(result.failedStage).toBe('test');
    });

    it('should stop at approval gate for low confidence', async () => {
      // This test would require mocking validation/test to return low confidence
      // For now, we'll create a skill that has valid structure but is marked for manual approval
      const skillPath = await createTestSkillDirectory('manual-approval', '1.0.0');

      const request: PromotionRequest = {
        skillId: 'manual-approval',
        fromVersion: '0.0.0',
        toVersion: '1.0.0',
        requestedBy: 'test-user',
        reason: 'Requires manual review',
      };

      // Assuming this skill has confidence < 0.90
      const result = await pipeline.promote(request, skillPath);

      // Should either be pending approval or auto-approved depending on confidence
      expect(result.success !== undefined).toBe(true);
    });
  });

  describe('Rollback Capability', () => {
    it('should rollback to previous version', async () => {
      const skillPath = await createTestSkillDirectory('rollback-skill', '2.0.0');
      const oldPath = path.join(TEST_PRODUCTION_DIR, 'rollback-skill');
      await fsMkdir(oldPath, { recursive: true });
      await fsWriteFile(path.join(oldPath, 'SKILL.md'), '---\nname: rollback-skill\nversion: 1.0.0\n---\n');

      const request: PromotionRequest = {
        skillId: 'rollback-skill',
        fromVersion: '1.0.0',
        toVersion: '2.0.0',
        requestedBy: 'test-user',
        reason: 'Test rollback',
      };

      // First promote
      const promotion = await pipeline.promote(request, skillPath);
      expect(promotion.success).toBe(true);

      // Then rollback
      const rollback = await pipeline.rollback(
        'rollback-skill',
        '2.0.0',
        '1.0.0',
        'rollback-user',
        'Critical bug found'
      );

      expect(rollback.success).toBe(true);
      expect(rollback.message).toContain('rolled back');

      // Verify audit trail
      const auditRecords = await pipeline.getAuditTrail('rollback-skill');
      expect(auditRecords.length).toBeGreaterThan(0);
      expect(auditRecords.some(r => r.action === 'rollback')).toBe(true);
    });

    it('should be atomic rollback', async () => {
      const skillPath = await createTestSkillDirectory('atomic-rollback', '2.0.0');

      const request: PromotionRequest = {
        skillId: 'atomic-rollback',
        fromVersion: '1.0.0',
        toVersion: '2.0.0',
        requestedBy: 'test-user',
        reason: 'Test',
      };

      const promotion = await pipeline.promote(request, skillPath);
      expect(promotion.success).toBe(true);

      const rollback = await pipeline.rollback(
        'atomic-rollback',
        '2.0.0',
        '1.0.0',
        'rollback-user',
        'Testing atomic rollback'
      );

      expect(rollback.success).toBe(true);
      // Audit should show rollback action
      const audit = await pipeline.getAuditTrail('atomic-rollback');
      expect(audit.some(r => r.action === 'rollback')).toBe(true);
    });
  });

  describe('Audit Trail', () => {
    it('should create audit trail for each promotion', async () => {
      const skillPath = await createTestSkillDirectory('audit-skill', '1.0.0');

      const request: PromotionRequest = {
        skillId: 'audit-skill',
        fromVersion: '0.0.0',
        toVersion: '1.0.0',
        requestedBy: 'test-user',
        reason: 'Test audit trail',
      };

      const result = await pipeline.promote(request, skillPath);
      expect(result.success).toBe(true);

      // Get audit trail
      const audit = await pipeline.getAuditTrail('audit-skill');

      expect(audit.length).toBeGreaterThan(0);
      expect(audit[0].skillId).toBe('audit-skill');
      expect(audit[0].action).toBe('promote');
      expect(audit[0].actor).toBe('test-user');
      expect(audit[0].timestamp).toBeDefined();
    });

    it('should include approval details in audit', async () => {
      const skillPath = await createTestSkillDirectory('audit-approve', '1.0.0');

      const request: PromotionRequest = {
        skillId: 'audit-approve',
        fromVersion: '0.0.0',
        toVersion: '1.0.0',
        requestedBy: 'test-user',
        reason: 'Test',
      };

      const result = await pipeline.promote(request, skillPath);
      expect(result.success).toBe(true);

      const audit = await pipeline.getAuditTrail('audit-approve');
      const approvalRecord = audit.find(r => r.action === 'approve');

      expect(approvalRecord).toBeDefined();
      if (approvalRecord) {
        expect(approvalRecord.details).toBeDefined();
        expect(JSON.parse(approvalRecord.details || '{}')).toHaveProperty('approvedBy');
      }
    });

    it('should record who promoted, when, and why', async () => {
      const skillPath = await createTestSkillDirectory('audit-metadata', '1.0.0');

      const request: PromotionRequest = {
        skillId: 'audit-metadata',
        fromVersion: '0.0.0',
        toVersion: '1.0.0',
        requestedBy: 'specific-user',
        reason: 'Specific reason for audit',
      };

      const result = await pipeline.promote(request, skillPath);
      expect(result.success).toBe(true);

      const audit = await pipeline.getAuditTrail('audit-metadata');
      const promotionRecord = audit.find(r => r.action === 'promote');

      expect(promotionRecord?.actor).toBe('specific-user');
      expect(promotionRecord?.skillId).toBe('audit-metadata');
      const details = JSON.parse(promotionRecord?.details || '{}');
      expect(details.reason).toBe('Specific reason for audit');
    });
  });

  describe('SLA Tracking', () => {
    it('should track time from submit to promote', async () => {
      const skillPath = await createTestSkillDirectory('sla-skill', '1.0.0');

      const request: PromotionRequest = {
        skillId: 'sla-skill',
        fromVersion: '0.0.0',
        toVersion: '1.0.0',
        requestedBy: 'test-user',
        reason: 'SLA test',
      };

      const beforeTime = Date.now();
      const result = await pipeline.promote(request, skillPath);
      const afterTime = Date.now();

      expect(result.success).toBe(true);
      expect(result.submittedAt).toBeDefined();
      expect(result.promotedAt).toBeDefined();

      const submittedMs = new Date(result.submittedAt!).getTime();
      const promotedMs = new Date(result.promotedAt!).getTime();

      expect(promotedMs - submittedMs).toBeGreaterThanOrEqual(0);
      expect(promotedMs - submittedMs).toBeLessThan(afterTime - beforeTime + 100);
    });
  });

  describe('Error Handling', () => {
    it('should handle missing skill gracefully', async () => {
      const nonexistentPath = path.join(TEST_STAGING_DIR, 'nonexistent');

      const request: PromotionRequest = {
        skillId: 'nonexistent',
        fromVersion: '0.0.0',
        toVersion: '1.0.0',
        requestedBy: 'test-user',
        reason: 'Test',
      };

      const result = await pipeline.promote(request, nonexistentPath);

      expect(result.success).toBe(false);
      expect(result.error).toBeDefined();
    });

    it('should handle database errors gracefully', async () => {
      const skillPath = await createTestSkillDirectory('db-error', '1.0.0');

      const request: PromotionRequest = {
        skillId: 'db-error',
        fromVersion: '0.0.0',
        toVersion: '1.0.0',
        requestedBy: 'test-user',
        reason: 'Test',
      };

      // This would require closing the database to trigger an error
      // For now, we just verify the structure handles errors
      const result = await pipeline.promote(request, skillPath);

      expect(result.success !== undefined).toBe(true);
    });
  });

  describe('Notification System', () => {
    it('should notify on successful promotion', async () => {
      let notificationSent = false;
      let notificationPayload: any;

      pipeline.on('promotion-success', (payload) => {
        notificationSent = true;
        notificationPayload = payload;
      });

      const skillPath = await createTestSkillDirectory('notify-success', '1.0.0');

      const request: PromotionRequest = {
        skillId: 'notify-success',
        fromVersion: '0.0.0',
        toVersion: '1.0.0',
        requestedBy: 'test-user',
        reason: 'Test notification',
      };

      const result = await pipeline.promote(request, skillPath);

      expect(result.success).toBe(true);
      expect(notificationSent).toBe(true);
      expect(notificationPayload?.skillId).toBe('notify-success');
    });

    it('should notify on promotion failure', async () => {
      let failureNotificationSent = false;
      let failurePayload: any;

      pipeline.on('promotion-failure', (payload) => {
        failureNotificationSent = true;
        failurePayload = payload;
      });

      const badPath = path.join(TEST_STAGING_DIR, 'notify-failure');
      await fsMkdir(badPath, { recursive: true });
      await fsWriteFile(path.join(badPath, 'SKILL.md'), 'invalid');

      const request: PromotionRequest = {
        skillId: 'notify-failure',
        fromVersion: '0.0.0',
        toVersion: '1.0.0',
        requestedBy: 'test-user',
        reason: 'Test',
      };

      const result = await pipeline.promote(request, badPath);

      expect(result.success).toBe(false);
      expect(failureNotificationSent).toBe(true);
      expect(failurePayload?.error).toBeDefined();
    });
  });

  describe('Concurrency & Locking', () => {
    it('should prevent concurrent promotions of same skill', async () => {
      const skillPath = await createTestSkillDirectory('concurrent-test', '1.0.0');

      const request: PromotionRequest = {
        skillId: 'concurrent-test',
        fromVersion: '0.0.0',
        toVersion: '1.0.0',
        requestedBy: 'test-user',
        reason: 'Test',
      };

      // Start two promotions
      const promise1 = pipeline.promote(request, skillPath);

      // Second request should fail due to lock
      const request2 = { ...request };
      const skillPath2 = skillPath; // Same skill

      // These should not both succeed
      const [result1, result2] = await Promise.all([
        promise1,
        pipeline.promote(request2, skillPath2).catch(e => ({ success: false, error: e.message })),
      ]);

      // At least one should succeed, the other should fail
      const successCount = [result1, result2].filter(r => r.success).length;
      expect(successCount).toBeLessThanOrEqual(1);
    });
  });
});

describe('PromotionPipeline - Configuration', () => {
  let dbService: DatabaseService;

  beforeAll(async () => {
    await cleanupTestDirs();
    await fsMkdir(TEST_STAGING_DIR, { recursive: true });
    await fsMkdir(TEST_PRODUCTION_DIR, { recursive: true });

    dbService = new DatabaseService({
      type: 'sqlite',
      path: TEST_DB_PATH,
    });

    await initializeTestDatabase(dbService);
  });

  afterAll(async () => {
    await cleanupTestDirs();
  });

  it('should allow custom approval threshold', async () => {
    const customPipeline = new PromotionPipeline(dbService, {
      stagingDir: TEST_STAGING_DIR,
      productionDir: TEST_PRODUCTION_DIR,
      autoApprovalConfidenceThreshold: 0.85,
    });

    const request: PromotionRequest = {
      skillId: 'threshold-test',
      fromVersion: '0.0.0',
      toVersion: '1.0.0',
      requestedBy: 'test-user',
      reason: 'Test',
    };

    // With 0.85 threshold, confidence of 0.87 should auto-approve
    const approval = await customPipeline.approvalStage(request, [
      { stage: 'validate', confidence: 0.87, passed: true },
      { stage: 'test', confidence: 0.88, passed: true },
    ]);

    expect(approval.approved).toBe(true);
    expect(approval.autoApproved).toBe(true);
  });

  it('should support custom staging/production directories', async () => {
    const customStaging = path.join(TEST_SKILLS_DIR, 'custom-staging');
    const customProduction = path.join(TEST_SKILLS_DIR, 'custom-production');

    await fsMkdir(customStaging, { recursive: true });
    await fsMkdir(customProduction, { recursive: true });

    const customPipeline = new PromotionPipeline(dbService, {
      stagingDir: customStaging,
      productionDir: customProduction,
    });

    expect(customPipeline).toBeDefined();
  });
});
