import {
  FunctionsFetchError,
  FunctionsHttpError,
  FunctionsRelayError,
} from '@supabase/supabase-js'

import { supabaseBrowser } from './client'

import type { Database } from '@/types/supabase'
import type { SupabaseClient } from '@supabase/supabase-js'

export type EdgeInvokeOptions = {
  method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
  headers?: Record<string, string>
  responseType?: 'json' | 'text' | 'blob' | 'arrayBuffer'
}

export class EdgeFunctionError extends Error {
  constructor(
    message: string,
    public readonly functionName: string,
    public readonly status?: number,
    public readonly details?: unknown,
  ) {
    super(message)
    this.name = 'EdgeFunctionError'
  }
}

// Soporta base64 puro o data:...;base64,...
function decodeBase64ToUint8Array(input: string): Uint8Array {
  const trimmed = input.trim()
  const base64 = trimmed.startsWith('data:')
    ? trimmed.slice(trimmed.indexOf(',') + 1)
    : trimmed

  const bin = atob(base64)
  const bytes = new Uint8Array(bin.length)
  for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i)
  return bytes
}

function stripDataUrlPrefix(input: string): string {
  const trimmed = input.trim()
  if (!trimmed.startsWith('data:')) return trimmed
  const commaIdx = trimmed.indexOf(',')
  return commaIdx >= 0 ? trimmed.slice(commaIdx + 1) : trimmed
}

function looksLikeBase64(s: string): boolean {
  const t = stripDataUrlPrefix(s).replace(/\s+/g, '').replace(/=+$/g, '')

  // base64 típico: solo chars permitidos y longitud razonable
  if (t.length < 64) return false
  return /^[A-Za-z0-9+/]+$/.test(t)
}

function startsWithZip(bytes: Uint8Array): boolean {
  return bytes.length >= 2 && bytes[0] === 0x50 && bytes[1] === 0x4b // "PK"
}

function startsWithPdf(bytes: Uint8Array): boolean {
  return (
    bytes.length >= 5 &&
    bytes[0] === 0x25 &&
    bytes[1] === 0x50 &&
    bytes[2] === 0x44 &&
    bytes[3] === 0x46 &&
    bytes[4] === 0x2d
  ) // "%PDF-"
}

function binaryStringToUint8Array(input: string): Uint8Array {
  const bytes = new Uint8Array(input.length)
  for (let i = 0; i < input.length; i++) bytes[i] = input.charCodeAt(i) & 0xff
  return bytes
}

export async function invokeEdge<TOut>(
  functionName: string,
  body?:
    | string
    | File
    | Blob
    | ArrayBuffer
    | FormData
    | ReadableStream<Uint8Array<ArrayBufferLike>>
    | Record<string, unknown>
    | undefined,
  opts: EdgeInvokeOptions = {},
  client?: SupabaseClient<Database>,
): Promise<TOut> {
  const supabase = client ?? supabaseBrowser()

  // Nota: algunas versiones/defs de @supabase/supabase-js no tipan `responseType`
  // aunque el runtime lo soporte. Usamos `any` para no bloquear el uso de Blob.
  const invoke: any = (supabase.functions as any).invoke.bind(
    supabase.functions,
  )
  const { data, error } = await invoke(functionName, {
    body,
    method: opts.method ?? 'POST',
    headers: opts.headers,
    responseType: opts.responseType,
  })

  if (error) {
    // Valores por defecto (por si falla el parseo o es otro tipo de error)
    let message = error.message // El genérico "returned a non-2xx status code"
    let status = undefined
    let details: unknown = error

    // 2. Verificamos si es un error HTTP (4xx o 5xx) que trae cuerpo JSON
    if (error instanceof FunctionsHttpError) {
      try {
        // Obtenemos el status real (ej. 404, 400)
        status = error.context.status

        // ¡LA CLAVE! Leemos el JSON que tu Edge Function envió
        const errorBody = await error.context.json()
        details = errorBody

        // Intentamos extraer el mensaje humano según tu estructura { error: { message: "..." } }
        // o la estructura simple { error: "..." }
        if (errorBody && typeof errorBody === 'object') {
          // Caso 1: Estructura anidada (la que definimos hace poco: { error: { message: "..." } })
          if (
            'error' in errorBody &&
            typeof errorBody.error === 'object' &&
            errorBody.error !== null &&
            'message' in errorBody.error
          ) {
            message = (errorBody.error as { message: string }).message
          }
          // Caso 2: Estructura simple ({ error: "Mensaje de error" })
          else if (
            'error' in errorBody &&
            typeof errorBody.error === 'string'
          ) {
            message = errorBody.error
          }
          // Caso 3: Propiedad message directa ({ message: "..." })
          else if (
            'message' in errorBody &&
            typeof errorBody.message === 'string'
          ) {
            message = errorBody.message
          }
        }
      } catch (e) {
        console.warn('No se pudo parsear el error JSON de la Edge Function', e)
      }
    } else if (error instanceof FunctionsRelayError) {
      message = `Error de Relay Supabase: ${error.message}`
    } else if (error instanceof FunctionsFetchError) {
      message = `Error de conexión (Fetch): ${error.message}`
    }

    // 3. Lanzamos tu error personalizado con los datos reales extraídos
    throw new EdgeFunctionError(message, functionName, status, details)
  }

  if (opts.responseType === 'blob') {
    const anyData: unknown = data

    if (anyData instanceof Blob) {
      return anyData as TOut
    }

    throw new EdgeFunctionError(
      'La Edge Function no devolvió un binario (Blob) válido.',
      functionName,
      undefined,
      { receivedType: typeof anyData, received: anyData },
    )
  }

  return data as TOut
}
