/**
 * TODOLIST
 * * [ ] 根据操作系统的不同,获取到特定的 Docker 对象
 * * [ ] 查询容器状态
 * * [ ] 安装镜像容器
 * * [ ] 在容器内操作命令, Docker API.exec
 */

import Dockerode from 'dockerode'
import { chmod, createReadStream, createWriteStream, mkdirSync, readFileSync, rmdirSync, rmSync } from 'fs'
import { resolve } from 'path'
import { removeUnreadCharacter } from '.'
import LogUtil from './log'
import tar from 'tar-fs'

class Docker {
  docker: Dockerode
  username: string
  env: string[]
  host: string
  constructor (host = 'host.docker.internal', username: string) {
    this.host = host
    this.docker = new Dockerode({ host, port: 2375 })
    this.username = username
    this.env = []
  }
  async checkContainer (name: string) {
    let targetContainerName = 'logwire_backend_helper.' + name
    let containers = await this.docker.listContainers({ filters: JSON.stringify({ status: ['created', 'restarting', 'running', 'removing', 'paused', 'exited', 'dead' ], name: [targetContainerName] }) })
    if (containers.length) {
      let container = await this.docker.getContainer(containers[0].Id)
      return container
    } else {
      return
    }
  }
  async checkAndCreateContainer ({ name, img, env, portBindings, cmd, exposedPorts }: { name: string, img: string, env?: string[], portBindings?: Record<string, any>, cmd?: string[], exposedPorts?: Record<string, any> }) {
    let targetContainerName = 'logwire_backend_helper.' + name
    let containers = await this.docker.listContainers({ filters: JSON.stringify({ status: ['created', 'restarting', 'running', 'removing', 'paused', 'exited', 'dead' ], name: [targetContainerName] }) })
    if (containers.length) {
      let container = await this.docker.getContainer(containers[0].Id)
      return container
    } else {
      let images = await this.docker.listImages({ filters: `{ "reference": ["${img}"] }` })
      if (images.length === 0) {
        await new Promise<void>((resolve, reject) => {
          this.docker.pull(img, (err: any, stream: any) => {
            this.docker.modem.followProgress(stream, onFinished, onProgress);
            function onFinished(err: any, output: any) {
                if (!err) {
                  resolve()
                } else {
                  reject(err)
                }
            }
            function onProgress(event: any) {
            }
          })
        })
      } 
      let opt = {
        name: targetContainerName,
        "Tty": true,
        Image: img,
        Env: env,
        Cmd: cmd,
        ExposedPorts: exposedPorts,
        HostConfig: {
          PortBindings: portBindings || {}
        }
      }
      return this.docker.createContainer(opt)
    }
  }
  async startContainer({ container }: { container: Dockerode.Container }) {
    let info = await container.inspect()
    if (info.State.Running) {
    } else {
      await container.start()
    }
    return container
  }
  async execContainerCommand ({ container, cmd, dir, quiet }: {container: Dockerode.Container, cmd: string | string[], dir?: string, quiet?: boolean }) {
    let exec = await container.exec({
      Cmd: cmd instanceof Array ? cmd : cmd.split(' '),
      AttachStdout: true,
      AttachStderr: true,
      WorkingDir: dir,
      User: 'root',
      Env: this.env
    })
    let result = await exec?.start({})
    return new Promise<string>((resolve, reject) => {
      let logs = ''
      let data = ''
      result?.on('data', chunk => {
        let text = removeUnreadCharacter(chunk.toString())
        !quiet && LogUtil.print(this.username, '[info] ' + text )
        logs += text
        data += text
        logs = logs.substring(logs.length - 1200)
      })
      result?.on('error', error => {
        logs += error.message + '\n' + error.stack
      })
      result?.on('end', async () => {
        let info = await exec.inspect()
        if (info?.ExitCode) {
          reject({ command: cmd, message: 'ExitCode: ' + info.ExitCode, exitcode: info.ExitCode, logs })
        } else {
          resolve(data)
        }
      })
    })
  }

  async putArchive ({ container, tarPath, targetPath }: { container: Dockerode.Container, tarPath: string, targetPath: string }) {
    return container.putArchive(tarPath, { path: targetPath })
  }
  async writeFile ({ container, path, text }: { container: Dockerode.Container, path: string, text: string }) {
    await this.execContainerCommand({ container, cmd: ['bash', '-c', 'echo ' + text + ' > ' + path] })
  }
  async appendFile({ container, path, text }: { container: Dockerode.Container, path: string, text: string }) {
    await this.execContainerCommand({ container, cmd: ['bash', '-c', 'echo ' + text + ' >> ' + path] })
  }
  async getFile ({ container, path }: { container: Dockerode.Container, path: string }): Promise<string> {
    return new Promise(async (resolve, reject) => {
      try {
        let readstream = await container.getArchive({ path })
        let tempFilePath = './temp_' + Math.random() + '.tar'
        let tempUnpackFolder = tempFilePath.replace('.tar', '')
        let tempUnpackFile = tempUnpackFolder + '/' + path.replace(/.*\//, '')
        mkdirSync(tempUnpackFolder)
        let writestream = createWriteStream(tempFilePath)
        readstream.pipe(writestream).on('finish', () => {
          createReadStream(tempFilePath).pipe(tar.extract(tempUnpackFolder)).on('finish', () => {
            let content = readFileSync(tempUnpackFile, { encoding: 'utf-8' })
            rmSync(tempFilePath)
            rmSync(tempUnpackFolder, { recursive: true, force: true })
            resolve(content)
          })
        })
      } catch (err) {
        reject(err)
      }
    })
  }
  async switchJavaVersion ({ container, username, version }: { container: Dockerode.Container, username: string, version: 17 | 8}) {
    LogUtil.printInfo(username, '更换 Java 环境')
    await this.execContainerCommand({ container, cmd: 'rm /usr/bin/java'})
    if (version === 8) {
      await this.execContainerCommand({ container, cmd: 'ln -sf /var/java-8-openjdk/bin/java /usr/bin/java' })
    } else {
      await this.execContainerCommand({ container, cmd: 'ln -sf /var/java-17-openjdk/bin/java /usr/bin/java' })
    }
  }
  async preStartSystem ({ container, username, platform }: { container: Dockerode.Container, username: string, platform: 'v1' | 'v2' }) {
    await this.execContainerCommand({ container, cmd: 'rm -f /root/.pm2/logs/backend-out.log'})
    await this.execContainerCommand({ container, cmd: 'rm -f /root/.pm2/logs/backend-error.log'})
    await this.execContainerCommand({ container, cmd: 'rm -f /root/.pm2/logs/gateway-out.log'})
    await this.execContainerCommand({ container, cmd: 'rm -f /root/.pm2/logs/gateway-error.log'})
    await this.execContainerCommand({ container, cmd: 'rm -f /root/.pm2/logs/platform-out.log'})
    await this.execContainerCommand({ container, cmd: 'rm -f /root/.pm2/logs/platform-error.log'})
    try { await this.execContainerCommand({ container, cmd: 'pm2 delete backend' }) } catch (err) {}
    try { await this.execContainerCommand({ container, cmd: 'pm2 delete gateway' }) } catch (err) {}
    try { await this.execContainerCommand({ container, cmd: 'pm2 delete platform' }) } catch (err) {}
    if (platform === 'v1') {
      await this.switchJavaVersion({ container, username, version: 8 })
      const statusV2 = (await getPgTableData( 'v2', username))?.status
      if (statusV2 === 'launched' || statusV2 === 'debug-launched') {
        await changeProjectStatus( 'v2', username, 'status', 'compiled') // 如果系统在运行,回退到已创建的状态
      }
    } else {
      await this.switchJavaVersion({ container, username, version: 17 })
      const statusV1 = (await getPgTableData( 'v1', username))?.status
      if (statusV1 === 'launched') {
        await changeProjectStatus( 'v1', username, 'status', 'compiled') // 如果系统在运行,回退到已创建的状态
      }
    }
  }
}

export function createDockerFactory (username: string) {
  const DOCKERHOST = useRuntimeConfig().public.dockerHost
  return new Docker(process.env.NODE_ENV === 'development' ? 'localhost': DOCKERHOST, username)
}