/**
 * Update Attribute - Attio API
 *
 * Updates an existing attribute on an Attio object.
 * PATCH /v2/objects/{object}/attributes/{attribute}
 *
 * Note: Cannot change api_slug or type after creation.
 */

import { ToolingError } from '../../../../../../types'
import { withRetry, createHttpError, DEFAULT_RETRY_POLICY } from '../../../../../../../../../platform/resilience'
import type { ExecutionContext } from '../../../../../../../base/types'
import type {
  AttioCredentials,
  UpdateAttributeParams,
  UpdateAttributeResult,
  AttioAttributeResponse,
  AttributeConfig
} from '../utils/types'

const ATTIO_API_BASE = 'https://api.attio.com/v2'

/**
 * Update an existing attribute on an Attio object
 *
 * Uses PATCH semantics for partial updates. Only fields provided will be updated.
 * Cannot change api_slug or type after attribute creation.
 *
 * @param credentials - Attio API credentials (apiKey)
 * @param params - Attribute update parameters
 * @param context - Execution context for logging
 * @returns Updated attribute ID, slug, and success status
 * @throws ToolingError if attribute update fails
 *
 * @example
 * // Update attribute title
 * const updated = await updateAttribute(
 *   { apiKey: 'xxx' },
 *   {
 *     objectSlug: 'people',
 *     attributeSlug: 'status',
 *     title: 'Lead Status'
 *   },
 *   context
 * )
 *
 * @example
 * // Archive an attribute
 * const archived = await updateAttribute(
 *   { apiKey: 'xxx' },
 *   {
 *     objectSlug: 'deals',
 *     attributeSlug: 'old_field',
 *     isArchived: true
 *   },
 *   context
 * )
 *
 * @example
 * // Update select options (include existing option IDs to preserve them)
 * const updated = await updateAttribute(
 *   { apiKey: 'xxx' },
 *   {
 *     objectSlug: 'people',
 *     attributeSlug: 'stage',
 *     config: {
 *       options: [
 *         { id: 'existing-id-1', title: 'Lead', isArchived: false },
 *         { id: 'existing-id-2', title: 'Qualified', isArchived: false },
 *         { title: 'New Stage', isArchived: false }  // New option (no id)
 *       ]
 *     }
 *   },
 *   context
 * )
 */
export async function updateAttribute(
  credentials: AttioCredentials,
  params: UpdateAttributeParams,
  context?: ExecutionContext
): Promise<UpdateAttributeResult> {
  const { apiKey } = credentials
  const { objectSlug, attributeSlug, title, description, isRequired, isArchived, config } = params

  // Validate credentials
  if (!apiKey) {
    throw new ToolingError('credentials_invalid', 'Missing Attio API key', {
      integration: 'attio',
      method: 'updateAttribute',
      organizationId: context?.organizationId
    })
  }

  // Validate required params
  if (!objectSlug) {
    throw new ToolingError('validation_error', 'objectSlug is required', {
      integration: 'attio',
      method: 'updateAttribute',
      organizationId: context?.organizationId
    })
  }
  if (!attributeSlug) {
    throw new ToolingError('validation_error', 'attributeSlug is required', {
      integration: 'attio',
      method: 'updateAttribute',
      organizationId: context?.organizationId
    })
  }

  // Ensure at least one field to update
  if (
    title === undefined &&
    description === undefined &&
    isRequired === undefined &&
    isArchived === undefined &&
    config === undefined
  ) {
    throw new ToolingError('validation_error', 'At least one field to update is required', {
      integration: 'attio',
      method: 'updateAttribute',
      organizationId: context?.organizationId
    })
  }

  const url = `${ATTIO_API_BASE}/objects/${encodeURIComponent(objectSlug)}/attributes/${encodeURIComponent(attributeSlug)}`

  // Build request body with only provided fields (PATCH semantics)
  const dataPayload: Record<string, unknown> = {}
  if (title !== undefined) dataPayload.title = title
  if (description !== undefined) dataPayload.description = description
  if (isRequired !== undefined) dataPayload.is_required = isRequired
  if (isArchived !== undefined) dataPayload.is_archived = isArchived
  if (config !== undefined) dataPayload.config = buildAttributeConfig(config)

  const requestBody = { data: dataPayload }

  try {
    const data = await withRetry(async () => {
      const response = await fetch(url, {
        method: 'PATCH',
        headers: {
          Authorization: `Bearer ${apiKey}`,
          'Content-Type': 'application/json',
          Accept: 'application/json'
        },
        body: JSON.stringify(requestBody)
      })

      if (!response.ok) {
        throw await createHttpError(response, {
          integration: 'attio',
          method: 'updateAttribute',
          organizationId: context?.organizationId,
          params: { objectSlug, attributeSlug }
        })
      }

      return (await response.json()) as AttioAttributeResponse
    }, DEFAULT_RETRY_POLICY)

    // Log success
    if (context?.logger) {
      context.logger.info(
        `[AttioAdapter] Attribute updated: objectSlug=${objectSlug} attributeSlug=${attributeSlug} attributeId=${data.data.id.attribute_id} executionId=${context.executionId}`
      )
    }

    return {
      attributeId: data.data.id.attribute_id,
      slug: data.data.api_slug,
      success: true
    }
  } catch (error) {
    // Re-throw ToolingError as-is
    if (error instanceof ToolingError) {
      throw error
    }

    // Wrap unknown errors
    throw new ToolingError('api_error', `Failed to update Attio attribute: ${(error as Error).message}`, {
      integration: 'attio',
      method: 'updateAttribute',
      organizationId: context?.organizationId,
      params: { objectSlug, attributeSlug }
    })
  }
}

/**
 * Build attribute config for API request
 *
 * Transforms internal config format to Attio API format.
 * IMPORTANT: When updating options, include existing option IDs to preserve them.
 */
function buildAttributeConfig(config: AttributeConfig): Record<string, unknown> {
  const result: Record<string, unknown> = {}

  // Build select options
  // IMPORTANT: When updating options, include existing option IDs to preserve them
  if (config.options) {
    result.options = config.options.map((opt) => ({
      ...(opt.id && { id: opt.id }),
      title: opt.title,
      is_archived: opt.isArchived
    }))
  }

  // Build other config fields
  if (config.targetObject) {
    result.target_object = config.targetObject
  }
  if (config.currencyCode) {
    result.currency_code = config.currencyCode
  }
  if (config.maxValue !== undefined) {
    result.max_value = config.maxValue
  }

  return result
}
