/**
 * Integration Test Harness - Main Entry Point
 * Provides reusable setup, teardown, and utilities for integration tests
 */

import { DockerManager } from './docker-manager';
import { DatabaseManager } from './database-manager';
import { ApiClient } from './api-client';
import { DataFactory } from './data-factory';

export interface IntegrationTestConfig {
  api: {
    baseUrl: string;
    port: number;
  };
  database: {
    host: string;
    port: number;
    database: string;
    user: string;
    password: string;
  };
  timeouts?: {
    setup?: number;
    teardown?: number;
    test?: number;
  };
}

export class IntegrationTestHarness {
  private docker: DockerManager;
  private database: DatabaseManager;
  private api: ApiClient;
  private factory: DataFactory;
  private config: IntegrationTestConfig;

  constructor(config: IntegrationTestConfig) {
    this.config = {
      timeouts: {
        setup: 120000,
        teardown: 30000,
        test: 60000,
        ...config.timeouts,
      },
      ...config,
    };

    this.docker = DockerManager.getInstance();
    this.database = new DatabaseManager(config.database);
    this.api = new ApiClient({ baseUrl: config.api.baseUrl, timeout: 60000 });
    this.factory = new DataFactory(this.database);
  }

  /**
   * Setup hook - starts Docker services and prepares database
   * Called once before all tests in a suite
   */
  async beforeAll(): Promise<void> {
    console.log('[Harness] Starting integration test setup...');
    
    // Start Docker services
    await this.docker.start();
    
    // Wait for database to be ready
    await this.database.waitForReady(this.config.timeouts.setup);
    
    // Wait for API to be ready
    await this.api.waitForReady(this.config.timeouts.setup);
    
    console.log('[Harness] ✓ Setup complete - all services ready');
  }

  /**
   * Teardown hook - stops Docker services and cleans up
   * Called once after all tests in a suite
   */
  async afterAll(): Promise<void> {
    console.log('[Harness] Starting integration test teardown...');
    
    try {
      await this.database.close();
      this.docker.stop();
      console.log('[Harness] ✓ Teardown complete');
    } catch (error) {
      console.warn('[Harness] Teardown warning:', error);
    }
  }

  /**
   * Setup hook - prepares database for each test
   * Called before each test
   */
  async beforeEach(): Promise<void> {
    // Truncate all tables to ensure clean state
    await this.database.truncateAllTables();
    
    // Optionally seed minimal required data
    // await this.database.seedMinimalData();
  }

  /**
   * Teardown hook - cleanup after each test
   * Called after each test
   */
  async afterEach(): Promise<void> {
    // Clean up any test-specific resources
    // This can be extended by individual tests
  }

  /**
   * Get database manager
   */
  getDatabase(): DatabaseManager {
    return this.database;
  }

  /**
   * Get API client
   */
  getApi(): ApiClient {
    return this.api;
  }

  /**
   * Get Docker manager
   */
  getDocker(): DockerManager {
    return this.docker;
  }

  /**
   * Get data factory
   */
  getFactory(): DataFactory {
    return this.factory;
  }

  /**
   * Execute database transaction with automatic rollback
   * Useful for tests that need to verify transaction behavior
   */
  async withTransaction<T>(callback: (db: DatabaseManager) => Promise<T>): Promise<T> {
    await this.database.begin();
    try {
      const result = await callback(this.database);
      await this.database.rollback(); // Always rollback in tests
      return result;
    } catch (error) {
      await this.database.rollback();
      throw error;
    }
  }

  /**
   * Helper to verify constraint violations
   */
  async expectConstraintViolation(
    operation: () => Promise<unknown>,
    expectedConstraint?: string
  ): Promise<void> {
    try {
      await operation();
      throw new Error('Expected constraint violation but operation succeeded');
    } catch (error) {
      // Check if it's a constraint violation
      const message = error instanceof Error ? error.message : String(error);
      const isConstraintError = 
        message.includes('constraint') ||
        message.includes('23505') || // Unique violation
        message.includes('23503') || // Foreign key violation
        message.includes('23514');   // Check violation

      if (!isConstraintError) {
        throw new Error(`Expected constraint violation but got: ${message}`);
      }

      if (expectedConstraint && !message.includes(expectedConstraint)) {
        throw new Error(`Expected constraint '${expectedConstraint}' but got: ${message}`);
      }
    }
  }
}

// Default configuration for docker-compose.test.yml
export const DEFAULT_TEST_CONFIG: IntegrationTestConfig = {
  api: {
    baseUrl: 'http://localhost:3101',
    port: 3101,
  },
  database: {
    host: 'localhost',
    port: 5433,
    database: 'gridpilot_test',
    user: 'gridpilot_test_user',
    password: 'gridpilot_test_pass',
  },
  timeouts: {
    setup: 120000,
    teardown: 30000,
    test: 60000,
  },
};

/**
 * Create a test harness with default configuration
 */
export function createTestHarness(config?: Partial<IntegrationTestConfig>): IntegrationTestHarness {
  const mergedConfig = {
    ...DEFAULT_TEST_CONFIG,
    ...config,
    api: { ...DEFAULT_TEST_CONFIG.api, ...config?.api },
    database: { ...DEFAULT_TEST_CONFIG.database, ...config?.database },
    timeouts: { ...DEFAULT_TEST_CONFIG.timeouts, ...config?.timeouts },
  };
  return new IntegrationTestHarness(mergedConfig);
}