import { ToolingError } from '../../../../../../types'
import type { ExecutionContext } from '../../../../../../../base/types'
import type {
  SignatureApiCredentials,
  DownloadDocumentParams,
  DownloadDocumentResult,
  Deliverable,
  ListDeliverablesResponse
} from '../utils/types'

const DEFAULT_API_URL = 'https://api.signatureapi.com/v1'

/**
 * Retry configuration for waiting on deliverable generation
 */
const DELIVERABLE_RETRY_CONFIG = {
  maxAttempts: 8,
  initialDelayMs: 500,
  maxDelayMs: 8000,
  backoffMultiplier: 2
}

/**
 * Sleep for specified milliseconds
 */
function sleep(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms))
}

/**
 * Download signed document from a completed envelope using the deliverable system
 *
 * SignatureAPI uses a deliverable system for signed documents:
 * 1. List deliverables for the envelope
 * 2. If none exist or none are generated, create one
 * 3. Poll until status is 'generated'
 * 4. Download via the pre-signed URL
 *
 * @param credentials - SignatureAPI credentials (apiKey, optional apiUrl)
 * @param params - Download parameters (envelopeId, documentType)
 * @param context - Execution context for logging
 * @returns Buffer containing PDF content, content type, and filename
 * @throws ToolingError if download fails
 */
export async function downloadDocument(
  credentials: SignatureApiCredentials,
  params: DownloadDocumentParams,
  context?: ExecutionContext
): Promise<DownloadDocumentResult> {
  const { apiKey, apiUrl = DEFAULT_API_URL } = credentials
  const { envelopeId } = params
  // Map documentType to deliverable type: 'combined'/'audit' -> 'standard', 'original' -> 'simple'
  const deliverableType = params.documentType === 'original' ? 'simple' : 'standard'

  // Validate credentials
  if (!apiKey) {
    throw new ToolingError('credentials_invalid', 'SignatureAPI credentials must include apiKey', {
      integration: 'signature-api',
      method: 'downloadDocument'
    })
  }

  // Validate required parameters
  if (!envelopeId) {
    throw new ToolingError('validation_error', 'Envelope ID is required', {
      integration: 'signature-api',
      method: 'downloadDocument'
    })
  }

  const log = (message: string, data?: Record<string, unknown>) => {
    if (context?.logger) {
      const dataStr = data
        ? ` ${Object.entries(data)
            .map(([k, v]) => `${k}=${v}`)
            .join(' ')}`
        : ''
      context.logger.info(
        `[SignatureAPI:downloadDocument] ${message} organizationId=${context.organizationId} executionId=${context.executionId} envelopeId=${envelopeId}${dataStr}`
      )
    }
  }

  try {
    // Step 1: List existing deliverables
    log('Listing deliverables')
    const listRes = await fetch(`${apiUrl}/envelopes/${envelopeId}/deliverables`, {
      headers: { 'X-API-Key': apiKey }
    })

    if (!listRes.ok) {
      const errorText = await listRes.text()
      throw new ToolingError('api_error', `Failed to list deliverables: ${listRes.status} ${errorText}`, {
        integration: 'signature-api',
        method: 'downloadDocument',
        envelopeId
      })
    }

    const listData: ListDeliverablesResponse = await listRes.json()
    log('Found deliverables', { count: listData.data.length })

    // Step 2: Find existing generated deliverable or create one
    let deliverable: Deliverable | undefined = listData.data.find(
      (d) => d.type === deliverableType && d.status === 'generated'
    )

    if (!deliverable) {
      // Check for pending/processing deliverable of matching type
      deliverable = listData.data.find(
        (d) => d.type === deliverableType && (d.status === 'pending' || d.status === 'processing')
      )

      if (!deliverable) {
        // Create new deliverable
        log('Creating new deliverable', { type: deliverableType })
        const createRes = await fetch(`${apiUrl}/envelopes/${envelopeId}/deliverables`, {
          method: 'POST',
          headers: {
            'X-API-Key': apiKey,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({ type: deliverableType })
        })

        if (!createRes.ok) {
          const errorText = await createRes.text()
          throw new ToolingError('api_error', `Failed to create deliverable: ${createRes.status} ${errorText}`, {
            integration: 'signature-api',
            method: 'downloadDocument',
            envelopeId
          })
        }

        deliverable = await createRes.json()
        log('Created deliverable', { id: deliverable!.id, status: deliverable!.status })
      }
    }

    // Step 3: Poll until generated (exponential backoff)
    let attempts = 0
    let delay = DELIVERABLE_RETRY_CONFIG.initialDelayMs

    while (deliverable!.status !== 'generated' && attempts < DELIVERABLE_RETRY_CONFIG.maxAttempts) {
      if (deliverable!.status === 'failed') {
        throw new ToolingError('api_error', 'Deliverable generation failed', {
          integration: 'signature-api',
          method: 'downloadDocument',
          envelopeId,
          deliverableId: deliverable!.id
        })
      }

      log('Deliverable not ready, waiting', { status: deliverable!.status, attempt: attempts + 1, delayMs: delay })
      await sleep(delay)

      // Fetch updated status
      const getRes = await fetch(`${apiUrl}/deliverables/${deliverable!.id}`, {
        headers: { 'X-API-Key': apiKey }
      })

      if (!getRes.ok) {
        const errorText = await getRes.text()
        throw new ToolingError('api_error', `Failed to get deliverable: ${getRes.status} ${errorText}`, {
          integration: 'signature-api',
          method: 'downloadDocument',
          deliverableId: deliverable!.id
        })
      }

      deliverable = await getRes.json()
      log('Deliverable status', { id: deliverable!.id, status: deliverable!.status })

      attempts++
      delay = Math.min(delay * DELIVERABLE_RETRY_CONFIG.backoffMultiplier, DELIVERABLE_RETRY_CONFIG.maxDelayMs)
    }

    if (deliverable!.status !== 'generated') {
      throw new ToolingError(
        'timeout_error',
        `Deliverable not ready after ${DELIVERABLE_RETRY_CONFIG.maxAttempts} attempts (status: ${deliverable!.status})`,
        { integration: 'signature-api', method: 'downloadDocument', envelopeId, deliverableId: deliverable!.id }
      )
    }

    // Step 4: Download PDF from pre-signed URL
    if (!deliverable!.url) {
      throw new ToolingError('api_error', 'Deliverable generated but URL is null', {
        integration: 'signature-api',
        method: 'downloadDocument',
        deliverableId: deliverable!.id
      })
    }

    log('Downloading PDF from pre-signed URL', { deliverableId: deliverable!.id })
    const downloadRes = await fetch(deliverable!.url)

    if (!downloadRes.ok) {
      throw new ToolingError('api_error', `Failed to download PDF: ${downloadRes.status}`, {
        integration: 'signature-api',
        method: 'downloadDocument',
        deliverableId: deliverable!.id
      })
    }

    const buffer = Buffer.from(await downloadRes.arrayBuffer())
    const filename = `${envelopeId}-signed.pdf`

    log('Download complete', { size: buffer.length, filename })

    return {
      content: buffer,
      contentType: 'application/pdf',
      filename
    }
  } catch (error) {
    // Re-throw ToolingError as-is
    if (error instanceof ToolingError) {
      throw error
    }

    // Wrap unknown errors
    throw new ToolingError('api_error', `Failed to download document: ${(error as Error).message}`, {
      integration: 'signature-api',
      method: 'downloadDocument',
      organizationId: context?.organizationId,
      originalError: error
    })
  }
}
