import Redis from 'ioredis'

let pub
let sub
let client

// 'ready' listeners (registered before connect; fire on initial connect AND each
// reconnect). Used by services that need to reseed Valkey state from Postgres.
const readyListeners = new Set()

const attachReadyHandler = (instance) => {
  instance.on('ready', () => {
    for (const fn of readyListeners) {
      // Fire-and-forget; each listener owns its own error handling.
      Promise.resolve()
        .then(() => fn())
        .catch((err) => console.error('[valkey] ready listener failed:', err))
    }
  })
}

export const onValkeyReady = (fn) => {
  readyListeners.add(fn)
  return () => readyListeners.delete(fn)
}

export const getValkeyClient = () => {
  if (!client) {
    const url = process.env.VALKEY_URL || 'redis://localhost:6379'
    client = new Redis(url)
    attachReadyHandler(client)
  }
  return client
}

export const getValkeyPub = () => {
  if (!pub) {
    const url = process.env.VALKEY_URL || 'redis://localhost:6379'
    pub = new Redis(url)
  }
  return pub
}

export const getValkeySub = () => {
  if (!sub) {
    const url = process.env.VALKEY_URL || 'redis://localhost:6379'
    sub = new Redis(url)
  }
  return sub
}

export const publish = async (channel, data) => {
  const pubClient = getValkeyPub()
  const numReceivers = await pubClient.publish(channel, JSON.stringify(data))
  console.log(`[valkey] publish to ${channel} → ${numReceivers} receiver(s)`)
}

export const subscribe = (channel, callback) => {
  const subClient = getValkeySub()
  subClient.subscribe(channel)
  console.log(`[valkey] subscribed to ${channel}`)
  const handler = (ch, message) => {
    if (ch === channel) {
      console.log(`[valkey] received on ${channel}`)
      callback(JSON.parse(message))
    }
  }
  subClient.on('message', handler)
  return () => {
    subClient.unsubscribe(channel)
    subClient.removeListener('message', handler)
    console.log(`[valkey] unsubscribed from ${channel}`)
  }
}

// --- Thin wrappers used by the mitra-availability mirror ---
//
// Each wrapper uses the shared `client` (separate ioredis instance from pub/sub
// to keep subscribe state isolated). Callers in services/* wrap these in
// try/catch and fall back to Postgres on error — see the plan doc.

export const sadd = (key, ...members) => getValkeyClient().sadd(key, ...members)
export const srem = (key, ...members) => getValkeyClient().srem(key, ...members)
export const sismember = async (key, member) =>
  (await getValkeyClient().sismember(key, member)) === 1
export const smembers = (key) => getValkeyClient().smembers(key)
export const sdiff = (...keys) => getValkeyClient().sdiff(...keys)
export const scard = (key) => getValkeyClient().scard(key)
export const set = (key, value) => getValkeyClient().set(key, value)
export const get = (key) => getValkeyClient().get(key)
export const del = (...keys) => getValkeyClient().del(...keys)
export const incr = (key) => getValkeyClient().incr(key)
export const decr = (key) => getValkeyClient().decr(key)
export const exists = (key) => getValkeyClient().exists(key)
export const pipeline = () => getValkeyClient().pipeline()
export const multi = () => getValkeyClient().multi()
