// tests/api/utils.ts

import { writeFile, mkdir } from 'fs/promises';
import { join } from 'path';
import { config, endpoints } from './config';

import { fileURLToPath } from 'url';
import { dirname } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

// Colors for console output
const colors = {
  reset: '\x1b[0m',
  green: '\x1b[32m',
  red: '\x1b[31m',
  yellow: '\x1b[33m',
  blue: '\x1b[34m',
  gray: '\x1b[90m',
  cyan: '\x1b[36m',
};

// Logging utilities
export const log = {
  success: (msg: string) => console.log(`${colors.green}✓${colors.reset} ${msg}`),
  error: (msg: string) => console.log(`${colors.red}✗${colors.reset} ${msg}`),
  info: (msg: string) => console.log(`${colors.blue}→${colors.reset} ${msg}`),
  warning: (msg: string) => console.log(`${colors.yellow}⚠${colors.reset} ${msg}`),
  section: (msg: string) => console.log(`\n${colors.cyan}━━━ ${msg} ━━━${colors.reset}`),
  detail: (key: string, value: any) => {
    const valueStr = typeof value === 'object' ? JSON.stringify(value, null, 2) : value;
    console.log(`  ${colors.gray}${key}:${colors.reset} ${valueStr}`);
  },
};

// API fetch wrapper
export async function api<T = any>(
  endpoint: string,
  options: RequestInit & {
    expectError?: boolean;
    timeout?: number;
  } = {}
): Promise<{
  data: T;
  status: number;
  headers: Headers;
  duration: number;
}> {
  const { expectError = false, timeout = config.requestTimeout, ...fetchOptions } = options;
  const url = `${config.baseURL}${endpoint}`;
  const startTime = Date.now();

  try {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), timeout);

    const response = await fetch(url, {
      ...fetchOptions,
      headers: {
        'X-API-Key': config.apiKey,
        ...fetchOptions.headers,
      },
      signal: controller.signal,
    });

    clearTimeout(timeoutId);
    const duration = Date.now() - startTime;

    let data: any;
    const contentType = response.headers.get('content-type');

    if (contentType?.includes('application/json')) {
      data = await response.json();
    } else if (contentType?.includes('image/')) {
      data = await response.arrayBuffer();
    } else {
      data = await response.text();
    }

    if (!response.ok && !expectError) {
      throw new Error(`HTTP ${response.status}: ${JSON.stringify(data)}`);
    }

    if (config.verbose) {
      const method = fetchOptions.method || 'GET';
      log.detail('Request', `${method} ${endpoint}`);
      log.detail('Status', response.status);
      log.detail('Duration', `${duration}ms`);
    }

    return {
      data,
      status: response.status,
      headers: response.headers,
      duration,
    };
  } catch (error) {
    const duration = Date.now() - startTime;
    if (!expectError) {
      log.error(`Request failed: ${error}`);
      log.detail('Endpoint', endpoint);
      log.detail('Duration', `${duration}ms`);
    }
    throw error;
  }
}

// Save image to results directory
export async function saveImage(
  buffer: ArrayBuffer,
  filename: string
): Promise<string> {
  const resultsPath = join(__dirname, config.resultsDir);

  try {
    await mkdir(resultsPath, { recursive: true });
  } catch (err) {
    // Directory exists, ignore
  }

  const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
  const fullFilename = `${timestamp}_${filename}`;
  const filepath = join(resultsPath, fullFilename);

  await writeFile(filepath, Buffer.from(buffer));

  if (config.saveImages) {
    log.info(`Saved image: ${fullFilename}`);
  }

  return filepath;
}

// Upload file helper
export async function uploadFile(
  filepath: string,
  fields: Record<string, string> = {}
): Promise<any> {
  const formData = new FormData();

  // Read file and detect MIME type from extension
  const fs = await import('fs/promises');
  const path = await import('path');
  const fileBuffer = await fs.readFile(filepath);
  const ext = path.extname(filepath).toLowerCase();
  const mimeTypes: Record<string, string> = {
    '.png': 'image/png',
    '.jpg': 'image/jpeg',
    '.jpeg': 'image/jpeg',
    '.webp': 'image/webp',
  };
  const mimeType = mimeTypes[ext] || 'application/octet-stream';
  const filename = path.basename(filepath);
  const blob = new Blob([fileBuffer], { type: mimeType });
  formData.append('file', blob, filename);

  // Add other fields
  for (const [key, value] of Object.entries(fields)) {
    formData.append(key, value);
  }

  const result = await api(endpoints.images + '/upload', {
    method: 'POST',
    body: formData,
    headers: {
      // Don't set Content-Type, let fetch set it with boundary
    },
  });

  return result.data.data;
}

// Wait helper
export async function wait(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// Poll for generation completion
export async function waitForGeneration(
  generationId: string,
  maxAttempts = 20
): Promise<any> {
  for (let i = 0; i < maxAttempts; i++) {
    const result = await api(`${endpoints.generations}/${generationId}`);
    const generation = result.data.data;

    if (generation.status === 'success' || generation.status === 'failed') {
      return generation;
    }

    await wait(1000);
  }

  throw new Error('Generation timeout');
}

// Test context to share data between tests
export const testContext: {
  imageId?: string;
  generationId?: string;
  flowId?: string;
  uploadedImageId?: string;
  [key: string]: any; // Allow dynamic properties
} = {};

// Test tracking state
let failedTests = 0;
let totalTests = 0;

// Test runner helper
export async function runTest(
  name: string,
  fn: () => Promise<void>
): Promise<boolean> {
  totalTests++;
  try {
    const startTime = Date.now();
    await fn();
    const duration = Date.now() - startTime;
    log.success(`${name} (${duration}ms)`);
    return true;
  } catch (error) {
    failedTests++;
    log.error(`${name}`);
    console.error(error);
    return false;
  }
}

// Get test statistics
export function getTestStats() {
  return { total: totalTests, failed: failedTests, passed: totalTests - failedTests };
}

// Exit with appropriate code based on test results
export function exitWithTestResults() {
  const stats = getTestStats();
  if (stats.failed > 0) {
    log.error(`${stats.failed}/${stats.total} tests failed`);
    process.exit(1);
  }
  log.success(`${stats.passed}/${stats.total} tests passed`);
  process.exit(0);
}

// Verify image is accessible at URL
export async function verifyImageAccessible(url: string): Promise<boolean> {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      return false;
    }
    const contentType = response.headers.get('content-type');
    if (!contentType?.includes('image/')) {
      log.warning(`URL returned non-image content type: ${contentType}`);
      return false;
    }
    const buffer = await response.arrayBuffer();
    return buffer.byteLength > 0;
  } catch (error) {
    log.warning(`Failed to access image: ${error}`);
    return false;
  }
}

// Helper to expect an error response
export async function expectError(
  fn: () => Promise<any>,
  expectedStatus?: number
): Promise<any> {
  try {
    const result = await fn();
    if (result.status >= 400) {
      // Error status returned
      if (expectedStatus && result.status !== expectedStatus) {
        throw new Error(`Expected status ${expectedStatus}, got ${result.status}`);
      }
      return result;
    }
    throw new Error(`Expected error but got success: ${result.status}`);
  } catch (error) {
    // If it's a fetch error or our assertion error, re-throw
    throw error;
  }
}

// Helper to create a test image via generation
export async function createTestImage(
  prompt: string,
  options: {
    aspectRatio?: string;
    alias?: string;
    flowId?: string | null;
    flowAlias?: string;
  } = {}
): Promise<any> {
  const result = await api(endpoints.generations, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      prompt,
      aspectRatio: options.aspectRatio || '1:1',
      alias: options.alias,
      flowId: options.flowId,
      flowAlias: options.flowAlias,
    }),
  });

  if (!result.data.data) {
    throw new Error('No generation returned');
  }

  // Wait for completion
  const generation = await waitForGeneration(result.data.data.id);

  if (generation.status !== 'success') {
    throw new Error(`Generation failed: ${generation.errorMessage}`);
  }

  return generation;
}

// Helper to resolve alias
// Returns format compatible with old /resolve/ endpoint: { imageId, scope, alias, image }
export async function resolveAlias(
  alias: string,
  flowId?: string
): Promise<any> {
  // Section 6.2: Use direct alias identifier instead of /resolve/ endpoint
  const endpoint = flowId
    ? `${endpoints.images}/${alias}?flowId=${flowId}`
    : `${endpoints.images}/${alias}`;

  const result = await api(endpoint);
  const image = result.data.data;

  // Determine scope based on alias type and context
  const technicalAliases = ['@last', '@first', '@upload'];
  let scope: string;
  if (technicalAliases.includes(alias)) {
    scope = 'technical';
  } else if (flowId) {
    scope = 'flow';
  } else {
    scope = 'project';
  }

  // Adapt response to match old /resolve/ format for test compatibility
  return {
    imageId: image.id,
    alias: image.alias || alias,
    scope,
    flowId: image.flowId,
    image,
  };
}
