import { appendFileSync, mkdirSync, readdirSync, readFileSync } from "node:fs"
import { basename } from "node:path"
import { getConfig } from "./config"

export type LogEvent =
  | { type: "webhook"; eventType: string; repo: string; sender?: string; ref?: string }
  | { type: "webhook-ignored"; eventType: string; repo: string }
  | { type: "pr"; action: string; pr: number; repo: string; user: string; title?: string }
  | { type: "comment"; action: string; pr: number; repo: string; user: string; body?: string }
  | { type: "comment-skipped"; pr: number; repo: string; commentId: number }
  | { type: "review"; action: string; pr: number; repo: string; user: string }
  | { type: "thread-auto-created"; pr: number; repo: string }
  | { type: "discord-relay"; pr: number; repo: string; user: string }
  | { type: "discord-command-error"; command: string; error: string }
  | { type: "discord-warning"; detail: string }
  | { type: "crash-log-found" }
  | { type: "startup"; detail: string }
  | { type: "discord-ready" }
  | { type: "error"; error: unknown; context?: string }

export type StoredLogEvent = LogEvent & { ts: string }

type Listener = (event: StoredLogEvent) => void
const listeners: Listener[] = []

const releaseSha = process.env.RENDER_GIT_COMMIT?.slice(0, 7) || "dev"

export function log(event: LogEvent) {
  const stored = { ...event, ts: new Date().toISOString() } as StoredLogEvent
  for (const listener of listeners) {
    listener(stored)
  }
}

export function onLog(listener: Listener) {
  listeners.push(listener)
  return () => {
    const index = listeners.indexOf(listener)
    if (index !== -1) listeners.splice(index, 1)
  }
}

// --- Log files ---

const logsDir = `${getConfig("dataDir")}/spike/logs`
mkdirSync(logsDir, { recursive: true })

function createLogFile(): string {
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-")
  return `${logsDir}/${releaseSha}_${timestamp}.jsonl`
}

export function listLogFiles(): string[] {
  return readdirSync(logsDir)
    .filter((f) => f.endsWith(".jsonl"))
    .sort()
    .reverse()
    .slice(0, 20)
}

export function readLogFile(filename: string): StoredLogEvent[] {
  const raw = readLogFileRaw(filename)
  if (!raw) return []
  return raw
    .trim()
    .split("\n")
    .filter(Boolean)
    .map((line) => JSON.parse(line) as StoredLogEvent)
}

export function readLogFileRaw(filename: string): string | undefined {
  if (filename !== basename(filename)) throw new Error("Invalid filename")
  const path = `${logsDir}/${filename}`
  try {
    return readFileSync(path, "utf-8")
  } catch {
    return
  }
}

// --- Built-in listeners ---

// Console
const dim = (s: string) => `\x1b[2m${s}\x1b[0m`
const red = (s: string) => `\x1b[31m${s}\x1b[0m`
const green = (s: string) => `\x1b[32m${s}\x1b[0m`
const yellow = (s: string) => `\x1b[33m${s}\x1b[0m`
const cyan = (s: string) => `\x1b[36m${s}\x1b[0m`

onLog((event) => {
  const time = dim(event.ts.slice(11, 19))

  if (event.type === "error") {
    const context = event.context ? ` ${event.context}` : ""
    console.error(`${time} ${red("error")}${context}`, event.error)
    return
  }

  const parts: string[] = []
  if ("action" in event) parts.push(event.action)
  if ("repo" in event) parts.push(dim(event.repo))
  if ("pr" in event) parts.push(cyan(`#${event.pr}`))
  if ("user" in event) parts.push(dim(event.user))
  if ("sender" in event && event.sender) parts.push(dim(event.sender))
  if ("ref" in event && event.ref) parts.push(dim(event.ref))
  if ("title" in event && event.title) parts.push(event.title)
  if ("detail" in event) parts.push(event.detail)
  if ("command" in event) parts.push(event.command)
  if ("eventType" in event) parts.push(event.eventType)

  const isSkipped = event.type === "webhook-ignored" || event.type === "comment-skipped"
  const typeColor = isSkipped ? yellow : green
  console.log(`${time} ${typeColor(event.type)} ${parts.join(" ")}`)
})

// JSONL file
const currentLogFile = createLogFile()

function serializeEvent(event: StoredLogEvent): string {
  if (event.type === "error") {
    const error = event.error instanceof Error
      ? { message: event.error.message, stack: event.error.stack }
      : event.error
    return JSON.stringify({ ...event, error })
  }
  return JSON.stringify(event)
}

onLog((event) => {
  appendFileSync(currentLogFile, serializeEvent(event) + "\n")
})
