/*
 * Copyright 2025 Daytona Platforms Inc.
 * SPDX-License-Identifier: AGPL-3.0
 */

import { Injectable, Logger } from '@nestjs/common'
import { Runner } from '../entities/runner.entity'
import { ModuleRef } from '@nestjs/core'
import { RunnerAdapterV0 } from './runnerAdapter.v0'
import { RunnerAdapterV2 } from './runnerAdapter.v2'
import { BuildInfo } from '../entities/build-info.entity'
import { DockerRegistry } from '../../docker-registry/entities/docker-registry.entity'
import { Sandbox } from '../entities/sandbox.entity'
import { SandboxState } from '../enums/sandbox-state.enum'
import { BackupState } from '../enums/backup-state.enum'
import { RunnerServiceInfo } from '../common/runner-service-info'

export interface RunnerSandboxInfo {
  state: SandboxState
  daemonVersion?: string
  backupState?: BackupState
  backupSnapshot?: string
  backupErrorReason?: string
}

export interface RunnerSnapshotInfo {
  name: string
  sizeGB: number
  entrypoint: string[]
  cmd: string[]
  hash: string
}

export interface SnapshotDigestResponse {
  hash: string
  sizeGB: number
}

// Result returned when the runner finished a snapshot-from-sandbox operation
// synchronously (v0 path). v2 dispatches the work as an async job and returns
// `undefined`; the API job-state handler picks up the result later.
export interface CreateSandboxSnapshotResult {
  ref: string
  hash: string
  sizeGB?: number
  entrypoint?: string[]
  cmd?: string[]
}

export interface RunnerMetrics {
  currentAllocatedCpu?: number
  currentAllocatedDiskGiB?: number
  currentAllocatedMemoryGiB?: number
  currentCpuUsagePercentage?: number
  currentDiskUsagePercentage?: number
  currentMemoryUsagePercentage?: number
  currentSnapshotCount?: number
  currentStartedSandboxes?: number
}

export interface RunnerInfo {
  serviceHealth?: RunnerServiceInfo[]
  metrics?: RunnerMetrics
  appVersion?: string
}

export interface StartSandboxResponse {
  daemonVersion: string
}

export interface RunnerAdapter {
  init(runner: Runner): Promise<void>

  healthCheck(signal?: AbortSignal): Promise<void>

  runnerInfo(signal?: AbortSignal): Promise<RunnerInfo>

  sandboxInfo(sandboxId: string): Promise<RunnerSandboxInfo>
  createSandbox(
    sandbox: Sandbox,
    snapshotRef: string,
    registry?: DockerRegistry,
    entrypoint?: string[],
    metadata?: { [key: string]: string },
    otelEndpoint?: string,
    skipStart?: boolean,
  ): Promise<StartSandboxResponse | undefined>
  startSandbox(
    sandboxId: string,
    authToken: string,
    metadata?: { [key: string]: string },
    skipStart?: boolean,
  ): Promise<StartSandboxResponse | undefined>
  stopSandbox(sandboxId: string, force?: boolean): Promise<void>
  destroySandbox(sandboxId: string): Promise<void>
  createBackup(sandbox: Sandbox, backupSnapshotName: string, registry?: DockerRegistry): Promise<void>

  removeSnapshot(snapshotName: string): Promise<void>
  buildSnapshot(
    buildInfo: BuildInfo,
    organizationId?: string,
    sourceRegistries?: DockerRegistry[],
    registry?: DockerRegistry,
    pushToInternalRegistry?: boolean,
  ): Promise<void>
  pullSnapshot(
    snapshotName: string,
    registry?: DockerRegistry,
    destinationRegistry?: DockerRegistry,
    destinationRef?: string,
    newTag?: string,
  ): Promise<void>
  snapshotExists(snapshotRef: string): Promise<boolean>
  getSnapshotInfo(snapshotName: string): Promise<RunnerSnapshotInfo>
  inspectSnapshotInRegistry(snapshotName: string, registry?: DockerRegistry): Promise<SnapshotDigestResponse>

  updateNetworkSettings(
    sandboxId: string,
    networkBlockAll?: boolean,
    networkAllowList?: string,
    networkLimitEgress?: boolean,
  ): Promise<void>

  forkSandbox(sourceSandboxId: string, newSandboxId: string): Promise<void>

  createSnapshotFromSandbox(
    sandboxId: string,
    snapshotName: string,
    organizationId: string,
    registry?: DockerRegistry,
  ): Promise<CreateSandboxSnapshotResult | undefined>

  recoverSandbox(sandbox: Sandbox, registry?: DockerRegistry, skipStart?: boolean): Promise<void>

  resizeSandbox(
    sandboxId: string,
    cpu?: number,
    memory?: number,
    disk?: number,
    registry?: DockerRegistry,
  ): Promise<void>
}

@Injectable()
export class RunnerAdapterFactory {
  private readonly logger = new Logger(RunnerAdapterFactory.name)

  constructor(private moduleRef: ModuleRef) {}

  async create(runner: Runner): Promise<RunnerAdapter> {
    switch (runner.apiVersion) {
      case '0': {
        const adapter = await this.moduleRef.create(RunnerAdapterV0)
        await adapter.init(runner)
        return adapter
      }
      case '2': {
        const adapter = await this.moduleRef.create(RunnerAdapterV2)
        await adapter.init(runner)
        return adapter
      }
      default:
        throw new Error(`Unsupported runner version: ${runner.apiVersion}`)
    }
  }
}
