const fetch = require('node-fetch');
const {
  transformRecordedData: transformGitHub,
  setupGitHub,
  teardownGitHub,
  setupGitHubTest,
  teardownGitHubTest,
} = require('./github');
const {
  transformRecordedData: transformGitLab,
  setupGitLab,
  teardownGitLab,
  setupGitLabTest,
  teardownGitLabTest,
} = require('./gitlab');
const { getGitClient } = require('./common');

function getEnvs() {
  const {
    NETLIFY_API_TOKEN: netlifyApiToken,
    GITHUB_REPO_TOKEN: githubToken,
    GITLAB_REPO_TOKEN: gitlabToken,
    NETLIFY_INSTALLATION_ID: installationId,
  } = process.env;
  if (!netlifyApiToken) {
    throw new Error(
      'Please set NETLIFY_API_TOKEN, GITHUB_REPO_TOKEN, GITLAB_REPO_TOKEN, NETLIFY_INSTALLATION_ID  environment variables',
    );
  }
  return { netlifyApiToken, githubToken, gitlabToken, installationId };
}

const apiRoot = 'https://api.netlify.com/api/v1/';

async function get(netlifyApiToken, path) {
  const response = await fetch(`${apiRoot}${path}`, {
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${netlifyApiToken}`,
    },
  }).then(res => res.json());

  return response;
}

async function post(netlifyApiToken, path, payload) {
  const response = await fetch(`${apiRoot}${path}`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${netlifyApiToken}`,
    },
    ...(payload ? { body: JSON.stringify(payload) } : {}),
  }).then(res => res.json());

  return response;
}

async function del(netlifyApiToken, path) {
  const response = await fetch(`${apiRoot}${path}`, {
    method: 'DELETE',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${netlifyApiToken}`,
    },
  }).then(res => res.text());

  return response;
}

async function createSite(netlifyApiToken, payload) {
  return post(netlifyApiToken, 'sites', payload);
}

async function enableIdentity(netlifyApiToken, siteId) {
  return post(netlifyApiToken, `sites/${siteId}/identity`, {});
}

async function enableGitGateway(netlifyApiToken, siteId, provider, token, repo) {
  return post(netlifyApiToken, `sites/${siteId}/services/git/instances`, {
    [provider]: {
      repo,
      access_token: token,
    },
  });
}

async function enableLargeMedia(netlifyApiToken, siteId) {
  return post(netlifyApiToken, `sites/${siteId}/services/large-media/instances`, {});
}

async function waitForDeploys(netlifyApiToken, siteId) {
  for (let i = 0; i < 10; i++) {
    const deploys = await get(netlifyApiToken, `sites/${siteId}/deploys`);
    if (deploys.some(deploy => deploy.state === 'ready')) {
      console.info('Deploy finished for site:', siteId);
      return;
    }
    console.info('Waiting on deploy of site:', siteId);
    await new Promise(resolve => setTimeout(resolve, 30 * 1000));
  }
  console.info('Timed out waiting on deploy of site:', siteId);
}

async function createUser(netlifyApiToken, siteUrl, email, password) {
  const response = await fetch(`${siteUrl}/.netlify/functions/create-user`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${netlifyApiToken}`,
    },
    body: JSON.stringify({ email, password }),
  });

  if (response.ok) {
    console.info('User created successfully');
  } else {
    throw new Error('Failed to create user');
  }
}

const netlifySiteURL = 'https://fake-site-url.netlify.com/';
const email = 'static@p-m.si';
const password = '12345678';
const backendName = 'git-gateway';

const methods = {
  github: {
    setup: setupGitHub,
    teardown: teardownGitHub,
    setupTest: setupGitHubTest,
    teardownTest: teardownGitHubTest,
    transformData: transformGitHub,
    createSite: (netlifyApiToken, result) => {
      const { installationId } = getEnvs();
      return createSite(netlifyApiToken, {
        repo: {
          provider: 'github',
          installation_id: installationId,
          repo: `${result.owner}/${result.repo}`,
        },
      });
    },
    token: () => getEnvs().githubToken,
  },
  gitlab: {
    setup: setupGitLab,
    teardown: teardownGitLab,
    setupTest: setupGitLabTest,
    teardownTest: teardownGitLabTest,
    transformData: transformGitLab,
    createSite: async (netlifyApiToken, result) => {
      const { id, public_key } = await post(netlifyApiToken, 'deploy_keys');
      const { gitlabToken } = getEnvs();
      const project = `${result.owner}/${result.repo}`;
      await fetch(`https://gitlab.com/api/v4/projects/${encodeURIComponent(project)}/deploy_keys`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          Authorization: `Bearer ${gitlabToken}`,
        },
        body: JSON.stringify({ title: 'Netlify Deploy Key', key: public_key, can_push: false }),
      }).then(res => res.json());

      const site = await createSite(netlifyApiToken, {
        account_slug: result.owner,
        repo: {
          provider: 'gitlab',
          repo: `${result.owner}/${result.repo}`,
          deploy_key_id: id,
        },
      });

      await fetch(`https://gitlab.com/api/v4/projects/${encodeURIComponent(project)}/hooks`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          Authorization: `Bearer ${gitlabToken}`,
        },
        body: JSON.stringify({
          url: 'https://api.netlify.com/hooks/gitlab',
          push_events: true,
          merge_requests_events: true,
          enable_ssl_verification: true,
        }),
      }).then(res => res.json());

      return site;
    },
    token: () => getEnvs().gitlabToken,
  },
};

async function setupGitGateway(options) {
  const { provider, ...rest } = options;
  const result = await methods[provider].setup(rest);

  const { netlifyApiToken } = getEnvs();

  console.info(`Creating Netlify Site for provider: ${provider}`);
  let site_id, ssl_url;
  try {
    ({ site_id, ssl_url } = await methods[provider].createSite(netlifyApiToken, result));
  } catch (e) {
    console.error(e);
    throw e;
  }

  console.info('Enabling identity for site:', site_id);
  await enableIdentity(netlifyApiToken, site_id);

  console.info('Enabling git gateway for site:', site_id);
  const token = methods[provider].token();
  await enableGitGateway(
    netlifyApiToken,
    site_id,
    provider,
    token,
    `${result.owner}/${result.repo}`,
  );

  console.info('Enabling large media for site:', site_id);
  await enableLargeMedia(netlifyApiToken, site_id);

  const git = getGitClient(result.tempDir);
  await git.raw([
    'config',
    '-f',
    '.lfsconfig',
    'lfs.url',
    `https://${site_id}.netlify.com/.netlify/large-media`,
  ]);
  await git.addConfig('commit.gpgsign', 'false');
  await git.add('.lfsconfig');
  await git.commit('add .lfsconfig');
  await git.push('origin', 'main');

  await waitForDeploys(netlifyApiToken, site_id);
  console.info('Creating user for site:', site_id, 'with email:', email);

  try {
    await createUser(netlifyApiToken, ssl_url, email, password);
  } catch (e) {
    console.error(e);
  }

  return {
    ...result,
    user: {
      ...result.user,
      backendName,
      netlifySiteURL: ssl_url,
      email,
      password,
    },
    site_id,
    ssl_url,
    provider,
  };
}

async function teardownGitGateway(taskData) {
  const { netlifyApiToken } = getEnvs();
  const { site_id } = taskData;
  console.info('Deleting Netlify site:', site_id);
  await del(netlifyApiToken, `sites/${site_id}`);

  return methods[taskData.provider].teardown(taskData);
}

async function setupGitGatewayTest(taskData) {
  return methods[taskData.provider].setupTest(taskData);
}

async function teardownGitGatewayTest(taskData) {
  return methods[taskData.provider].teardownTest(taskData, {});
}

module.exports = {
  setupGitGateway,
  teardownGitGateway,
  setupGitGatewayTest,
  teardownGitGatewayTest,
};