import { afterEach, describe, expect, test } from "bun:test";
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { resolveToken, type GitHubYamlAuthConfig } from "./gh/auth-and-safety";
import { authRequired } from "./gh/client";
import { resolvePositionalIssueReference, resolvePositionalPrReference } from "./gh/refs";
import type { GitHubOptions, GitHubResolvedNumberReference } from "./gh/types";

const originalGhToken = process.env.GH_TOKEN;
const originalGithubToken = process.env.GITHUB_TOKEN;
const projectedEnv = "UNIDESK_TEST_GH_REPOSITORY_OVERRIDE";
const originalProjectedToken = process.env[projectedEnv];
const temporaryRoots: string[] = [];

afterEach(() => {
  if (originalGhToken === undefined) delete process.env.GH_TOKEN;
  else process.env.GH_TOKEN = originalGhToken;
  if (originalGithubToken === undefined) delete process.env.GITHUB_TOKEN;
  else process.env.GITHUB_TOKEN = originalGithubToken;
  if (originalProjectedToken === undefined) delete process.env[projectedEnv];
  else process.env[projectedEnv] = originalProjectedToken;
  while (temporaryRoots.length > 0) rmSync(temporaryRoots.pop()!, { recursive: true, force: true });
});

function testAuthConfig(): GitHubYamlAuthConfig {
  const root = mkdtempSync("/tmp/unidesk-gh-token-override-");
  temporaryRoots.push(root);
  writeFileSync(join(root, "global-token.txt"), "global-token\n");
  writeFileSync(join(root, "selfmedia-token.txt"), "selfmedia-token\n");
  return {
    tokenSource: {
      enabled: true,
      priority: "before-env",
      root,
      sourceRef: "global-token.txt",
      sourceKey: "GH_TOKEN",
      format: "raw-token",
      scope: "global",
      configRef: "test#github.auth.tokenSource",
    },
    repositoryOverrides: [{
      repository: "pikainc/selfmedia",
      priority: "before-env",
      root,
      sourceRef: "selfmedia-token.txt",
      sourceKey: "GH_TOKEN",
      format: "raw-token",
      scope: "repository-override",
      configRef: "test#github.auth.repositoryOverrides[0]",
    }],
  };
}

function options(repo: string): GitHubOptions {
  return { repo } as GitHubOptions;
}

function resolvedRepo(value: GitHubResolvedNumberReference | { ok: boolean }): string {
  if (!("number" in value)) throw new Error("expected a resolved positional reference");
  return value.repo;
}

describe("GitHub repository token overrides", () => {
  test("selects the exact repository override without affecting global repositories", () => {
    delete process.env.GH_TOKEN;
    delete process.env.GITHUB_TOKEN;
    const authConfig = testAuthConfig();

    const selfmedia = resolveToken("pikainc/selfmedia", false, authConfig);
    const unidesk = resolveToken("pikasTech/unidesk", false, authConfig);
    const other = resolveToken("owner/other", false, authConfig);

    expect(selfmedia.token).toBe("selfmedia-token");
    expect(selfmedia.probe).toMatchObject({ scope: "repository-override", sourceRef: "selfmedia-token.txt", sourceKey: "GH_TOKEN", valuesPrinted: false });
    expect(unidesk.token).toBe("global-token");
    expect(other.token).toBe("global-token");
    expect(unidesk.probe).toMatchObject({ scope: "global", sourceRef: "global-token.txt", valuesPrinted: false });
    expect(other.probe).toMatchObject({ scope: "global", sourceRef: "global-token.txt", valuesPrinted: false });
    expect(selfmedia.probe.tokenFingerprint).not.toBe(unidesk.probe.tokenFingerprint);
  });

  test("keeps before-env repository overrides ahead of global environment tokens", () => {
    process.env.GH_TOKEN = "environment-token";
    process.env.GITHUB_TOKEN = "secondary-environment-token";

    const selected = resolveToken("pikainc/selfmedia", false, testAuthConfig());

    expect(selected.token).toBe("selfmedia-token");
    expect(selected.probe).toMatchObject({ source: "yaml-token-source", scope: "repository-override", yamlSourcePriority: "before-env" });
  });

  test("uses the YAML-declared projected environment for repository overrides", () => {
    process.env.GH_TOKEN = "global-environment-token";
    process.env[projectedEnv] = "projected-repository-token";
    const authConfig = testAuthConfig();
    authConfig.repositoryOverrides[0] = {
      ...authConfig.repositoryOverrides[0],
      sourceRef: "missing-token.txt",
      projectedEnv,
    };

    const selected = resolveToken("pikainc/selfmedia", false, authConfig);

    expect(selected.token).toBe("projected-repository-token");
    expect(selected.probe).toMatchObject({
      source: "yaml-token-projection",
      scope: "repository-override",
      sourceExists: false,
      sourceKeyPresent: false,
      projectedEnv,
      projectedEnvPresent: true,
      valuesPrinted: false,
    });
  });

  test("fails closed when a declared repository override is missing", () => {
    process.env.GH_TOKEN = "global-environment-token";
    delete process.env[projectedEnv];
    const authConfig = testAuthConfig();
    authConfig.repositoryOverrides[0] = {
      ...authConfig.repositoryOverrides[0],
      sourceRef: "missing-token.txt",
      projectedEnv,
    };

    const selected = resolveToken("pikainc/selfmedia", true, authConfig);
    const failure = authRequired("pikainc/selfmedia", "pr view", selected.probe);

    expect(selected.token).toBeNull();
    expect(selected.probe).toMatchObject({
      present: false,
      scope: "repository-override",
      sourceExists: false,
      sourceKeyPresent: false,
      projectedEnv,
      projectedEnvPresent: false,
      ghFallbackAttempted: false,
      valuesPrinted: false,
    });
    expect(failure).toMatchObject({
      ok: false,
      degradedReason: "missing-token",
      runnerDisposition: "infra-blocked",
      token: {
        scope: "repository-override",
        projectedEnvPresent: false,
        valuesPrinted: false,
      },
    });
    expect(JSON.stringify(failure)).not.toContain("global-environment-token");
  });

  test("matches repository overrides without case sensitivity", () => {
    delete process.env.GH_TOKEN;
    delete process.env.GITHUB_TOKEN;
    const authConfig = testAuthConfig();

    for (const repo of ["PikaInc/selfmedia", "pikainc/SELFMEDIA", "PIKAINC/SELFMEDIA"]) {
      const selected = resolveToken(repo, false, authConfig);
      expect(selected.token).toBe("selfmedia-token");
      expect(selected.probe).toMatchObject({ scope: "repository-override", sourceRef: "selfmedia-token.txt", valuesPrinted: false });
    }

    const global = resolveToken("PikasTech/UniDesk", false, authConfig);
    expect(global.token).toBe("global-token");
    expect(global.probe).toMatchObject({ scope: "global", sourceRef: "global-token.txt", valuesPrinted: false });
  });

  test("uses the final positional repository for issue and PR token selection", () => {
    delete process.env.GH_TOKEN;
    delete process.env.GITHUB_TOKEN;
    const authConfig = testAuthConfig();
    const issueRef = resolvePositionalIssueReference(["issue", "edit", "pikainc/selfmedia#12"], 2, "issue edit", options("pikasTech/unidesk"));
    const prRef = resolvePositionalPrReference(["pr", "comment", "pikainc/selfmedia#34"], 2, "pr comment", options("pikasTech/unidesk"));

    const issueToken = resolveToken(resolvedRepo(issueRef), false, authConfig);
    const prToken = resolveToken(resolvedRepo(prRef), false, authConfig);
    const defaultToken = resolveToken("pikasTech/unidesk", false, authConfig);

    expect(issueToken.token).toBe("selfmedia-token");
    expect(prToken.token).toBe("selfmedia-token");
    expect(defaultToken.token).toBe("global-token");
  });
});
