import { http, URL } from '@graphql-hive/core';
import { Flags } from '@oclif/core';
import Command from '../../base-command';
import {
  HTTPError,
  isAggregateError,
  MissingCdnEndpointError,
  MissingCdnKeyError,
  NetworkError,
  UnexpectedError,
} from '../../helpers/errors';

export default class ArtifactsFetch extends Command<typeof ArtifactsFetch> {
  static description = 'fetch artifacts from the CDN';
  static flags = {
    'cdn.endpoint': Flags.string({
      description: 'CDN endpoint',
    }),
    'cdn.accessToken': Flags.string({
      description: 'CDN access token',
    }),
    artifact: Flags.string({
      description: 'artifact to fetch (Note: supergraph is only available for federation projects)',
      options: ['sdl', 'supergraph', 'metadata', 'services', 'sdl.graphql', 'sdl.graphqls'],
      required: true,
    }),
    outputFile: Flags.string({
      description: 'whether to write to a file instead of stdout',
    }),
  };

  async run() {
    const { flags } = await this.parse(ArtifactsFetch);

    let cdnEndpoint: string, token: string;
    try {
      cdnEndpoint = this.ensure({
        key: 'cdn.endpoint',
        args: flags,
        env: 'HIVE_CDN_ENDPOINT',
        description: ArtifactsFetch.flags['cdn.endpoint'].description!,
      });
    } catch (e) {
      this.logDebug(e);
      throw new MissingCdnEndpointError();
    }

    try {
      token = this.ensure({
        key: 'cdn.accessToken',
        args: flags,
        env: 'HIVE_CDN_ACCESS_TOKEN',
        description: ArtifactsFetch.flags['cdn.accessToken'].description!,
      });
    } catch (e) {
      this.logDebug(e);
      throw new MissingCdnKeyError();
    }

    const artifactType = flags.artifact;

    const url = new URL(`${cdnEndpoint}/${artifactType}`);

    let response;
    try {
      response = await http.get(url.toString(), {
        headers: {
          'x-hive-cdn-key': token,
          'User-Agent': `hive-cli/${this.config.version}`,
        },
        retry: {
          retries: 3,
        },
        logger: this.flags.debug
          ? {
              info: (...args: Array<unknown>) => {
                this.logInfo(...args);
              },
              error: (...args: Array<unknown>) => {
                this.logFailure(...args);
              },
              debug: (...args: Array<unknown>) => {
                this.logInfo(...args);
              },
            }
          : undefined,
      });
    } catch (e: any) {
      const sourceError = e?.cause ?? e;
      if (isAggregateError(sourceError)) {
        throw new NetworkError(sourceError.errors[0]?.message);
      } else {
        throw new NetworkError(sourceError);
      }
    }

    if (!response.ok) {
      const responseBody = await response.text();
      throw new HTTPError(
        url.toString(),
        response.status,
        responseBody ?? response.statusText ?? 'Invalid status code for HTTP call',
      );
    }

    try {
      if (flags.outputFile) {
        const fs = await import('fs/promises');
        const contents = Buffer.from(await response.arrayBuffer());
        await fs.writeFile(flags.outputFile, contents);
        this.log(`Wrote ${contents.length} bytes to ${flags.outputFile}`);
        return;
      }

      this.log(await response.text());
    } catch (e) {
      throw new UnexpectedError(e);
    }
  }
}
