/**
 * GitHub tool adapters.
 *
 * Credential resolution: vault first (getSecret), falling back to process.env
 * for backwards compatibility during the migration window.
 *
 * All requests use the GitHub REST API v3 (api.github.com).
 * Responses are curated to return minimal, safe shapes — never raw API blobs.
 * The GITHUB_TOKEN is never echoed in returned errors.
 */

import { getSecret } from '../../secrets.js';
import { wrapFetch } from '../../egress.js';

const GH_API = 'https://api.github.com';

// ── Credential helpers ────────────────────────────────────────────────────────

function resolveCredential(name, options = {}) {
  try {
    const secret = getSecret(name, {
      agent_id: options.agent_id,
      project_id: options.project_id,
    });
    if (secret) return secret;
  } catch {
    // Vault not ready — fall through to process.env.
  }
  return process.env[name] || null;
}

function requireCredential(name, options = {}) {
  const value = resolveCredential(name, options);
  if (!value) throw new Error(`${name} not configured`);
  return value;
}

function githubHeaders(token) {
  return {
    Authorization: `Bearer ${token}`,
    Accept: 'application/vnd.github+json',
    'X-GitHub-Api-Version': '2022-11-28',
    'User-Agent': 'madarx-os',
  };
}

// ── Input validation ──────────────────────────────────────────────────────────

const REPO_FULL_NAME_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;

function validateRepoFullName(repo_full_name) {
  if (!repo_full_name || typeof repo_full_name !== 'string') {
    throw new Error('repo_full_name is required (format: owner/repo)');
  }
  if (!REPO_FULL_NAME_RE.test(repo_full_name)) {
    throw new Error(`repo_full_name must be in owner/repo format, got: ${repo_full_name}`);
  }
}

function parseOwnerRepo(repo_full_name) {
  const [owner, repo] = repo_full_name.split('/');
  return { owner, repo };
}

// ── Error extraction ──────────────────────────────────────────────────────────

async function extractGitHubError(res) {
  try {
    const data = await res.json();
    return data?.message || `GitHub API error: ${res.status}`;
  } catch {
    return `GitHub API error: ${res.status}`;
  }
}

// ── Response shapers ──────────────────────────────────────────────────────────

function shapeRepo(r) {
  return {
    id: r.id,
    name: r.name,
    full_name: r.full_name,
    private: r.private,
    default_branch: r.default_branch,
    description: r.description || null,
    stars: r.stargazers_count,
    forks: r.forks_count,
    updated_at: r.updated_at,
  };
}

function shapeIssue(i) {
  return {
    number: i.number,
    title: i.title,
    state: i.state,
    user: { login: i.user?.login },
    labels: (i.labels || []).map(l => (typeof l === 'string' ? l : l.name)),
    created_at: i.created_at,
    updated_at: i.updated_at,
    html_url: i.html_url,
    body_excerpt: typeof i.body === 'string' ? i.body.slice(0, 500) : null,
  };
}

function shapePR(pr) {
  return {
    number: pr.number,
    title: pr.title,
    state: pr.state,
    user: { login: pr.user?.login },
    labels: (pr.labels || []).map(l => (typeof l === 'string' ? l : l.name)),
    created_at: pr.created_at,
    updated_at: pr.updated_at,
    html_url: pr.html_url,
    body_excerpt: typeof pr.body === 'string' ? pr.body.slice(0, 500) : null,
    head: { ref: pr.head?.ref, sha: pr.head?.sha },
    base: { ref: pr.base?.ref },
    merged: pr.merged || false,
    mergeable: pr.mergeable ?? null,
  };
}

// ── Adapters ──────────────────────────────────────────────────────────────────

export async function ghRepoRead(input = {}, options = {}) {
  validateRepoFullName(input.repo_full_name);
  const { owner, repo } = parseOwnerRepo(input.repo_full_name);
  const token = requireCredential('GITHUB_TOKEN', options);
  const agentId = options.agent_id ?? null;

  const res = await wrapFetch('gh_repo_read', agentId)(`${GH_API}/repos/${owner}/${repo}`, {
    headers: githubHeaders(token),
  });

  if (!res.ok) {
    const message = await extractGitHubError(res);
    throw new Error(message);
  }

  const data = await res.json();
  return shapeRepo(data);
}

export async function ghRepoList(input = {}, options = {}) {
  const token = requireCredential('GITHUB_TOKEN', options);
  const agentId = options.agent_id ?? null;

  const url = new URL(`${GH_API}/user/repos`);
  if (input.visibility) url.searchParams.set('visibility', String(input.visibility));
  if (input.type) url.searchParams.set('type', String(input.type));
  if (input.sort) url.searchParams.set('sort', String(input.sort));
  const perPage = Math.min(Math.max(Number(input.per_page || 30), 1), 100);
  url.searchParams.set('per_page', String(perPage));

  const res = await wrapFetch('gh_repo_list', agentId)(url, {
    headers: githubHeaders(token),
  });

  if (!res.ok) {
    const message = await extractGitHubError(res);
    throw new Error(message);
  }

  const data = await res.json();
  return { repos: (Array.isArray(data) ? data : []).map(shapeRepo) };
}

export async function ghIssueList(input = {}, options = {}) {
  validateRepoFullName(input.repo_full_name);
  const { owner, repo } = parseOwnerRepo(input.repo_full_name);
  const token = requireCredential('GITHUB_TOKEN', options);
  const agentId = options.agent_id ?? null;

  const url = new URL(`${GH_API}/repos/${owner}/${repo}/issues`);
  if (input.state) url.searchParams.set('state', String(input.state));
  if (input.labels) url.searchParams.set('labels', String(input.labels));
  const perPage = Math.min(Math.max(Number(input.per_page || 30), 1), 100);
  url.searchParams.set('per_page', String(perPage));

  const res = await wrapFetch('gh_issue_list', agentId)(url, {
    headers: githubHeaders(token),
  });

  if (!res.ok) {
    const message = await extractGitHubError(res);
    throw new Error(message);
  }

  const data = await res.json();
  return { issues: (Array.isArray(data) ? data : []).map(shapeIssue) };
}

export async function ghIssueCreate(input = {}, options = {}) {
  validateRepoFullName(input.repo_full_name);
  const { owner, repo } = parseOwnerRepo(input.repo_full_name);
  if (!input.title) throw new Error('title is required');
  const token = requireCredential('GITHUB_TOKEN', options);
  const agentId = options.agent_id ?? null;

  const body = { title: String(input.title) };
  if (input.body) body.body = String(input.body);
  if (Array.isArray(input.labels)) body.labels = input.labels.map(String);
  if (Array.isArray(input.assignees)) body.assignees = input.assignees.map(String);

  const res = await wrapFetch('gh_issue_create', agentId)(`${GH_API}/repos/${owner}/${repo}/issues`, {
    method: 'POST',
    headers: { ...githubHeaders(token), 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  });

  if (!res.ok) {
    const message = await extractGitHubError(res);
    throw new Error(message);
  }

  const data = await res.json();
  return shapeIssue(data);
}

export async function ghIssueComment(input = {}, options = {}) {
  validateRepoFullName(input.repo_full_name);
  const { owner, repo } = parseOwnerRepo(input.repo_full_name);
  if (!input.issue_number) throw new Error('issue_number is required');
  if (!input.body) throw new Error('body is required');
  const token = requireCredential('GITHUB_TOKEN', options);
  const agentId = options.agent_id ?? null;

  const res = await wrapFetch('gh_issue_comment', agentId)(
    `${GH_API}/repos/${owner}/${repo}/issues/${input.issue_number}/comments`,
    {
      method: 'POST',
      headers: { ...githubHeaders(token), 'Content-Type': 'application/json' },
      body: JSON.stringify({ body: String(input.body) }),
    }
  );

  if (!res.ok) {
    const message = await extractGitHubError(res);
    throw new Error(message);
  }

  const data = await res.json();
  return {
    id: data.id,
    html_url: data.html_url,
    created_at: data.created_at,
  };
}

export async function ghPRList(input = {}, options = {}) {
  validateRepoFullName(input.repo_full_name);
  const { owner, repo } = parseOwnerRepo(input.repo_full_name);
  const token = requireCredential('GITHUB_TOKEN', options);
  const agentId = options.agent_id ?? null;

  const url = new URL(`${GH_API}/repos/${owner}/${repo}/pulls`);
  if (input.state) url.searchParams.set('state', String(input.state));
  if (input.head) url.searchParams.set('head', String(input.head));
  if (input.base) url.searchParams.set('base', String(input.base));
  const perPage = Math.min(Math.max(Number(input.per_page || 30), 1), 100);
  url.searchParams.set('per_page', String(perPage));

  const res = await wrapFetch('gh_pr_list', agentId)(url, {
    headers: githubHeaders(token),
  });

  if (!res.ok) {
    const message = await extractGitHubError(res);
    throw new Error(message);
  }

  const data = await res.json();
  return { pull_requests: (Array.isArray(data) ? data : []).map(shapePR) };
}

export async function ghPRCreate(input = {}, options = {}) {
  validateRepoFullName(input.repo_full_name);
  const { owner, repo } = parseOwnerRepo(input.repo_full_name);
  if (!input.title) throw new Error('title is required');
  if (!input.head) throw new Error('head is required');
  if (!input.base) throw new Error('base is required');
  const token = requireCredential('GITHUB_TOKEN', options);
  const agentId = options.agent_id ?? null;

  const body = {
    title: String(input.title),
    head: String(input.head),
    base: String(input.base),
  };
  if (input.body) body.body = String(input.body);
  if (input.draft !== undefined) body.draft = Boolean(input.draft);

  const res = await wrapFetch('gh_pr_create', agentId)(`${GH_API}/repos/${owner}/${repo}/pulls`, {
    method: 'POST',
    headers: { ...githubHeaders(token), 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  });

  if (!res.ok) {
    const message = await extractGitHubError(res);
    throw new Error(message);
  }

  const data = await res.json();
  return shapePR(data);
}

export async function ghPRComment(input = {}, options = {}) {
  validateRepoFullName(input.repo_full_name);
  const { owner, repo } = parseOwnerRepo(input.repo_full_name);
  if (!input.pr_number) throw new Error('pr_number is required');
  if (!input.body) throw new Error('body is required');
  const token = requireCredential('GITHUB_TOKEN', options);
  const agentId = options.agent_id ?? null;

  // GitHub PR comments use the issues endpoint (by design).
  const res = await wrapFetch('gh_pr_comment', agentId)(
    `${GH_API}/repos/${owner}/${repo}/issues/${input.pr_number}/comments`,
    {
      method: 'POST',
      headers: { ...githubHeaders(token), 'Content-Type': 'application/json' },
      body: JSON.stringify({ body: String(input.body) }),
    }
  );

  if (!res.ok) {
    const message = await extractGitHubError(res);
    throw new Error(message);
  }

  const data = await res.json();
  return {
    id: data.id,
    html_url: data.html_url,
    created_at: data.created_at,
  };
}

export async function ghPRMerge(input = {}, options = {}) {
  validateRepoFullName(input.repo_full_name);
  const { owner, repo } = parseOwnerRepo(input.repo_full_name);
  if (!input.pr_number) throw new Error('pr_number is required');
  const token = requireCredential('GITHUB_TOKEN', options);
  const agentId = options.agent_id ?? null;

  const body = {
    merge_method: input.merge_method || 'squash',
  };
  if (input.commit_title) body.commit_title = String(input.commit_title);
  if (input.commit_message) body.commit_message = String(input.commit_message);

  const res = await wrapFetch('gh_pr_merge', agentId)(
    `${GH_API}/repos/${owner}/${repo}/pulls/${input.pr_number}/merge`,
    {
      method: 'PUT',
      headers: { ...githubHeaders(token), 'Content-Type': 'application/json' },
      body: JSON.stringify(body),
    }
  );

  if (!res.ok) {
    const message = await extractGitHubError(res);
    throw new Error(message);
  }

  const data = await res.json();
  return {
    merged: true,
    sha: data.sha,
    message: data.message,
  };
}

export async function ghActionsDispatch(input = {}, options = {}) {
  validateRepoFullName(input.repo_full_name);
  const { owner, repo } = parseOwnerRepo(input.repo_full_name);
  if (!input.workflow_id) throw new Error('workflow_id is required');
  if (!input.ref) throw new Error('ref is required');
  const token = requireCredential('GITHUB_TOKEN', options);
  const agentId = options.agent_id ?? null;

  const body = { ref: String(input.ref) };
  if (input.inputs && typeof input.inputs === 'object') body.inputs = input.inputs;

  const res = await wrapFetch('gh_actions_dispatch', agentId)(
    `${GH_API}/repos/${owner}/${repo}/actions/workflows/${encodeURIComponent(String(input.workflow_id))}/dispatches`,
    {
      method: 'POST',
      headers: { ...githubHeaders(token), 'Content-Type': 'application/json' },
      body: JSON.stringify(body),
    }
  );

  // GitHub returns 204 No Content on success.
  if (!res.ok) {
    const message = await extractGitHubError(res);
    throw new Error(message);
  }

  return { ok: true, workflow_id: input.workflow_id, ref: input.ref };
}

export async function ghReleaseCreate(input = {}, options = {}) {
  validateRepoFullName(input.repo_full_name);
  const { owner, repo } = parseOwnerRepo(input.repo_full_name);
  if (!input.tag_name) throw new Error('tag_name is required');
  const token = requireCredential('GITHUB_TOKEN', options);
  const agentId = options.agent_id ?? null;

  const body = { tag_name: String(input.tag_name) };
  if (input.target_commitish) body.target_commitish = String(input.target_commitish);
  if (input.name) body.name = String(input.name);
  if (input.body) body.body = String(input.body);
  if (input.draft !== undefined) body.draft = Boolean(input.draft);
  if (input.prerelease !== undefined) body.prerelease = Boolean(input.prerelease);

  const res = await wrapFetch('gh_release_create', agentId)(`${GH_API}/repos/${owner}/${repo}/releases`, {
    method: 'POST',
    headers: { ...githubHeaders(token), 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  });

  if (!res.ok) {
    const message = await extractGitHubError(res);
    throw new Error(message);
  }

  const data = await res.json();
  return {
    id: data.id,
    tag_name: data.tag_name,
    html_url: data.html_url,
    draft: data.draft,
    prerelease: data.prerelease,
  };
}
