/**
 * Example:
 * `TARGET=<target_id> FEDERATION=1 STAGE=local TOKEN=<access_token> pnpm seed:schemas`
 *
 * Where
 * - <target_id> = target's Resource ID
 * - <access_token> = An Access Token (obtained from Organization's Settings page)
 */
import { parse as parsePath } from 'path';
import { printSchema } from 'graphql';
import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader';
import { loadSchema, loadTypedefs } from '@graphql-tools/load';

const token = process.env.TOKEN || process.env.HIVE_TOKEN;

if (!token) {
  throw new Error('Missing "TOKEN"');
}

const target = process.env.TARGET;
const isFederation = process.env.FEDERATION === '1';
const stage = process.env.STAGE || 'local';

let graphqlEndpoint: string;
let environment: string;
switch (stage.toLowerCase()) {
  case 'staging': {
    graphqlEndpoint = 'https://app.hiveready.dev/graphql';
    environment = 'staging';
    break;
  }
  case 'dev': {
    graphqlEndpoint = 'https://app.buzzcheck.dev/graphql';
    environment = 'dev';
    break;
  }
  default: {
    graphqlEndpoint = 'http://localhost:3001/graphql';
    environment = 'local';
  }
}

console.log(`
  Environment:               ${environment}
  Hive GraphQL endpoint:     ${graphqlEndpoint}
  Schema type:               ${isFederation ? 'federation' : 'single'}
`);

const publishMutationDocument =
  /* GraphQL */
  `
    mutation schemaPublish($input: SchemaPublishInput!) {
      schemaPublish(input: $input) {
        __typename
        ... on SchemaPublishSuccess {
          initial
          valid
          message
          linkToWebsite
          changes {
            nodes {
              message
              criticality
            }
            total
          }
        }
        ... on SchemaPublishError {
          valid
          linkToWebsite
          changes {
            nodes {
              message
              criticality
            }
            total
          }
          errors {
            nodes {
              message
            }
            total
          }
        }
      }
    }
  `;

async function publishSchema(args: { sdl: string; service?: string; target?: string }) {
  const commit = `${Date.now()}`;
  const response = await fetch(graphqlEndpoint, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${token}`,
    },
    body: JSON.stringify({
      query: publishMutationDocument,
      variables: {
        input: {
          author: 'MoneyBoy',
          commit,
          sdl: args.sdl,
          service: args.service,
          url: `https://${args.service ? `${args.service}.` : ''}localhost/graphql`,
          target: args.target ? { byId: args.target } : null,
        },
      },
    }),
  }).then(res => res.json());
  return response as {
    data: {
      schemaPublish: {
        linkToWebsite: string;
        valid: boolean;
      } | null;
    } | null;
    errors?: any[];
  };
}

function minifySchema(schema: string): string {
  return schema.replace(/\s+/g, ' ').trim();
}

async function single() {
  const schema = await loadSchema('scripts/seed-schemas/mono.graphql', {
    loaders: [new GraphQLFileLoader()],
  });
  const sdl = minifySchema(printSchema(schema));
  const result = await publishSchema({
    sdl,
    target,
  });
  if (result?.errors || result?.data?.schemaPublish?.valid !== true) {
    console.error(`Published schema is invalid.`);
  } else {
    console.log(`Published successfully (${result.data.schemaPublish?.linkToWebsite})`);
  }
  return result;
}

async function federation() {
  const schemaDocs = await loadTypedefs('scripts/seed-schemas/federated/*.graphql', {
    loaders: [new GraphQLFileLoader()],
  });
  const uploads = schemaDocs
    .map(async d => {
      if (!d.rawSDL) {
        console.error(`Missing SDL at "${d.location}"`);
        return null;
      }
      const service = d.location ? parsePath(d.location).name.replaceAll('.', '-') : undefined;

      const result = await publishSchema({
        sdl: minifySchema(d.rawSDL),
        service,
        target,
      });

      if (result?.errors || result?.data?.schemaPublish?.valid !== true) {
        console.error(`Published schema is invalid for "${service}".`);
      } else {
        console.log(`Published "${service}" successfully.`);
      }

      return result;
    })
    .filter(Boolean);

  return Promise.all(uploads);
}

if (isFederation === false) {
  await single();
} else {
  await federation();
}
