import assert from "node:assert/strict";
import { execFile } from "node:child_process";
import path from "node:path";
import { promisify } from "node:util";
import { fileURLToPath } from "node:url";

const execFileAsync = promisify(execFile);
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const issue = "pikasTech/HWLAB#51";
const supports = ["pikasTech/HWLAB#7", "pikasTech/HWLAB#42", "pikasTech/HWLAB#39"];
const defaultTimeoutMs = 5000;
const githubApiBase = "https://api.github.com";

function parseArgs(argv) {
  const args = {
    timeoutMs: defaultTimeoutMs,
    help: false
  };

  for (let index = 0; index < argv.length; index += 1) {
    const arg = argv[index];
    if (arg === "--timeout-ms") {
      args.timeoutMs = Number.parseInt(argv[++index], 10);
    } else if (arg === "--help") {
      args.help = true;
    } else {
      throw new Error(`unknown argument ${arg}`);
    }
  }

  assert.ok(Number.isInteger(args.timeoutMs) && args.timeoutMs > 0, "--timeout-ms must be positive");
  return args;
}

function usage() {
  return "Usage: node scripts/runner-issue-visibility-preflight.mjs [--timeout-ms 5000]";
}

function commandLine(command, args) {
  return [command, ...args].join(" ");
}

async function run(command, args = [], options = {}) {
  const commandText = commandLine(command, args);
  try {
    const result = await execFileAsync(command, args, {
      cwd: repoRoot,
      timeout: options.timeoutMs ?? defaultTimeoutMs,
      maxBuffer: 1024 * 1024
    });
    return {
      ok: true,
      command: commandText,
      exitCode: 0,
      stdout: result.stdout.trim(),
      stderr: result.stderr.trim()
    };
  } catch (error) {
    return {
      ok: false,
      command: commandText,
      exitCode: typeof error.code === "number" ? error.code : 1,
      stdout: String(error.stdout ?? "").trim(),
      stderr: String(error.stderr ?? "").trim(),
      error: error instanceof Error ? error.message : String(error)
    };
  }
}

async function readRemoteUrl() {
  return run("git", ["remote", "get-url", "origin"]);
}

async function readHeadCommit() {
  return run("git", ["rev-parse", "--short=12", "HEAD"]);
}

async function probeGitTransport(timeoutMs) {
  const result = await run("git", ["ls-remote", "origin", "main"], { timeoutMs });
  const line = result.stdout.split("\n").find(Boolean) ?? "";
  const [commitId, ref] = line.split("\t");
  return {
    ...result,
    ref: ref ?? null,
    commitId: commitId ?? null,
    reachable: result.ok && Boolean(commitId) && Boolean(ref)
  };
}

function parseGithubSlug(remoteUrl) {
  const patterns = [
    /^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/iu,
    /^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/iu,
    /^ssh:\/\/git@github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/iu
  ];

  for (const pattern of patterns) {
    const match = remoteUrl.match(pattern);
    if (match) {
      return `${match[1]}/${match[2]}`;
    }
  }

  return null;
}

async function probeGithubEndpoint(url, timeoutMs) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);
  try {
    const response = await fetch(url, {
      method: "GET",
      headers: {
        accept: "application/vnd.github+json",
        "user-agent": "hwlab-runner-gh-visibility-preflight"
      },
      signal: controller.signal
    });
    const body = await response.text();
    return {
      url,
      reachable: response.ok,
      ok: response.ok,
      status: response.status,
      statusText: response.statusText,
      bodyPreview: body.trim().slice(0, 240)
    };
  } catch (error) {
    return {
      url,
      reachable: false,
      ok: false,
      error: error instanceof Error ? error.message : String(error)
    };
  } finally {
    clearTimeout(timer);
  }
}

async function checkGh() {
  return run("gh", ["--version"]);
}

function tokenPresence(name) {
  return Boolean(process.env[name] && String(process.env[name]).trim().length > 0);
}

function blocker(type, scope, summary, nextTask) {
  const entry = {
    type,
    scope,
    status: "open",
    summary
  };
  if (nextTask) {
    entry.nextTask = nextTask;
  }
  return entry;
}

function promptContract() {
  return {
    issueIsAuxiliaryOnly: true,
    selfContainedPromptRequired: true,
    requiredSections: [
      "repository",
      "branch",
      "task",
      "background",
      "constraints",
      "acceptance",
      "validation",
      "final-response"
    ],
    mustInclude: [
      "full task objective",
      "relevant background facts",
      "hard constraints",
      "acceptance criteria",
      "exact validation commands",
      "issue context pasted into the prompt when needed"
    ],
    mustNotAssume: [
      "issue comments are readable in the runner",
      "GitHub REST issue visibility is available",
      "GitHub REST pull-request visibility is available",
      "gh is installed",
      "GH_TOKEN or GITHUB_TOKEN is present"
    ]
  };
}

export async function runRunnerIssueVisibilityPreflight(argv) {
  const args = parseArgs(argv);
  if (args.help) {
    return {
      reportKind: "runner-gh-visibility-preflight",
      usage: usage()
    };
  }

  const [remoteUrl, headCommit, gitTransport, ghResult] = await Promise.all([
    readRemoteUrl(),
    readHeadCommit(),
    probeGitTransport(args.timeoutMs),
    checkGh()
  ]);

  const repoSlug = remoteUrl.ok ? parseGithubSlug(remoteUrl.stdout) : null;
  const tokenFlags = {
    GH_TOKEN: tokenPresence("GH_TOKEN"),
    GITHUB_TOKEN: tokenPresence("GITHUB_TOKEN")
  };

  const github = {
    repo: null,
    issues: null,
    pulls: null
  };

  if (repoSlug) {
    github.repo = await probeGithubEndpoint(`${githubApiBase}/repos/${repoSlug}`, args.timeoutMs);
    github.issues = await probeGithubEndpoint(`${githubApiBase}/repos/${repoSlug}/issues?per_page=1`, args.timeoutMs);
    github.pulls = await probeGithubEndpoint(`${githubApiBase}/repos/${repoSlug}/pulls?per_page=1`, args.timeoutMs);
  }

  const blockers = [];
  if (!remoteUrl.ok) {
    blockers.push(
      blocker(
        "environment_blocker",
        "git-origin",
        `Unable to read the origin remote URL: ${remoteUrl.stderr || remoteUrl.error || "unknown error"}.`,
        "Fix the local git remote before queueing a runner task."
      )
    );
  }
  if (!gitTransport.reachable) {
    blockers.push(
      blocker(
        "environment_blocker",
        "git-transport",
        `git ls-remote origin main is not reachable from this runner: ${gitTransport.stderr || gitTransport.error || "unknown error"}.`,
        "Restore git transport reachability before relying on runner-side GitHub visibility."
      )
    );
  }
  if (!repoSlug) {
    blockers.push(
      blocker(
        "observability_blocker",
        "github-remote-parse",
        `Could not parse a GitHub repo slug from origin URL ${JSON.stringify(remoteUrl.stdout || "")}.`,
        "Use a GitHub origin remote URL that can be mapped to owner/repo."
      )
    );
  } else {
    for (const [scope, result] of [
      ["github-repo", github.repo],
      ["github-issues", github.issues],
      ["github-pulls", github.pulls]
    ]) {
      if (result && !result.reachable) {
        blockers.push(
          blocker(
            "observability_blocker",
            scope,
            `${scope} is not reachable from this runner${typeof result.status === "number" ? ` (HTTP ${result.status})` : `: ${result.error || "unknown error"}`}.`,
            "Do not assume issue or PR text is available; keep the prompt self-contained."
          )
        );
      }
    }
  }

  const conclusion = blockers.length === 0 ? "ready" : "blocked";
  const upstreamRecommendations = [];
  if (ghResult.ok === false) {
    upstreamRecommendations.push("Add gh to the runner image or another supported GitHub visibility helper.");
  }
  if (!tokenFlags.GH_TOKEN && !tokenFlags.GITHUB_TOKEN) {
    upstreamRecommendations.push("Inject a read-only GH_TOKEN or GITHUB_TOKEN when GitHub issue or PR reads are required.");
  }
  if (blockers.some((item) => item.scope.startsWith("github-") || item.scope === "git-transport")) {
    upstreamRecommendations.push("Add a queue preflight that checks git transport, repo, issue, and PR visibility before dispatch.");
    upstreamRecommendations.push("Fail fast when GitHub visibility is missing and require the prompt to include the full task context.");
  }

  return {
    reportKind: "runner-gh-visibility-preflight",
    reportVersion: "v1",
    issue,
    supports,
    repoRoot,
    repoSlug,
    generatedFromCommit: headCommit.ok ? headCommit.stdout : null,
    conclusion,
    promptContract: promptContract(),
    checks: {
      remoteUrl,
      headCommit,
      gitTransport,
      gh: {
        available: ghResult.ok,
        command: ghResult.command,
        exitCode: ghResult.exitCode,
        stderr: ghResult.stderr,
        error: ghResult.error || null
      },
      tokenPresence: tokenFlags,
      github
    },
    blockers,
    upstreamRecommendations,
    validationCommands: [
      "node --check scripts/runner-issue-visibility-preflight.mjs",
      "node --check scripts/src/runner-issue-visibility-preflight.mjs",
      "node scripts/runner-issue-visibility-preflight.mjs"
    ]
  };
}

