import { spawnSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import { afterEach } from "vitest";
import { describe, expect, it } from "vitest";
import {
  androidBuildMetadataGradleArgs,
  resolveAndroidBuildMetadata,
  verifyAndroidReleaseSource,
} from "../../apps/android/scripts/build-release-artifacts.ts";
import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";

const SCRIPT = "apps/android/scripts/build-release-artifacts.ts";
const APK_CERTIFICATE_SHA256 = "80dbc62315ea216dd6e8a7060735a866ddc464a48ed50fef29ff0550468b9a63";
const tempRoots = useAutoCleanupTempDirTracker(afterEach);

function run(args: string[], env: NodeJS.ProcessEnv = {}) {
  const processEnv = { ...process.env };
  delete processEnv.GIT_COMMIT;
  delete processEnv.GIT_SHA;
  delete processEnv.GITHUB_SHA;
  delete processEnv.OPENCLAW_BUILD_TIMESTAMP;
  return spawnSync(process.execPath, ["--import", "tsx", SCRIPT, ...args], {
    cwd: process.cwd(),
    encoding: "utf8",
    env: { ...processEnv, ...env },
  });
}

function fakeApkSigner(certificateSha256: string, signerCount = 1) {
  const tempRoot = tempRoots.make("openclaw-apksigner-");
  const buildToolsDir = path.join(tempRoot, "build-tools", "36.0.0");
  fs.mkdirSync(buildToolsDir, { recursive: true });
  const apkSignerPath = path.join(buildToolsDir, "apksigner");
  const signerLines = Array.from(
    { length: signerCount },
    (_, index) => `Signer #${index + 1} certificate SHA-256 digest: ${certificateSha256}`,
  );
  fs.writeFileSync(
    apkSignerPath,
    `#!/bin/sh\nprintf '%s\\n' ${signerLines.map((line) => `'${line}'`).join(" ")}\n`,
  );
  fs.chmodSync(apkSignerPath, 0o755);
  const apkPath = path.join(tempRoot, "OpenClaw-Android.apk");
  fs.writeFileSync(apkPath, "fake apk bytes");
  return { apkPath, sdkRoot: tempRoot };
}

describe("Android release artifacts", () => {
  it("resolves release metadata from explicit environment values first", () => {
    const metadata = resolveAndroidBuildMetadata({
      env: {
        GIT_COMMIT: "A".repeat(40),
        GIT_SHA: "d".repeat(40),
        GITHUB_SHA: "b".repeat(40),
        OPENCLAW_BUILD_TIMESTAMP: "2026-07-10T01:02:03Z",
      },
      now: () => new Date("2026-07-11T00:00:00Z"),
      readGitCommit: () => "c".repeat(40),
    });

    expect(metadata).toEqual({
      commit: "a".repeat(40),
      timestamp: "2026-07-10T01:02:03.000Z",
    });
  });

  it("prefers explicit metadata, then the checkout, then a git-less GitHub fallback", () => {
    const gitShaMetadata = resolveAndroidBuildMetadata({
      env: { GIT_SHA: "a".repeat(40), GITHUB_SHA: "b".repeat(40) },
      now: () => new Date("2026-07-10T04:05:06Z"),
      readGitCommit: () => "c".repeat(40),
    });
    const repositoryMetadata = resolveAndroidBuildMetadata({
      env: { GITHUB_SHA: "b".repeat(40) },
      now: () => new Date("2026-07-10T04:05:06Z"),
      readGitCommit: () => "c".repeat(40),
    });
    const githubMetadata = resolveAndroidBuildMetadata({
      env: { GITHUB_SHA: "b".repeat(40) },
      now: () => new Date("2026-07-10T04:05:06Z"),
      readGitCommit: () => {
        throw new Error("git unavailable");
      },
    });

    expect(gitShaMetadata.commit).toBe("a".repeat(40));
    expect(githubMetadata.commit).toBe("b".repeat(40));
    expect(repositoryMetadata).toEqual({
      commit: "c".repeat(40),
      timestamp: "2026-07-10T04:05:06.000Z",
    });
  });

  it("rejects missing commit metadata when neither Git nor GitHub is available", () => {
    expect(() =>
      resolveAndroidBuildMetadata({
        env: {},
        readGitCommit: () => {
          throw new Error("git unavailable");
        },
      }),
    ).toThrow("Unable to resolve the Android release Git commit");
  });

  it("rejects partial commits and non-UTC build timestamps", () => {
    expect(() =>
      resolveAndroidBuildMetadata({
        env: { GIT_COMMIT: "abc1234", GITHUB_SHA: "b".repeat(40) },
      }),
    ).toThrow("full 40-character hexadecimal Git commit");
    expect(() =>
      resolveAndroidBuildMetadata({
        env: {
          GIT_COMMIT: "a".repeat(40),
          OPENCLAW_BUILD_TIMESTAMP: "2026-07-10T01:02:03+01:00",
        },
      }),
    ).toThrow("ISO-8601 UTC timestamp");
  });

  it("passes build metadata as named Gradle project properties", () => {
    expect(
      androidBuildMetadataGradleArgs({
        commit: "a".repeat(40),
        timestamp: "2026-07-10T01:02:03.000Z",
      }),
    ).toEqual([
      `-PopenclawBuildCommit=${"a".repeat(40)}`,
      "-PopenclawBuildTimestamp=2026-07-10T01:02:03.000Z",
    ]);
  });

  it("requires release metadata to match a clean checkout", () => {
    const commit = "a".repeat(40);
    const cleanGit = (args: string[]) => (args[0] === "rev-parse" ? `${commit}\n` : "");

    expect(() => verifyAndroidReleaseSource(commit, { runGit: cleanGit })).not.toThrow();
    expect(() => verifyAndroidReleaseSource("b".repeat(40), { runGit: cleanGit })).toThrow(
      "Android release commit mismatch",
    );
    expect(() =>
      verifyAndroidReleaseSource(commit, {
        runGit: (args) => (args[0] === "rev-parse" ? `${commit}\n` : " M app/src/main.kt\n"),
      }),
    ).toThrow("Android release builds require a clean Git checkout");
  });

  it("selects only the signed third-party APK for GitHub distribution", () => {
    const result = run(["--artifact", "third-party", "--dry-run"]);

    expect(result.status).toBe(0);
    expect(result.stdout).toContain("Release artifact: third-party apk");
    expect(result.stdout).toContain("Gradle task: :app:assembleThirdPartyRelease");
    expect(result.stdout).not.toContain("Release artifact: play aab");
  });

  it("rejects unknown artifact selectors", () => {
    const result = run(["--artifact", "debug", "--dry-run"]);

    expect(result.status).toBe(1);
    expect(result.stderr).toContain("--artifact must be one of: all, play, wear, third-party");
  });

  it("accepts the pinned standalone APK signing certificate", () => {
    const { apkPath, sdkRoot } = fakeApkSigner(APK_CERTIFICATE_SHA256);

    const result = run(["--verify-apk", apkPath], {
      ANDROID_HOME: sdkRoot,
      ANDROID_SDK_ROOT: sdkRoot,
    });

    expect(result.status).toBe(0);
    expect(result.stdout).toContain("Verified pinned APK signing certificate");
  });

  it("rejects an APK signed by another certificate", () => {
    const { apkPath, sdkRoot } = fakeApkSigner("a".repeat(64));

    const result = run(["--verify-apk", apkPath], {
      ANDROID_HOME: sdkRoot,
      ANDROID_SDK_ROOT: sdkRoot,
    });

    expect(result.status).toBe(1);
    expect(result.stderr).toContain("APK signing certificate mismatch");
  });

  it("rejects APKs with multiple signers", () => {
    const { apkPath, sdkRoot } = fakeApkSigner(APK_CERTIFICATE_SHA256, 2);

    const result = run(["--verify-apk", apkPath], {
      ANDROID_HOME: sdkRoot,
      ANDROID_SDK_ROOT: sdkRoot,
    });

    expect(result.status).toBe(1);
    expect(result.stderr).toContain("Expected exactly one SHA-256 signing certificate");
  });
});
