import {
  existsSync,
  mkdirSync,
  readFileSync,
  unlinkSync,
  writeFileSync
} from 'node:fs'
import net from 'node:net'
import os from 'node:os'
import { join } from 'node:path'

export const PROTOCOL_PREFIX = 'farmcontrol://'
export const WINDOWS_PIPE_NAME = '\\\\.\\pipe\\com.tombutcher.farmcontrol.instance'
export const SIGNAL_FILE = 'instance-signal.json'
export const LOCK_FILE = 'primary.lock'
const FORWARD_TIMEOUT_MS = 750
const PIPE_RETRY_ATTEMPTS = 3
const PIPE_RETRY_DELAY_MS = 100

export function getInstanceDir() {
  if (process.platform === 'win32') {
    return join(
      os.homedir(),
      'AppData',
      'Local',
      'com.tombutcher.farmcontrol',
      'instance'
    )
  }

  return join(process.env.TMPDIR || '/tmp', 'com.tombutcher.farmcontrol', 'instance')
}

export function getSignalPath() {
  return join(getInstanceDir(), SIGNAL_FILE)
}

export function getLockPath() {
  return join(getInstanceDir(), LOCK_FILE)
}

export function isProcessAlive(pid) {
  if (!Number.isInteger(pid) || pid <= 0) {
    return false
  }

  try {
    process.kill(pid, 0)
    return true
  } catch {
    return false
  }
}

export function isPrimaryInstanceRunning() {
  const lockPath = getLockPath()
  if (!existsSync(lockPath)) {
    return false
  }

  try {
    const existingPid = Number.parseInt(readFileSync(lockPath, 'utf8').trim(), 10)
    return isProcessAlive(existingPid)
  } catch {
    return false
  }
}

export function findProtocolUrl(args) {
  const directMatch = args.find(
    (arg) => typeof arg === 'string' && arg.startsWith(PROTOCOL_PREFIX)
  )

  if (directMatch) {
    return directMatch
  }

  const sources = args.filter((arg) => typeof arg === 'string')

  for (const arg of sources) {
    const trimmed = arg.trim().replace(/^['"]+|['"]+$/g, '')
    const match = trimmed.match(/farmcontrol:\/\/\S+/i)
    if (match) {
      const rawUrl = match[0].replace(/['"]+$/g, '')
      try {
        return decodeURI(rawUrl)
      } catch {
        return rawUrl
      }
    }
  }

  const combinedMatch = sources.join(' ').match(/farmcontrol:\/\/\S+/i)
  if (combinedMatch) {
    const rawUrl = combinedMatch[0].replace(/['"]+$/g, '')
    try {
      return decodeURI(rawUrl)
    } catch {
      return rawUrl
    }
  }

  return undefined
}

export function buildDeeplinkPayload(url, argv = process.argv) {
  const commandLine = [...argv]

  return url
    ? { type: 'deeplink', url, argv: commandLine }
    : { type: 'focus', argv: commandLine }
}

function readSignalQueue({ clear = true } = {}) {
  const signalPath = getSignalPath()
  if (!existsSync(signalPath)) {
    return []
  }

  try {
    const raw = readFileSync(signalPath, 'utf8')
    const parsed = JSON.parse(raw)

    if (clear) {
      unlinkSync(signalPath)
    }

    if (Array.isArray(parsed)) {
      return parsed
    }

    return parsed ? [parsed] : []
  } catch {
    if (clear) {
      try {
        unlinkSync(signalPath)
      } catch {
        // Ignore cleanup failures.
      }
    }
    return []
  }
}

export function writeDeeplinkSignal(payload) {
  const signalPath = getSignalPath()
  mkdirSync(getInstanceDir(), { recursive: true })

  const queue = readSignalQueue({ clear: false })
  queue.push({ ...payload, timestamp: Date.now() })

  const tempPath = `${signalPath}.${process.pid}.${Date.now()}.tmp`
  writeFileSync(tempPath, JSON.stringify(queue), 'utf8')
  writeFileSync(signalPath, readFileSync(tempPath))
  unlinkSync(tempPath)
}

function tryForwardViaPipe(payload) {
  return new Promise((resolve) => {
    let settled = false
    const finish = (forwarded) => {
      if (settled) return
      settled = true
      resolve(forwarded)
    }

    const client = net.connect({ path: WINDOWS_PIPE_NAME })
    const message = JSON.stringify(payload)

    client.on('connect', () => {
      client.write(message)
      client.end()
      finish(true)
    })

    client.on('error', () => {
      finish(false)
    })

    client.setTimeout(FORWARD_TIMEOUT_MS, () => {
      client.destroy()
      finish(false)
    })
  })
}

function delay(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms)
  })
}

async function tryForwardViaPipeWithRetries(payload) {
  for (let attempt = 0; attempt < PIPE_RETRY_ATTEMPTS; attempt += 1) {
    if (await tryForwardViaPipe(payload)) {
      return true
    }

    if (attempt < PIPE_RETRY_ATTEMPTS - 1) {
      await delay(PIPE_RETRY_DELAY_MS)
    }
  }

  return false
}

export async function forwardDeeplinkToRunningInstance(payload) {
  if (process.platform === 'win32') {
    if (await tryForwardViaPipeWithRetries(payload)) {
      return true
    }
  }

  if (isPrimaryInstanceRunning()) {
    writeDeeplinkSignal(payload)
    return true
  }

  return false
}
