/**
 * register-agent GitHub Action script.
 *
 * Flow:
 *   1. Request a GitHub OIDC token via @actions/core.getIDToken()
 *   2. Exchange the OIDC token for a SentryAgent.ai access token via POST /oidc/token
 *   3. Register a new agent via POST /agents using the access token
 *   4. Set the `agent-id` output
 *
 * Error handling:
 *   - OIDC exchange failures emit a clear message with a link to the trust policy setup docs
 *   - Agent registration failures surface the API error message
 */

'use strict';

const core = require('@actions/core');
const { HttpClient, BearerCredentialHandler } = require('@actions/http-client');

/**
 * Exchanges a GitHub OIDC JWT for a SentryAgent.ai access token.
 *
 * @param {string} apiUrl - Base URL of the SentryAgent.ai AgentIdP API.
 * @param {string} oidcToken - GitHub OIDC JWT obtained from core.getIDToken().
 * @returns {Promise<string>} The SentryAgent.ai access token.
 * @throws {Error} If the exchange fails, with a message including trust policy setup instructions.
 */
async function exchangeOIDCToken(apiUrl, oidcToken) {
  const client = new HttpClient('sentryagent-register-agent/1.0');
  const url = `${apiUrl}/api/v1/oidc/token`;

  const body = JSON.stringify({
    provider: 'github',
    token: oidcToken,
  });

  let response;
  try {
    response = await client.post(url, body, {
      'Content-Type': 'application/json',
      Accept: 'application/json',
    });
  } catch (err) {
    throw new Error(
      `Failed to reach the SentryAgent.ai OIDC token endpoint at ${url}. ` +
        `Check that the api-url input is correct and the API is reachable.\n` +
        `Underlying error: ${err instanceof Error ? err.message : String(err)}`,
    );
  }

  const rawBody = await response.readBody();
  const statusCode = response.message.statusCode ?? 0;

  if (statusCode === 403) {
    throw new Error(
      'GitHub OIDC token exchange was rejected with HTTP 403 (Forbidden). ' +
        'This usually means no trust policy has been registered for this repository.\n\n' +
        'To fix this, register a trust policy by calling:\n' +
        `  POST ${apiUrl}/oidc/trust-policies\n` +
        '  Body: { "provider": "github", "repository": "org/repo", "agentId": "<agent-id>" }\n\n' +
        'For full setup instructions, visit: https://docs.sentryagent.ai/github-actions#trust-policy',
    );
  }

  if (statusCode < 200 || statusCode >= 300) {
    let detail = rawBody;
    try {
      const parsed = JSON.parse(rawBody);
      detail = parsed.message ?? parsed.error_description ?? rawBody;
    } catch {
      // use rawBody as-is
    }
    throw new Error(
      `OIDC token exchange failed with HTTP ${statusCode}: ${detail}\n` +
        'For trust policy setup instructions, visit: https://docs.sentryagent.ai/github-actions#trust-policy',
    );
  }

  let tokenData;
  try {
    tokenData = JSON.parse(rawBody);
  } catch {
    throw new Error(`OIDC token exchange returned non-JSON response: ${rawBody}`);
  }

  if (typeof tokenData.access_token !== 'string' || tokenData.access_token.length === 0) {
    throw new Error('OIDC token exchange response did not include an access_token.');
  }

  return tokenData.access_token;
}

/**
 * Registers a new agent via POST /agents.
 *
 * @param {string} apiUrl - Base URL of the SentryAgent.ai AgentIdP API.
 * @param {string} accessToken - A valid SentryAgent.ai Bearer access token.
 * @param {string} agentName - Email (unique name) for the new agent.
 * @param {string} agentDescription - Optional description stored as the owner field.
 * @returns {Promise<string>} The UUID of the newly registered agent.
 * @throws {Error} If the API returns a non-2xx response.
 */
async function registerAgent(apiUrl, accessToken, agentName, agentDescription) {
  const auth = new BearerCredentialHandler(accessToken);
  const client = new HttpClient('sentryagent-register-agent/1.0', [auth]);
  const url = `${apiUrl}/api/v1/agents`;

  const payload = {
    email: agentName,
    agentType: 'custom',
    version: '1.0.0',
    capabilities: [],
    owner: agentDescription || agentName,
    deploymentEnv: 'production',
  };

  let response;
  try {
    response = await client.post(url, JSON.stringify(payload), {
      'Content-Type': 'application/json',
      Accept: 'application/json',
    });
  } catch (err) {
    throw new Error(
      `Failed to reach the SentryAgent.ai agents endpoint at ${url}.\n` +
        `Underlying error: ${err instanceof Error ? err.message : String(err)}`,
    );
  }

  const rawBody = await response.readBody();
  const statusCode = response.message.statusCode ?? 0;

  if (statusCode < 200 || statusCode >= 300) {
    let detail = rawBody;
    try {
      const parsed = JSON.parse(rawBody);
      detail = parsed.message ?? parsed.error ?? rawBody;
    } catch {
      // use rawBody as-is
    }
    throw new Error(`Agent registration failed with HTTP ${statusCode}: ${detail}`);
  }

  let agentData;
  try {
    agentData = JSON.parse(rawBody);
  } catch {
    throw new Error(`Agent registration returned non-JSON response: ${rawBody}`);
  }

  if (typeof agentData.agentId !== 'string' || agentData.agentId.length === 0) {
    throw new Error('Agent registration response did not include an agentId.');
  }

  return agentData.agentId;
}

/**
 * Main entry point for the register-agent GitHub Action.
 *
 * @returns {Promise<void>}
 */
async function run() {
  try {
    // Read inputs
    const apiUrl = core.getInput('api-url', { required: true }).replace(/\/$/, '');
    const agentName = core.getInput('agent-name', { required: true });
    const agentDescription = core.getInput('agent-description') || '';

    core.info(`Requesting GitHub OIDC token for audience: ${apiUrl}`);
    let oidcToken;
    try {
      oidcToken = await core.getIDToken(apiUrl);
    } catch (err) {
      throw new Error(
        'Failed to obtain a GitHub OIDC token. ' +
          "Ensure the workflow has 'id-token: write' permission in its permissions block.\n\n" +
          'Example:\n' +
          'permissions:\n' +
          '  id-token: write\n' +
          '  contents: read\n\n' +
          `Underlying error: ${err instanceof Error ? err.message : String(err)}\n` +
          'For setup instructions, visit: https://docs.sentryagent.ai/github-actions#trust-policy',
      );
    }

    core.info('Exchanging GitHub OIDC token for SentryAgent.ai access token...');
    const accessToken = await exchangeOIDCToken(apiUrl, oidcToken);

    core.info(`Registering agent: ${agentName}`);
    const agentId = await registerAgent(apiUrl, accessToken, agentName, agentDescription);

    core.setOutput('agent-id', agentId);
    core.info(`Agent registered successfully. agent-id: ${agentId}`);
  } catch (err) {
    core.setFailed(err instanceof Error ? err.message : String(err));
  }
}

run();
