import log from "./utils/log.ts"
import { Signal } from "./utils/signal.ts"
import { processStdout, processStderr } from "./utils/stdio.ts"

export class Baresip {
  baresipArgs: string[]
  process?: Bun.PipedSubprocess
  callEstablished = new Signal<{ contact: string }>()
  callReceived = new Signal<{ contact: string }>()
  hungUp = new Signal()
  error = new Signal<{ message: string }>()
  registrationSuccess = new Signal()

  constructor(baresipArgs: string[]) {
    this.baresipArgs = baresipArgs
  }

  async connect() {
    this.process = Bun.spawn(this.baresipArgs, {
      stdout: "pipe",
      stderr: "pipe",
      onExit: (_proc, exitCode, signalCode, error) => {
        log.debug(`📞 Baresip process exited (code: ${exitCode}, signal: ${signalCode})`)
        if (error) {
          log.error("Process error:", error)
        }
      },
    })

    Promise.all([
      processStdout(this.process, (line) => this.#parseLine(line)),
      processStderr(this.process),
    ]).catch((error) => {
      log.error("Error processing output:", error)
    })
  }

  accept() {
    executeCommand("a")
  }

  dial(phoneNumber: string) {
    executeCommand(`d${phoneNumber}`)
  }

  hangUp() {
    executeCommand("b")
  }

  disconnectAll() {
    this.callEstablished.disconnect()
    this.callReceived.disconnect()
    this.hungUp.disconnect()
    this.registrationSuccess.disconnect()
  }

  kill() {
    if (!this.process) throw new Error("Process not started")
    this.process.kill()
    this.disconnectAll()
    this.process = undefined
  }

  #parseLine(line: string) {
    log.debug(`📞 Baresip: ${line}`)
    const callEstablishedMatch = line.match(/Call established: (.+)/)
    if (callEstablishedMatch) {
      log.debug(`Call established with "${line}"`)
      this.callEstablished.emit({ contact: callEstablishedMatch[1]! })
    }

    const callReceivedMatch = line.match(/Incoming call from: \+\d+ (\S+) -/)
    if (callReceivedMatch) {
      log.debug(`Incoming call from "${line}"`)
      this.callReceived.emit({ contact: callReceivedMatch[1]!?.trim() })
    }

    const hangUpMatch = line.match(/(.+): session closed/)
    if (hangUpMatch) {
      log.debug(`Call hung up with "${line}"`)
      this.hungUp.emit()
    }

    const callTerminatedMatch = line.match(/(.+) terminated \(duration: /)
    if (callTerminatedMatch) {
      log.debug(`⁉️ NOT HANDLED: Call terminated with "${line}"`)
    }

    const registrationSuccessMatch = line.match(/\[\d+ bindings?\]/)
    if (registrationSuccessMatch) {
      this.registrationSuccess.emit()
    }

    const registrationFailedMatch = line.match(/reg: sip:\S+ 403 Forbidden/)
    const socketInUseMatch = line.match(/tcp: sock_bind:/)
    if (registrationFailedMatch || socketInUseMatch) {
      log.error(`⁉️ NOT HANDLED: Registration failed with "${line}"`)
      this.error.emit({ message: line })
    }
  }
}

const executeCommand = async (command: string) => {
  try {
    const url = new URL(`/?${command}`, "http://127.0.0.1:8000")
    const response = await Bun.fetch(url)

    if (!response.ok) {
      throw new Error(`Error executing command: ${response.statusText}`)
    }
  } catch (error) {
    log.error("Failed to execute command:", error)
  }
}
