import { ToolingError } from '../../../../../../types'
import { withRetry, createHttpError, DEFAULT_RETRY_POLICY } from '../../../../../../../../../platform/resilience'
import type { ExecutionContext } from '../../../../../../../base/types'

interface ApifyCredentials {
  apiToken: string
}

export interface RunActorParams {
  actorId: string
  input?: Record<string, unknown>
  timeoutSecs?: number
  pollIntervalSecs?: number
  maxItems?: number
}

export interface RunActorResult {
  status: 'SUCCEEDED' | 'FAILED' | 'ABORTED' | 'TIMED_OUT'
  runId: string
  datasetId: string
  items: unknown[]
  totalCount: number
  executionTimeMs: number
}

const APIFY_API_BASE = 'https://api.apify.com/v2'

/**
 * Run an Apify actor, wait for completion, and fetch results
 *
 * Combined operation that:
 * 1. Starts the actor run with provided input
 * 2. Polls for completion (SUCCEEDED, FAILED, ABORTED, TIMED-OUT)
 * 3. Fetches dataset items when complete
 *
 * TIMEOUT BEHAVIOR:
 * If actor exceeds timeoutSecs, returns status='TIMED_OUT' with empty items array.
 * Workflow developers should handle this by either:
 * - Increasing timeoutSecs for long-running actors
 * - Checking run status manually via Apify console
 * - Retrying with exponential backoff
 *
 * @param credentials - Apify API token
 * @param params - Actor ID, input, timeout, poll interval
 * @param context - Execution context for logging
 * @returns Run status, dataset items, and metadata
 */
export async function runActor(
  credentials: ApifyCredentials,
  params: RunActorParams,
  context?: ExecutionContext
): Promise<RunActorResult> {
  const { apiToken } = credentials
  const { actorId, input = {}, timeoutSecs = 300, pollIntervalSecs = 10, maxItems } = params

  if (!actorId) {
    throw new ToolingError('validation_error', 'Missing required parameter: actorId', {
      integration: 'apify',
      method: 'runActor'
    })
  }

  const startTime = Date.now()
  const timeoutMs = timeoutSecs * 1000

  // Step 1: Start the actor run
  // URL-encode the actorId to handle slashes (e.g., "apify/hello-world" -> "apify%2Fhello-world")
  const encodedActorId = encodeURIComponent(actorId)
  // Pass timeout as query param to set the actor's server-side execution timeout on Apify
  const runUrl = `${APIFY_API_BASE}/acts/${encodedActorId}/runs?timeout=${timeoutSecs}`

  const runResponse = await withRetry(async () => {
    const response = await fetch(runUrl, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${apiToken}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(input)
    })

    if (!response.ok) {
      throw await createHttpError(response, {
        integration: 'apify',
        method: 'runActor',
        actorId
      })
    }

    return response.json()
  }, DEFAULT_RETRY_POLICY)

  const runId = runResponse.data?.id
  const defaultDatasetId = runResponse.data?.defaultDatasetId

  if (!runId) {
    throw new ToolingError('api_error', 'No run ID returned from Apify', {
      integration: 'apify',
      method: 'runActor',
      actorId
    })
  }

  if (context?.logger) {
    context.logger.info(
      `[ApifyAdapter] Actor run started: organizationId=${context.organizationId} executionId=${context.executionId} actorId=${actorId} runId=${runId}`
    )
  }

  // Step 2: Poll for completion with retry logic
  const statusUrl = `${APIFY_API_BASE}/actor-runs/${runId}`
  let status = 'RUNNING'
  let datasetId = defaultDatasetId

  while (status === 'RUNNING' || status === 'READY') {
    // Check timeout
    if (Date.now() - startTime > timeoutMs) {
      return {
        status: 'TIMED_OUT',
        runId,
        datasetId: datasetId || '',
        items: [],
        totalCount: 0,
        executionTimeMs: Date.now() - startTime
      }
    }

    // Wait before polling
    await new Promise((resolve) => setTimeout(resolve, pollIntervalSecs * 1000))

    // Check status with retry for transient network errors
    const statusData = await withRetry(async () => {
      const response = await fetch(statusUrl, {
        headers: { Authorization: `Bearer ${apiToken}` }
      })

      if (!response.ok) {
        throw await createHttpError(response, {
          integration: 'apify',
          method: 'runActor',
          runId
        })
      }

      return response.json()
    }, DEFAULT_RETRY_POLICY)

    status = statusData.data?.status
    datasetId = statusData.data?.defaultDatasetId || datasetId

    if (context?.logger) {
      context.logger.info(
        `[ApifyAdapter] Polling status: organizationId=${context.organizationId} runId=${runId} status=${status} elapsedMs=${Date.now() - startTime}`
      )
    }
  }

  // Step 3: Handle final status
  if (status !== 'SUCCEEDED') {
    // Map Apify status to our result status
    const resultStatus = status === 'ABORTED' ? 'ABORTED' : 'FAILED'
    return {
      status: resultStatus,
      runId,
      datasetId: datasetId || '',
      items: [],
      totalCount: 0,
      executionTimeMs: Date.now() - startTime
    }
  }

  // Step 4: Fetch dataset items
  if (!datasetId) {
    throw new ToolingError('api_error', 'No dataset ID available', {
      integration: 'apify',
      method: 'runActor',
      runId
    })
  }

  let datasetUrl = `${APIFY_API_BASE}/datasets/${datasetId}/items?format=json`
  if (maxItems) {
    datasetUrl += `&limit=${maxItems}`
  }

  const datasetResponse = await withRetry(async () => {
    const response = await fetch(datasetUrl, {
      headers: { Authorization: `Bearer ${apiToken}` }
    })

    if (!response.ok) {
      throw await createHttpError(response, {
        integration: 'apify',
        method: 'runActor',
        datasetId
      })
    }

    return response.json()
  }, DEFAULT_RETRY_POLICY)

  const items = Array.isArray(datasetResponse) ? datasetResponse : []

  if (context?.logger) {
    context.logger.info(
      `[ApifyAdapter] Actor run completed: organizationId=${context.organizationId} executionId=${context.executionId} actorId=${actorId} runId=${runId} itemCount=${items.length} executionTimeMs=${Date.now() - startTime}`
    )
  }

  return {
    status: 'SUCCEEDED',
    runId,
    datasetId,
    items,
    totalCount: items.length,
    executionTimeMs: Date.now() - startTime
  }
}
