import { getTableConfig, tableNames } from '../data-sources'
import type { ParsedQuery, TableConfig } from '../data-sources/types'
import { getSupabaseClient } from '../utils/supabase'
import { applyParsedQuery } from './query-runner'

type GenericObject = Record<string, unknown>

function isRecord(value: unknown): value is GenericObject {
  return typeof value === 'object' && value !== null && !Array.isArray(value)
}

interface MetadataOptions {
  parsedQuery?: ParsedQuery | null
}

interface DataFilters {
  id?: string
  createdFrom?: string
  createdTo?: string
}

interface DataOptions {
  parsedQuery?: ParsedQuery | null
  filters?: DataFilters
  limit?: number
  offset?: number
}

function serializeRow(row: unknown, config: TableConfig): GenericObject | null {
  if (!isRecord(row) || ('error' in row && (row as any).error === true)) {
    return null
  }

  const transform = config.transforms?.serializeRow
  return transform ? transform(row) : row
}

export async function fetchAllTablesMetadata() {
  const results = await Promise.allSettled(
    tableNames.map((name) => fetchTableMetadata(name))
  )

  // Log any errors for debugging
  results.forEach((result, index) => {
    if (result.status === 'rejected') {
      console.error(`Failed to fetch metadata for table ${tableNames[index]}:`, result.reason)
    }
  })

  return results
    .filter((result): result is PromiseFulfilledResult<Awaited<ReturnType<typeof fetchTableMetadata>>> =>
      result.status === 'fulfilled'
    )
    .map((result) => result.value)
}

export async function fetchTableMetadata(tableName: string, options?: MetadataOptions) {
  try {
    const config = getTableConfig(tableName)

    if (!config) {
      throw createError({ statusCode: 404, statusMessage: `Tabla ${tableName} no encontrada` })
    }

    const supabase = getSupabaseClient()

    const baseSelect = config.defaultSelect ?? '*'

    const samplePromise = applyParsedQuery(
      supabase.from(config.table).select(baseSelect),
      options?.parsedQuery ?? null
    )
      .limit(1)

    // Try to get created_at range
    // For views with 'fecha' field, try that first, otherwise use 'created_at'
    const orderField = baseSelect.includes('fecha') ? 'fecha' : 'created_at'

    const earliestPromise = supabase
      .from(config.table)
      .select(baseSelect)
      .order(orderField, { ascending: true })
      .limit(1)

    const latestPromise = supabase
      .from(config.table)
      .select(baseSelect)
      .order(orderField, { ascending: false })
      .limit(1)

    const [sampleResult, earliestResult, latestResult] =
      await Promise.all([samplePromise, earliestPromise, latestPromise])

    const { data: sampleData, error: sampleError } = sampleResult

    // Handle created_at queries that may fail for views without this column
    const earliest = earliestResult.error ? { data: null } : earliestResult as { data: any[] | null }
    const latest = latestResult.error ? { data: null } : latestResult as { data: any[] | null }

    if (sampleError) {
      console.error(`Error fetching sample for ${config.table}:`, {
        message: sampleError.message,
        details: sampleError.details,
        hint: sampleError.hint,
        code: sampleError.code,
        full: sampleError
      })
      const errorMsg = sampleError.message || sampleError.hint || sampleError.details || sampleError.code || 'Error desconocido al obtener muestra'
      throw createError({ statusCode: 500, statusMessage: errorMsg })
    }

    const sampleSet = Array.isArray(sampleData) ? sampleData : []
    const sampleRow = serializeRow(sampleSet[0] ?? null, config)
    const columnNames = sampleRow ? Object.keys(sampleRow) : []
    const approxRowSize = sampleRow ? JSON.stringify(sampleRow).length : 0
    const approxSizeBytes = null // We'll calculate this from in-memory data

    return {
      name: config.name,
      table: config.table,
      primaryKey: config.primaryKey ?? 'id',
      rowCount: 0, // Will be calculated from in-memory data
      approxSizeBytes,
      columns: columnNames,
      createdAtRange: {
        from: earliest.data?.[0]?.created_at ?? null,
        to: latest.data?.[0]?.created_at ?? null
      },
      sampleRow,
      lastRefreshed: new Date().toISOString(),
      description: config.description
    }
  } catch (error: any) {
    console.error(`Unexpected error in fetchTableMetadata for ${tableName}:`, error)
    throw createError({
      statusCode: 500,
      statusMessage: error?.message || error?.toString() || 'Error inesperado al obtener metadatos'
    })
  }
}

export async function fetchTableRecordMetadata(tableName: string, id: string) {
  const config = getTableConfig(tableName)

  if (!config) {
    throw createError({ statusCode: 404, statusMessage: `Tabla ${tableName} no encontrada` })
  }

  const supabase = getSupabaseClient()
  const primaryKey = config.primaryKey ?? 'id'

  const { data, error } = await supabase
    .from(config.table)
    .select(config.defaultSelect ?? '*')
    .eq(primaryKey, id)
    .maybeSingle()

  if (error) {
    throw createError({ statusCode: 500, statusMessage: error.message })
  }

  if (!data || (typeof data === 'object' && data !== null && 'error' in data)) {
    if (data && typeof data === 'object' && 'message' in data) {
      throw createError({ statusCode: 500, statusMessage: String((data as any).message) })
    }
    throw createError({ statusCode: 404, statusMessage: `Registro ${id} no encontrado` })
  }

  return {
    table: config.table,
    id,
    metadata: serializeRow(data, config)
  }
}

export async function fetchTableRecord(tableName: string, id: string) {
  const config = getTableConfig(tableName)

  if (!config) {
    throw createError({ statusCode: 404, statusMessage: `Tabla ${tableName} no encontrada` })
  }

  const supabase = getSupabaseClient()
  const primaryKey = config.primaryKey ?? 'id'

  const { data, error } = await supabase
    .from(config.table)
    .select(config.defaultSelect ?? '*')
    .eq(primaryKey, id)
    .maybeSingle()

  if (error) {
    throw createError({ statusCode: 500, statusMessage: error.message })
  }

  if (!data || (typeof data === 'object' && data !== null && 'error' in data)) {
    if (data && typeof data === 'object' && 'message' in data) {
      throw createError({ statusCode: 500, statusMessage: String((data as any).message) })
    }
    throw createError({ statusCode: 404, statusMessage: `Registro ${id} no encontrado` })
  }

  return serializeRow(data, config)
}

export async function fetchAllData(limitPerTable = 100) {
  const supabase = getSupabaseClient()

  const tablePromises = tableNames.map(async (name) => {
    const config = getTableConfig(name)!
    const { data, error, count } = await supabase
      .from(config.table)
      .select(config.defaultSelect ?? '*', { count: 'exact' })
      .limit(limitPerTable)

    if (error) {
      throw createError({ statusCode: 500, statusMessage: error.message })
    }

    const rows = Array.isArray(data) ? data : []

    return {
      table: config.table,
      count: count ?? 0,
      limit: limitPerTable,
      records: rows
        .map((row) => serializeRow(row, config))
        .filter((row): row is GenericObject => row !== null)
    }
  })

  return Promise.all(tablePromises)
}

export async function fetchTableData(tableName: string, options?: DataOptions) {
  const config = getTableConfig(tableName)

  if (!config) {
    throw createError({ statusCode: 404, statusMessage: `Tabla ${tableName} no encontrada` })
  }

  const supabase = getSupabaseClient()
  const primaryKey = config.primaryKey ?? 'id'
  const limit = options?.limit ?? 100

  let query = supabase
    .from(config.table)
    .select(config.defaultSelect ?? '*', { count: 'exact' })

  query = applyParsedQuery(query as never, options?.parsedQuery ?? null) as never

  if (options?.filters?.id) {
    query = query.eq(primaryKey, options.filters.id)
  }

  if (options?.filters?.createdFrom) {
    query = query.gte('created_at', options.filters.createdFrom)
  }

  if (options?.filters?.createdTo) {
    query = query.lte('created_at', options.filters.createdTo)
  }

  if (!options?.parsedQuery?.limit) {
    query = query.limit(limit)
  }

  if (options?.offset !== undefined && options.offset > 0) {
    query = query.range(options.offset, options.offset + limit - 1)
  }

  const { data, error, count } = await query

  if (error) {
    throw createError({ statusCode: 500, statusMessage: error.message })
  }

  const rows = Array.isArray(data) ? data : []
  const records = rows
    .map((row) => serializeRow(row, config))
    .filter((row): row is GenericObject => row !== null)

  return {
    table: config.table,
    count: count ?? records.length,
    limit,
    records
  }
}
