import type { SQLiteBunDatabase } from "drizzle-orm/bun-sqlite"
import type { NodeSQLiteDatabase } from "drizzle-orm/node-sqlite"
import path from "path"
import { ProjectTable } from "../project/project.sql"
import { SessionTable, MessageTable, PartTable, TodoTable, PermissionTable } from "../session/session.sql"
import { SessionShareTable } from "../share/share.sql"
import type { Stats, Orphans } from "./json-migration-helpers"
import { readBatch, insertBatch, log } from "./json-migration-helpers"

type Db = SQLiteBunDatabase<any, any> | NodeSQLiteDatabase<any, any>

export async function migrateProjectsSync(
  db: Db, files: string[], stats: Stats, step: (label: string, count: number) => void, batchSize: number, now: number,
) {
  const projectIds = new Set<string>()
  const projectValues: unknown[] = []
  for (let i = 0; i < files.length; i += batchSize) {
    const end = Math.min(i + batchSize, files.length)
    const batch = await readBatch(files, i, end, stats.errors)
    projectValues.length = 0
    for (let j = 0; j < batch.length; j++) {
      const data = batch[j]
      if (!data) continue
      const id = path.basename(files[i + j], ".json")
      projectIds.add(id)
      projectValues.push({
        id, worktree: data.worktree ?? "/", vcs: data.vcs, name: data.name ?? undefined,
        icon_url: data.icon?.url, icon_url_override: data.icon?.override, icon_color: data.icon?.color,
        time_created: data.time?.created ?? now, time_updated: data.time?.updated ?? now,
        time_initialized: data.time?.initialized, sandboxes: data.sandboxes ?? [], commands: data.commands,
      })
    }
    stats.projects += insertBatch(db, projectValues, ProjectTable, "project", stats.errors)
    step("projects", end - i)
  }
  log.info("migrated projects", { count: stats.projects })
  return projectIds
}

export async function migrateSessionsSync(
  db: Db, files: string[], stats: Stats, orphans: Orphans, projectIds: Set<string>,
  step: (label: string, count: number) => void, batchSize: number, now: number,
) {
  const sessionProjects = files.map((file) => path.basename(path.dirname(file)))
  const sessionIds = new Set<string>()
  const values: unknown[] = []
  for (let i = 0; i < files.length; i += batchSize) {
    const end = Math.min(i + batchSize, files.length)
    const batch = await readBatch(files, i, end, stats.errors)
    values.length = 0
    for (let j = 0; j < batch.length; j++) {
      const data = batch[j]
      if (!data) continue
      const id = path.basename(files[i + j], ".json")
      const projectID = sessionProjects[i + j]
      if (!projectIds.has(projectID)) { orphans.sessions++; continue }
      sessionIds.add(id)
      values.push({
        id, project_id: projectID, parent_id: data.parentID ?? null, slug: data.slug ?? "",
        directory: data.directory ?? "", path: data.path ?? null, title: data.title ?? "",
        version: data.version ?? "", share_url: data.share?.url ?? null,
        summary_additions: data.summary?.additions ?? null, summary_deletions: data.summary?.deletions ?? null,
        summary_files: data.summary?.files ?? null, summary_diffs: data.summary?.diffs ?? null,
        revert: data.revert ?? null, permission: data.permission ?? null,
        time_created: data.time?.created ?? now, time_updated: data.time?.updated ?? now,
        time_compacting: data.time?.compacting ?? null, time_archived: data.time?.archived ?? null,
      })
    }
    stats.sessions += insertBatch(db, values, SessionTable, "session", stats.errors)
    step("sessions", end - i)
  }
  log.info("migrated sessions", { count: stats.sessions })
  if (orphans.sessions > 0) log.warn("skipped orphaned sessions", { count: orphans.sessions })
  return sessionIds
}

export async function migrateMessagesSync(
  db: Db, files: string[], sessionIds: Set<string>, stats: Stats,
  step: (label: string, count: number) => void, batchSize: number, now: number,
) {
  const validFiles: string[] = []
  const validSessions: string[] = []
  const messageSessions = new Map<string, string>()
  for (const file of files) {
    const sessionID = path.basename(path.dirname(file))
    if (!sessionIds.has(sessionID)) continue
    validFiles.push(file)
    validSessions.push(sessionID)
  }
  for (let i = 0; i < validFiles.length; i += batchSize) {
    const end = Math.min(i + batchSize, validFiles.length)
    const batch = await readBatch(validFiles, i, end, stats.errors)
    const values = new Array(batch.length)
    let count = 0
    for (let j = 0; j < batch.length; j++) {
      const data = batch[j]
      if (!data) continue
      const id = path.basename(validFiles[i + j], ".json")
      const sessionID = validSessions[i + j]
      messageSessions.set(id, sessionID)
      const rest = data; delete rest.id; delete rest.sessionID
      values[count++] = { id, session_id: sessionID, time_created: data.time?.created ?? now, time_updated: data.time?.updated ?? now, data: rest }
    }
    values.length = count
    stats.messages += insertBatch(db, values, MessageTable, "message", stats.errors)
    step("messages", end - i)
  }
  log.info("migrated messages", { count: stats.messages })
  return messageSessions
}

export async function migratePartsSync(
  db: Db, files: string[], sessionIds: Set<string>, messageSessions: Map<string, string>,
  stats: Stats, step: (label: string, count: number) => void, batchSize: number, now: number,
) {
  for (let i = 0; i < files.length; i += batchSize) {
    const end = Math.min(i + batchSize, files.length)
    const batch = await readBatch(files, i, end, stats.errors)
    const values = new Array(batch.length)
    let count = 0
    for (let j = 0; j < batch.length; j++) {
      const data = batch[j]
      if (!data) continue
      const id = path.basename(files[i + j], ".json")
      const messageID = path.basename(path.dirname(files[i + j]))
      const sessionID = messageSessions.get(messageID)
      if (!sessionID) { stats.errors.push(`part missing message session: ${files[i + j]}`); continue }
      if (!sessionIds.has(sessionID)) continue
      const rest = data; delete rest.id; delete rest.messageID; delete rest.sessionID
      values[count++] = { id, message_id: messageID, session_id: sessionID, time_created: data.time?.created ?? now, time_updated: data.time?.updated ?? now, data: rest }
    }
    values.length = count
    stats.parts += insertBatch(db, values, PartTable, "part", stats.errors)
    step("parts", end - i)
  }
  log.info("migrated parts", { count: stats.parts })
}

export async function migrateTodosSync(
  db: Db, files: string[], sessionIds: Set<string>, stats: Stats, orphans: Orphans,
  step: (label: string, count: number) => void, batchSize: number, now: number,
) {
  const todoSessions = files.map((file) => path.basename(file, ".json"))
  for (let i = 0; i < files.length; i += batchSize) {
    const end = Math.min(i + batchSize, files.length)
    const batch = await readBatch(files, i, end, stats.errors)
    const values: unknown[] = []
    for (let j = 0; j < batch.length; j++) {
      const data = batch[j]
      if (!data) continue
      const sessionID = todoSessions[i + j]
      if (!sessionIds.has(sessionID)) { orphans.todos++; continue }
      if (!Array.isArray(data)) { stats.errors.push(`todo not an array: ${files[i + j]}`); continue }
      for (let position = 0; position < data.length; position++) {
        const todo = data[position]
        if (!todo?.content || !todo?.status || !todo?.priority) continue
        values.push({ session_id: sessionID, content: todo.content, status: todo.status, priority: todo.priority, position, time_created: now, time_updated: now })
      }
    }
    stats.todos += insertBatch(db, values, TodoTable, "todo", stats.errors)
    step("todos", end - i)
  }
  log.info("migrated todos", { count: stats.todos })
  if (orphans.todos > 0) log.warn("skipped orphaned todos", { count: orphans.todos })
}

export async function migratePermissionsSync(
  db: Db, files: string[], projectIds: Set<string>, stats: Stats, orphans: Orphans,
  step: (label: string, count: number) => void, batchSize: number,
) {
  const permProjects = files.map((file) => path.basename(file, ".json"))
  const values: unknown[] = []
  for (let i = 0; i < files.length; i += batchSize) {
    const end = Math.min(i + batchSize, files.length)
    const batch = await readBatch(files, i, end, stats.errors)
    values.length = 0
    for (let j = 0; j < batch.length; j++) {
      const data = batch[j]
      if (!data) continue
      const projectID = permProjects[i + j]
      if (!projectIds.has(projectID)) { orphans.permissions++; continue }
      values.push({ project_id: projectID, data })
    }
    stats.permissions += insertBatch(db, values, PermissionTable, "permission", stats.errors)
    step("permissions", end - i)
  }
  log.info("migrated permissions", { count: stats.permissions })
  if (orphans.permissions > 0) log.warn("skipped orphaned permissions", { count: orphans.permissions })
}

export async function migrateSharesSync(
  db: Db, files: string[], sessionIds: Set<string>, stats: Stats, orphans: Orphans,
  step: (label: string, count: number) => void, batchSize: number,
) {
  const shareSessions = files.map((file) => path.basename(file, ".json"))
  const values: unknown[] = []
  for (let i = 0; i < files.length; i += batchSize) {
    const end = Math.min(i + batchSize, files.length)
    const batch = await readBatch(files, i, end, stats.errors)
    values.length = 0
    for (let j = 0; j < batch.length; j++) {
      const data = batch[j]
      if (!data) continue
      const sessionID = shareSessions[i + j]
      if (!sessionIds.has(sessionID)) { orphans.shares++; continue }
      if (!data?.id || !data?.secret || !data?.url) { stats.errors.push(`session_share missing id/secret/url: ${files[i + j]}`); continue }
      values.push({ session_id: sessionID, id: data.id, secret: data.secret, url: data.url })
    }
    stats.shares += insertBatch(db, values, SessionShareTable, "session_share", stats.errors)
    step("shares", end - i)
  }
  log.info("migrated session shares", { count: stats.shares })
  if (orphans.shares > 0) log.warn("skipped orphaned session shares", { count: orphans.shares })
}
