import { commandError, errorPayload, githubRequest, githubTextRequest, isGitHubError, validationError } from "./client";

interface WorkflowRun {
  id: number;
  name: string;
  display_title: string;
  event: string;
  status: string;
  conclusion: string | null;
  head_branch: string;
  head_sha: string;
  run_number: number;
  created_at: string;
  updated_at: string;
  html_url: string;
}

interface WorkflowJob {
  id: number;
  name: string;
  status: string;
  conclusion: string | null;
  started_at: string | null;
  completed_at: string | null;
  html_url: string;
  steps?: Array<{ name: string; status: string; conclusion: string | null; number: number }>;
}

interface WorkflowRunsResponse { total_count: number; workflow_runs: WorkflowRun[] }
interface WorkflowJobsResponse { total_count: number; jobs: WorkflowJob[] }

function splitRepo(repo: string): [string, string] | null {
  const [owner, name, extra] = repo.split("/");
  return owner && name && extra === undefined ? [owner, name] : null;
}

function compactRun(run: WorkflowRun): Record<string, unknown> {
  return {
    id: run.id,
    workflow: run.name,
    title: run.display_title,
    event: run.event,
    status: run.status,
    conclusion: run.conclusion,
    branch: run.head_branch,
    commit: run.head_sha,
    number: run.run_number,
    createdAt: run.created_at,
    updatedAt: run.updated_at,
    url: run.html_url,
  };
}

function compactJob(job: WorkflowJob): Record<string, unknown> {
  return {
    id: job.id,
    name: job.name,
    status: job.status,
    conclusion: job.conclusion,
    startedAt: job.started_at,
    completedAt: job.completed_at,
    url: job.html_url,
    steps: (job.steps ?? []).map((step) => ({
      number: step.number,
      name: step.name,
      status: step.status,
      conclusion: step.conclusion,
    })),
  };
}

function redactActionLog(text: string): string {
  return text
    .replace(/(https?:\/\/)[^/@\s]+@/giu, "$1<redacted>@")
    .replace(/(Bearer\s+)[A-Za-z0-9._~+/=-]+/giu, "$1<redacted>")
    .replace(/((?:PASSWORD|SECRET|TOKEN|API[_-]?KEY)\s*[=:]\s*)\S+/giu, "$1<redacted>");
}

export function redactAndTailActionLog(text: string, lineLimit = 60, charLimit = 5000): string {
  const redacted = redactActionLog(text);
  return redacted.split(/\r?\n/u).slice(-lineLimit).join("\n").slice(-charLimit);
}

export function actionLogFailureExcerpt(text: string, charLimit = 3000): string {
  const lines = redactActionLog(text).split(/\r?\n/u);
  const selected = new Set<number>();
  let found = 0;
  for (let index = 0; index < lines.length && selected.size < 36; index += 1) {
    if (!/\bFAILED\b|fatal error|##\[error\]|Expected equality|Assertion failed/u.test(lines[index] ?? "")) continue;
    const before = found === 0 ? 20 : 2;
    const after = found === 0 ? 8 : 2;
    for (let context = Math.max(0, index - before); context <= Math.min(lines.length - 1, index + after); context += 1) selected.add(context);
    found += 1;
  }
  return [...selected].sort((left, right) => left - right).map((index) => lines[index]).join("\n").slice(0, charLimit);
}

export function isActionLogNotReady(error: unknown): boolean {
  if (typeof error !== "object" || error === null) return false;
  const value = error as { status?: unknown; message?: unknown };
  return value.status === 404 && typeof value.message === "string" && value.message.toLowerCase().includes("blob does not exist");
}

export async function actionRunList(repo: string, token: string, limit: number): Promise<Record<string, unknown>> {
  const parts = splitRepo(repo);
  if (parts === null) return validationError("run list", repo, "--repo must use owner/name format");
  const [owner, name] = parts;
  const result = await githubRequest<WorkflowRunsResponse>(token, "GET", `/repos/${owner}/${name}/actions/runs?per_page=${Math.min(limit, 100)}`);
  if (isGitHubError(result)) return commandError("run list", repo, result, { mutation: false });
  const runs = result.workflow_runs.map(compactRun);
  return {
    ok: true,
    command: "run list",
    repo,
    mutation: false,
    runs,
    count: runs.length,
    total: result.total_count,
    next: runs.length > 0 ? { view: `bun scripts/cli.ts gh run view ${String(result.workflow_runs[0]?.id)} --repo ${repo}` } : null,
  };
}

async function loadRunAndJobs(repo: string, token: string, runId: number): Promise<{ run: WorkflowRun; jobs: WorkflowJob[] } | Record<string, unknown>> {
  const parts = splitRepo(repo);
  if (parts === null) return validationError("run view", repo, "--repo must use owner/name format");
  const [owner, name] = parts;
  const run = await githubRequest<WorkflowRun>(token, "GET", `/repos/${owner}/${name}/actions/runs/${runId}`);
  if (isGitHubError(run)) return commandError("run view", repo, run, { runId, mutation: false });
  const jobs = await githubRequest<WorkflowJobsResponse>(token, "GET", `/repos/${owner}/${name}/actions/runs/${runId}/jobs?per_page=100`);
  if (isGitHubError(jobs)) return commandError("run view", repo, jobs, { runId, mutation: false });
  return { run, jobs: jobs.jobs };
}

export async function actionRunView(repo: string, token: string, runId: number): Promise<Record<string, unknown>> {
  const loaded = await loadRunAndJobs(repo, token, runId);
  if (!("run" in loaded) || !("jobs" in loaded)) return loaded;
  const failedJobs = loaded.jobs.filter((job) => job.conclusion === "failure");
  const startupFailure = loaded.run.conclusion === "failure" && loaded.jobs.length === 0;
  return {
    ok: true,
    command: "run view",
    repo,
    mutation: false,
    run: compactRun(loaded.run),
    jobs: loaded.jobs.map(compactJob),
    failedJobIds: failedJobs.map((job) => job.id),
    startupFailure,
    diagnosis: startupFailure
      ? "workflow failed before any job was created; inspect workflow syntax, triggers, permissions, and repository Actions settings"
      : null,
    next: failedJobs.map((job) => `bun scripts/cli.ts gh run logs ${runId} --job ${job.id} --repo ${repo}`),
  };
}

export async function actionRunLogs(repo: string, token: string, runId: number, jobId: number): Promise<Record<string, unknown>> {
  const loaded = await loadRunAndJobs(repo, token, runId);
  if (!("run" in loaded) || !("jobs" in loaded)) return loaded;
  const job = loaded.jobs.find((candidate) => candidate.id === jobId);
  if (job === undefined) return validationError("run logs", repo, `job ${jobId} does not belong to run ${runId}`, { runId, jobId });
  const parts = splitRepo(repo);
  if (parts === null) return validationError("run logs", repo, "--repo must use owner/name format");
  const [owner, name] = parts;
  const log = await githubTextRequest(token, `/repos/${owner}/${name}/actions/jobs/${jobId}/logs`);
  if (isGitHubError(log)) {
    if (isActionLogNotReady(log)) {
      return commandError("run logs", repo, errorPayload("action-log-not-ready", "job log is not available yet", {
        status: 404,
        retryable: true,
        details: { runId, jobId, jobStatus: job.status },
      }), {
        runId,
        jobId,
        mutation: false,
        retryable: true,
        next: `bun scripts/cli.ts gh run view ${runId} --repo ${repo}`,
      });
    }
    return commandError("run logs", repo, log, { runId, jobId, mutation: false });
  }
  const logTail = redactAndTailActionLog(log);
  const failureExcerpt = actionLogFailureExcerpt(log);
  return {
    ok: true,
    command: "run logs",
    repo,
    mutation: false,
    runId,
    job: compactJob(job),
    failureExcerpt,
    logTail,
    lineLimit: 60,
    charLimit: 5000,
    failureExcerptCharLimit: 3000,
    truncated: logTail.length < log.length,
    valuesPrinted: false,
  };
}
