'use strict';

const https = require('https');
const path = require('path');

const {execFileAsync, repoRoot} = require('./utils');

async function fetchNpmInfo(packageName, {log}) {
  const npmArgs = ['view', `${packageName}@latest`, '--json'];
  const options = {cwd: repoRoot, maxBuffer: 10 * 1024 * 1024};
  log(`Fetching npm info for ${packageName}...`);
  const {stdout} = await execFileAsync('npm', npmArgs, options);

  let data = stdout.trim();
  if (!data) {
    throw new Error(`npm view returned empty result for ${packageName}`);
  }

  let info = JSON.parse(data);
  if (Array.isArray(info)) {
    info = info[info.length - 1];
  }

  const version = info.version || info['dist-tags']?.latest;
  let gitHead = info.gitHead || null;

  if (!gitHead) {
    const gitHeadResult = await execFileAsync(
      'npm',
      ['view', `${packageName}@${version}`, 'gitHead'],
      {cwd: repoRoot, maxBuffer: 1024 * 1024}
    );
    const possibleGitHead = gitHeadResult.stdout.trim();
    if (
      possibleGitHead &&
      possibleGitHead !== 'undefined' &&
      possibleGitHead !== 'null'
    ) {
      log(`Found gitHead for ${packageName}@${version}: ${possibleGitHead}`);
      gitHead = possibleGitHead;
    }
  }

  if (!version) {
    throw new Error(
      `Unable to determine latest published version for ${packageName}`
    );
  }
  if (!gitHead) {
    throw new Error(
      `Unable to determine git commit for ${packageName}@${version}`
    );
  }

  return {
    publishedVersion: version,
    gitHead,
  };
}

async function collectCommitsSince(packageName, sinceGitSha, {log}) {
  log(`Collecting commits for ${packageName} since ${sinceGitSha}...`);
  await execFileAsync('git', ['cat-file', '-e', `${sinceGitSha}^{commit}`], {
    cwd: repoRoot,
  });
  const {stdout} = await execFileAsync(
    'git',
    [
      'rev-list',
      '--reverse',
      `${sinceGitSha}..HEAD`,
      '--',
      path.posix.join('packages', packageName),
    ],
    {cwd: repoRoot, maxBuffer: 10 * 1024 * 1024}
  );

  return stdout
    .trim()
    .split('\n')
    .map(line => line.trim())
    .filter(Boolean);
}

async function loadCommitDetails(sha, {log}) {
  log(`Loading commit details for ${sha}...`);
  const format = ['%H', '%s', '%an', '%ae', '%ct', '%B'].join('%n');
  const {stdout} = await execFileAsync(
    'git',
    ['show', '--quiet', `--format=${format}`, sha],
    {cwd: repoRoot, maxBuffer: 10 * 1024 * 1024}
  );

  const [commitSha, subject, authorName, authorEmail, timestamp, ...rest] =
    stdout.split('\n');
  const body = rest.join('\n').trim();

  return {
    sha: commitSha.trim(),
    subject: subject.trim(),
    authorName: authorName.trim(),
    authorEmail: authorEmail.trim(),
    timestamp: +timestamp.trim() || 0,
    body,
  };
}

function extractPrNumber(subject, body) {
  const patterns = [
    /\(#(\d+)\)/,
    /https:\/\/github\.com\/facebook\/react\/pull\/(\d+)/,
  ];

  for (let i = 0; i < patterns.length; i++) {
    const pattern = patterns[i];
    const subjectMatch = subject && subject.match(pattern);
    if (subjectMatch) {
      return subjectMatch[1];
    }
    const bodyMatch = body && body.match(pattern);
    if (bodyMatch) {
      return bodyMatch[1];
    }
  }

  return null;
}

async function fetchPullRequestMetadata(prNumber, {log}) {
  log(`Fetching PR metadata for #${prNumber}...`);
  const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN || null;
  const requestOptions = {
    hostname: 'api.github.com',
    path: `/repos/facebook/react/pulls/${prNumber}`,
    method: 'GET',
    headers: {
      'User-Agent': 'generate-changelog-script',
      Accept: 'application/vnd.github+json',
    },
  };
  if (token) {
    requestOptions.headers.Authorization = `Bearer ${token}`;
  }

  return new Promise(resolve => {
    const req = https.request(requestOptions, res => {
      let raw = '';
      res.on('data', chunk => {
        raw += chunk;
      });
      res.on('end', () => {
        if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
          try {
            const json = JSON.parse(raw);
            resolve({
              authorLogin: json.user?.login || null,
            });
          } catch (error) {
            process.stderr.write(
              `Warning: unable to parse GitHub response for PR #${prNumber}: ${error.message}\n`
            );
            resolve(null);
          }
        } else {
          process.stderr.write(
            `Warning: GitHub API request failed for PR #${prNumber} with status ${res.statusCode}\n`
          );
          resolve(null);
        }
      });
    });

    req.on('error', error => {
      process.stderr.write(
        `Warning: GitHub API request errored for PR #${prNumber}: ${error.message}\n`
      );
      resolve(null);
    });

    req.end();
  });
}

module.exports = {
  fetchNpmInfo,
  collectCommitsSince,
  loadCommitDetails,
  extractPrNumber,
  fetchPullRequestMetadata,
};