import * as db from '../src/db/index.js'
import { prisma } from '../src/db/prisma.js'
import { getIncusClient, getInstance, getInstanceState } from '../src/lib/incus/index.js'
import {
  persistResolvedInstanceNetworkAddresses,
  resolveInstanceNetworkAddresses,
  type IncusNetworkStateLike
} from '../src/lib/instance-network-sync.js'

function parseArgs(argv: string[]): { dryRun: boolean; hostId: number | null } {
  let dryRun = false
  let hostId: number | null = null

  for (const arg of argv) {
    if (arg === '--dry-run') {
      dryRun = true
      continue
    }

    if (arg.startsWith('--host-id=')) {
      const value = Number(arg.slice('--host-id='.length))
      if (!Number.isNaN(value)) {
        hostId = value
      }
    }
  }

  return { dryRun, hostId }
}

async function main(): Promise<void> {
  const { dryRun, hostId } = parseArgs(process.argv.slice(2))

  const allHosts = await db.getAllHosts()
  const hosts = allHosts.filter(host => host.status !== 'offline' && (hostId === null || host.id === hostId))

  if (hosts.length === 0) {
    console.log('[BackfillInstanceNetwork] No matching online hosts found')
    return
  }

  let scanned = 0
  let changedInstances = 0
  let ipv4ChangedCount = 0
  let ipv6ChangedCount = 0
  let failed = 0

  console.log(`[BackfillInstanceNetwork] Starting${dryRun ? ' dry-run' : ''} for ${hosts.length} host(s)`)

  for (const host of hosts) {
    console.log(`[BackfillInstanceNetwork] Host #${host.id} ${host.name}`)

    let client: Awaited<ReturnType<typeof getIncusClient>>
    try {
      client = await getIncusClient(host)
    } catch (error) {
      failed++
      const message = error instanceof Error ? error.message : String(error)
      console.error(`[BackfillInstanceNetwork] Failed to connect host #${host.id}: ${message}`)
      continue
    }

    const instances = await prisma.instance.findMany({
      where: {
        hostId: host.id,
        status: { notIn: ['deleted', 'creating'] }
      },
      orderBy: { id: 'asc' },
      select: {
        id: true,
        name: true,
        incusId: true,
        networkMode: true,
        ipv4: true,
        ipv6: true
      }
    })

    for (const instance of instances) {
      scanned++

      try {
        const incusInstance = await getInstance(client, instance.incusId)

        let state: IncusNetworkStateLike | null = null
        try {
          state = await getInstanceState(client, instance.incusId) as IncusNetworkStateLike
        } catch {
          state = null
        }

        const resolved = resolveInstanceNetworkAddresses(
          {
            id: instance.id,
            name: instance.name,
            networkMode: instance.networkMode,
            ipv4: instance.ipv4,
            ipv6: instance.ipv6
          },
          incusInstance,
          state
        )

        const ipv4WillChange = (instance.ipv4 ?? null) !== (resolved.ipv4 ?? null)
        const ipv6WillChange = (instance.ipv6 ?? null) !== (resolved.ipv6 ?? null)

        if (!ipv4WillChange && !ipv6WillChange) {
          continue
        }

        changedInstances++
        if (ipv4WillChange) ipv4ChangedCount++
        if (ipv6WillChange) ipv6ChangedCount++

        if (dryRun) {
          console.log(
            `[BackfillInstanceNetwork] DRY-RUN instance #${instance.id} ${instance.name}: `
            + `ipv4 ${instance.ipv4 ?? '-'} -> ${resolved.ipv4 ?? '-'}, `
            + `ipv6 ${instance.ipv6 ?? '-'} -> ${resolved.ipv6 ?? '-'}`
          )
          continue
        }

        const persisted = await persistResolvedInstanceNetworkAddresses(
          {
            id: instance.id,
            name: instance.name,
            networkMode: instance.networkMode,
            ipv4: instance.ipv4,
            ipv6: instance.ipv6
          },
          resolved,
          { updateLastSyncedAt: true }
        )

        console.log(
          `[BackfillInstanceNetwork] Updated instance #${instance.id} ${instance.name}: `
          + `ipv4 ${persisted.oldIpv4 ?? '-'} -> ${persisted.newIpv4 ?? '-'}, `
          + `ipv6 ${persisted.oldIpv6 ?? '-'} -> ${persisted.newIpv6 ?? '-'}`
        )
      } catch (error) {
        failed++
        const message = error instanceof Error ? error.message : String(error)
        console.error(`[BackfillInstanceNetwork] Failed instance #${instance.id} ${instance.name}: ${message}`)
      }
    }
  }

  console.log(
    `[BackfillInstanceNetwork] Completed${dryRun ? ' dry-run' : ''}: `
    + `scanned=${scanned}, changed=${changedInstances}, `
    + `ipv4Changed=${ipv4ChangedCount}, ipv6Changed=${ipv6ChangedCount}, failed=${failed}`
  )
}

main()
  .catch(error => {
    const message = error instanceof Error ? error.stack || error.message : String(error)
    console.error(`[BackfillInstanceNetwork] Fatal error: ${message}`)
    process.exitCode = 1
  })
  .finally(async () => {
    await prisma.$disconnect()
  })
