import WebSocket from 'ws'

interface ThreadfinConfig {
  url: string
  user: string
  pass: string
}

interface XepgEntry {
  'tvg-id': string
  'x-active': boolean
  [key: string]: unknown
}

type EpgMapping = Record<string, XepgEntry>

interface ActivateResult {
  activated: string[]
  notFound: string[]
}

function getConfig(): ThreadfinConfig {
  const url = process.env.THREADFIN_URL
  const user = process.env.THREADFIN_USER
  const pass = process.env.THREADFIN_PASS
  if (!url || !user || !pass) {
    throw new Error('THREADFIN_URL, THREADFIN_USER, THREADFIN_PASS must be set')
  }
  return { url: url.replace(/\/$/, ''), user, pass }
}

async function login(): Promise<string> {
  const { url, user, pass } = getConfig()
  const res = await fetch(`${url}/api/`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ cmd: 'login', username: user, password: pass }),
  })
  if (!res.ok) throw new Error(`Threadfin login failed: ${res.status}`)
  const data = await res.json()
  if (!data.token) throw new Error('Threadfin login returned no token')
  return data.token
}

export async function activateChannels(
  channelIds: string[],
): Promise<ActivateResult> {
  const { url } = getConfig()
  const token = await login()

  const wsUrl = url.replace(/^http/, 'ws') + `/data/?Token=${token}`

  return new Promise<ActivateResult>((resolve, reject) => {
    const ws = new WebSocket(wsUrl)
    const timeout = setTimeout(() => {
      ws.close()
      reject(new Error('Threadfin WebSocket timed out after 30s'))
    }, 30_000)

    let currentToken = token

    ws.on('error', (err) => {
      clearTimeout(timeout)
      reject(err)
    })

    ws.on('message', (raw) => {
      try {
        const msg = JSON.parse(raw.toString())

        // Update token if rotated
        if (msg.token) currentToken = msg.token

        // Initial data response contains the xepg map
        if (msg.xepg) {
          const epgMapping: EpgMapping = msg.xepg

          const activated: string[] = []
          const idSet = new Set(channelIds)
          const foundIds = new Set<string>()

          // Find and activate matching channels
          for (const [key, entry] of Object.entries(epgMapping)) {
            const tvgId = entry['tvg-id']
            if (tvgId && idSet.has(tvgId)) {
              entry['x-active'] = true
              activated.push(tvgId)
              foundIds.add(tvgId)
            }
          }

          const notFound = channelIds.filter((id) => !foundIds.has(id))

          if (activated.length === 0) {
            clearTimeout(timeout)
            ws.close()
            resolve({ activated, notFound })
            return
          }

          // Must send the ENTIRE map back
          const saveMsg = JSON.stringify({
            cmd: 'saveEpgMapping',
            epgMapping,
            token: currentToken,
          })
          ws.send(saveMsg, (err) => {
            clearTimeout(timeout)
            if (err) {
              ws.close()
              reject(err)
              return
            }
            // Give Threadfin a moment to process, then close
            setTimeout(() => {
              ws.close()
              resolve({ activated, notFound })
            }, 1000)
          })
        }
      } catch (err) {
        clearTimeout(timeout)
        ws.close()
        reject(err)
      }
    })
  })
}
