/**
 * Copyright 2023 actions-toolkit authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import * as core from '@actions/core';
import * as exec from '@actions/exec';
import * as semver from 'semver';

import {Context} from '../context';
import {Buildx} from '../buildx/buildx';
import {Builder} from '../buildx/builder';
import {Config} from './config';

import {BuilderInfo, NodeInfo} from '../types/builder';

export interface BuildKitOpts {
  context: Context;
  buildx?: Buildx;
}

export class BuildKit {
  private readonly context: Context;
  private readonly buildx: Buildx;

  public readonly config: Config;

  constructor(opts: BuildKitOpts) {
    this.context = opts.context;
    this.config = new Config(this.context);
    this.buildx =
      opts?.buildx ||
      new Buildx({
        context: this.context
      });
  }

  public async getVersion(node: NodeInfo): Promise<string | undefined> {
    if (!node.buildkitVersion && node.name) {
      try {
        return await this.getVersionWithinImage(node.name);
      } catch (e) {
        core.warning(e);
      }
    }
    return node.buildkitVersion;
  }

  private async getVersionWithinImage(nodeName: string): Promise<string> {
    return exec
      .getExecOutput(`docker`, ['inspect', '--format', '{{.Config.Image}}', `${Buildx.containerNamePrefix}${nodeName}`], {
        ignoreReturnCode: true,
        silent: true
      })
      .then(bkitimage => {
        if (bkitimage.exitCode == 0 && bkitimage.stdout.length > 0) {
          return exec
            .getExecOutput(`docker`, ['run', '--rm', bkitimage.stdout.trim(), '--version'], {
              ignoreReturnCode: true,
              silent: true
            })
            .then(bkitversion => {
              if (bkitversion.exitCode == 0 && bkitversion.stdout.length > 0) {
                return `${bkitimage.stdout.trim()} => ${bkitversion.stdout.trim()}`;
              } else if (bkitversion.stderr.length > 0) {
                throw new Error(bkitimage.stderr.trim());
              }
              return bkitversion.stdout.trim();
            });
        } else if (bkitimage.stderr.length > 0) {
          throw new Error(bkitimage.stderr.trim());
        }
        return bkitimage.stdout.trim();
      });
  }

  public async versionSatisfies(builderName: string, range: string, builderInfo?: BuilderInfo): Promise<boolean> {
    if (!builderInfo) {
      builderInfo = await new Builder({
        context: this.context,
        buildx: this.buildx
      }).inspect(builderName);
    }
    for (const node of builderInfo.nodes) {
      let bkversion = node.buildkitVersion;
      if (!bkversion) {
        try {
          bkversion = await this.getVersionWithinImage(node.name || '');
        } catch (e) {
          return false;
        }
      }
      // BuildKit version reported by moby is in the format of `v0.11.0-moby`
      if (builderInfo.driver == 'docker' && !bkversion.endsWith('-moby')) {
        return false;
      }
      if (!semver.satisfies(bkversion.replace(/-moby$/, ''), range)) {
        return false;
      }
    }
    return true;
  }
}
