import fs from "fs";
import os from "os";
import path from "path";
import { afterEach, describe, expect, it } from "vitest";
import { normalizeKnownFailuresDocument } from "./index.mjs";
import {
  parseGitHubRepoSlug,
  shouldFailKnownFailureIssueValidation,
  validateKnownFailureIssues,
} from "./github.mjs";

const tempDirs = [];

afterEach(() => {
  for (const tempDir of tempDirs.splice(0)) {
    fs.rmSync(tempDir, { recursive: true, force: true });
  }
});

describe("known failures GitHub validation", () => {
  it("parses GitHub repo slugs from common origin URLs", () => {
    expect(parseGitHubRepoSlug("https://github.com/acme/repo.git")).toBe("acme/repo");
    expect(parseGitHubRepoSlug("git@github.com:acme/repo.git")).toBe("acme/repo");
    expect(parseGitHubRepoSlug("ssh://git@github.com/acme/repo.git")).toBe("acme/repo");
    expect(parseGitHubRepoSlug("https://gitlab.com/acme/repo.git")).toBe(null);
  });

  it("fails validation for title/state drift and closed issues that still reproduce", async () => {
    const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-gh-issues-"));
    tempDirs.push(tempDir);
    const document = normalizeKnownFailuresDocument({
      schemaVersion: 1,
      issueRepo: "acme/repo",
      entries: [
        {
          id: "bad-message",
          title: "Bad message bug",
          classification: "product_bug",
          state: "open",
          issue: {
            repo: "acme/repo",
            number: 12,
            url: "https://github.com/acme/repo/issues/12",
          },
          description: "Wrong message",
          whyFailing: "Payload is wrong",
          lastReviewedAt: "2026-04-27",
          matches: [
            {
              service: "api",
              type: "int",
              path: "src/api/routes/__testkit__/failing.int.testkit.ts",
            },
          ],
        },
      ],
    });

    const result = await validateKnownFailureIssues({
      productDir: tempDir,
      document,
      statusArtifact: {
        tests: [
          {
            service: "api",
            type: "int",
            path: "src/api/routes/__testkit__/failing.int.testkit.ts",
            status: "failed",
          },
        ],
      },
      config: {
        provider: "github",
        mode: "error",
        cacheTtlSeconds: 60,
      },
      gitMetadata: {
        repoSlug: "acme/repo",
        remoteUrl: "https://github.com/acme/repo.git",
      },
      transport: {
        async fetchRepoIssues(repo, numbers) {
          const map = new Map();
          map.set(numbers[0], {
            repo,
            number: numbers[0],
            exists: true,
            title: "Different title",
            state: "CLOSED",
            url: `https://github.com/${repo}/issues/${numbers[0]}`,
            checkedAt: "2026-04-27T00:00:00.000Z",
            source: "github",
          });
          return map;
        },
      },
    });

    expect(result.summary.byCode.closed_but_failing).toBe(1);
    expect(result.summary.byCode.title_mismatch).toBe(1);
    expect(result.summary.byCode.state_mismatch).toBe(1);
    expect(result.summary.errors).toBeGreaterThan(0);
    expect(result.entries[0].status).toBe("closed_but_failing");
    expect(shouldFailKnownFailureIssueValidation(result)).toBe(true);
  });

  it("allows multiple local entries to share one issue title while keeping distinct descriptions", async () => {
    const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-gh-shared-title-"));
    tempDirs.push(tempDir);
    const document = normalizeKnownFailuresDocument({
      schemaVersion: 1,
      issueRepo: "acme/repo",
      entries: [
        {
          id: "route-a-invalid-uuid",
          title: "UUID path params reach Postgres and return 500 instead of 400",
          classification: "product_bug",
          state: "open",
          issue: {
            repo: "acme/repo",
            number: 77,
            url: "https://github.com/acme/repo/issues/77",
          },
          description: "Route A leaks Postgres UUID parse errors on malformed ids.",
          whyFailing: "Route A is missing UUID param validation.",
          lastReviewedAt: "2026-04-27",
          matches: [
            {
              service: "api",
              type: "int",
              path: "src/api/routes/__testkit__/route-a.int.testkit.ts",
            },
          ],
        },
        {
          id: "route-b-invalid-uuid",
          title: "UUID path params reach Postgres and return 500 instead of 400",
          classification: "product_bug",
          state: "open",
          issue: {
            repo: "acme/repo",
            number: 77,
            url: "https://github.com/acme/repo/issues/77",
          },
          description: "Route B leaks Postgres UUID parse errors on malformed ids.",
          whyFailing: "Route B is missing UUID param validation.",
          lastReviewedAt: "2026-04-27",
          matches: [
            {
              service: "api",
              type: "int",
              path: "src/api/routes/__testkit__/route-b.int.testkit.ts",
            },
          ],
        },
      ],
    });

    const result = await validateKnownFailureIssues({
      productDir: tempDir,
      document,
      statusArtifact: {
        tests: [
          {
            service: "api",
            type: "int",
            path: "src/api/routes/__testkit__/route-a.int.testkit.ts",
            status: "failed",
          },
          {
            service: "api",
            type: "int",
            path: "src/api/routes/__testkit__/route-b.int.testkit.ts",
            status: "failed",
          },
        ],
      },
      config: {
        provider: "github",
        mode: "error",
      },
      transport: {
        async fetchRepoIssues(repo, numbers) {
          const map = new Map();
          map.set(numbers[0], {
            repo,
            number: numbers[0],
            exists: true,
            title: "UUID path params reach Postgres and return 500 instead of 400",
            state: "OPEN",
            url: `https://github.com/${repo}/issues/${numbers[0]}`,
            checkedAt: "2026-04-27T00:00:00.000Z",
            source: "github",
          });
          return map;
        },
      },
    });

    expect(result.summary.byCode.title_mismatch).toBeUndefined();
    expect(result.summary.errors).toBe(0);
    expect(result.entries).toHaveLength(2);
    expect(result.entries[0].findings).toEqual([]);
    expect(result.entries[1].findings).toEqual([]);
    expect(result.entries[0].status).toBe("open_and_failing");
    expect(result.entries[1].status).toBe("open_and_failing");
  });

  it("warns when an open issue is not reproduced", async () => {
    const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-gh-open-"));
    tempDirs.push(tempDir);
    const document = normalizeKnownFailuresDocument({
      schemaVersion: 1,
      issueRepo: "acme/repo",
      entries: [
        {
          id: "bad-message",
          title: "Bad message bug",
          classification: "product_bug",
          state: "open",
          issue: {
            repo: "acme/repo",
            number: 12,
            url: "https://github.com/acme/repo/issues/12",
          },
          description: "Wrong message",
          whyFailing: "Payload is wrong",
          lastReviewedAt: "2026-04-27",
          matches: [
            {
              service: "api",
              type: "int",
              path: "src/api/routes/__testkit__/failing.int.testkit.ts",
            },
          ],
        },
      ],
    });

    const result = await validateKnownFailureIssues({
      productDir: tempDir,
      document,
      statusArtifact: {
        tests: [
          {
            service: "api",
            type: "int",
            path: "src/api/routes/__testkit__/failing.int.testkit.ts",
            status: "passed",
          },
        ],
      },
      config: {
        provider: "github",
        mode: "warn",
      },
      gitMetadata: {
        repoSlug: "acme/repo",
      },
      transport: {
        async fetchRepoIssues(repo, numbers) {
          const map = new Map();
          map.set(numbers[0], {
            repo,
            number: numbers[0],
            exists: true,
            title: "Bad message bug",
            state: "OPEN",
            url: `https://github.com/${repo}/issues/${numbers[0]}`,
            checkedAt: "2026-04-27T00:00:00.000Z",
            source: "github",
          });
          return map;
        },
      },
    });

    expect(result.summary.byCode.open_not_reproduced).toBe(1);
    expect(result.summary.errors).toBe(0);
    expect(result.summary.warnings).toBeGreaterThan(0);
    expect(shouldFailKnownFailureIssueValidation(result)).toBe(false);
    expect(result.entries[0].observed).toMatchObject({
      matchedTests: 1,
      executedTests: 1,
      passedTests: 1,
      failedTests: 0,
      skippedTests: 0,
      notRunTests: 0,
      reproduced: false,
    });
  });

  it("does not treat skipped or not_run files as not reproduced", async () => {
    const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-gh-not-executed-"));
    tempDirs.push(tempDir);
    const document = normalizeKnownFailuresDocument({
      schemaVersion: 1,
      issueRepo: "acme/repo",
      entries: [
        {
          id: "bad-message",
          title: "Bad message bug",
          classification: "product_bug",
          state: "open",
          issue: {
            repo: "acme/repo",
            number: 12,
            url: "https://github.com/acme/repo/issues/12",
          },
          description: "Wrong message",
          whyFailing: "Payload is wrong",
          lastReviewedAt: "2026-04-27",
          matches: [
            {
              service: "api",
              type: "int",
              path: "src/api/routes/__testkit__/failing.int.testkit.ts",
            },
            {
              service: "api",
              type: "int",
              path: "src/api/routes/__testkit__/blocked.int.testkit.ts",
            },
          ],
        },
      ],
    });

    const result = await validateKnownFailureIssues({
      productDir: tempDir,
      document,
      statusArtifact: {
        tests: [
          {
            service: "api",
            type: "int",
            path: "src/api/routes/__testkit__/failing.int.testkit.ts",
            status: "skipped",
          },
          {
            service: "api",
            type: "int",
            path: "src/api/routes/__testkit__/blocked.int.testkit.ts",
            status: "not_run",
          },
        ],
      },
      config: {
        provider: "github",
        mode: "warn",
      },
      transport: {
        async fetchRepoIssues(repo, numbers) {
          const map = new Map();
          map.set(numbers[0], {
            repo,
            number: numbers[0],
            exists: true,
            title: "Bad message bug",
            state: "OPEN",
            url: `https://github.com/${repo}/issues/${numbers[0]}`,
            checkedAt: "2026-04-27T00:00:00.000Z",
            source: "github",
          });
          return map;
        },
      },
    });

    expect(result.summary.byCode.open_not_reproduced).toBeUndefined();
    expect(result.entries[0].findings).toEqual([]);
    expect(result.entries[0].status).toBe("not_executed");
    expect(result.entries[0].observed).toMatchObject({
      matchedTests: 2,
      executedTests: 0,
      passedTests: 0,
      failedTests: 0,
      skippedTests: 1,
      notRunTests: 1,
      reproduced: false,
    });
  });

  it("still treats mixed passed and skipped execution as not reproduced", async () => {
    const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-gh-mixed-execution-"));
    tempDirs.push(tempDir);
    const document = normalizeKnownFailuresDocument({
      schemaVersion: 1,
      issueRepo: "acme/repo",
      entries: [
        {
          id: "bad-message",
          title: "Bad message bug",
          classification: "product_bug",
          state: "open",
          issue: {
            repo: "acme/repo",
            number: 12,
            url: "https://github.com/acme/repo/issues/12",
          },
          description: "Wrong message",
          whyFailing: "Payload is wrong",
          lastReviewedAt: "2026-04-27",
          matches: [
            {
              service: "api",
              type: "int",
              path: "src/api/routes/__testkit__/passed.int.testkit.ts",
            },
            {
              service: "api",
              type: "int",
              path: "src/api/routes/__testkit__/skipped.int.testkit.ts",
            },
          ],
        },
      ],
    });

    const result = await validateKnownFailureIssues({
      productDir: tempDir,
      document,
      statusArtifact: {
        tests: [
          {
            service: "api",
            type: "int",
            path: "src/api/routes/__testkit__/passed.int.testkit.ts",
            status: "passed",
          },
          {
            service: "api",
            type: "int",
            path: "src/api/routes/__testkit__/skipped.int.testkit.ts",
            status: "skipped",
          },
        ],
      },
      config: {
        provider: "github",
        mode: "warn",
      },
      transport: {
        async fetchRepoIssues(repo, numbers) {
          const map = new Map();
          map.set(numbers[0], {
            repo,
            number: numbers[0],
            exists: true,
            title: "Bad message bug",
            state: "OPEN",
            url: `https://github.com/${repo}/issues/${numbers[0]}`,
            checkedAt: "2026-04-27T00:00:00.000Z",
            source: "github",
          });
          return map;
        },
      },
    });

    expect(result.summary.byCode.open_not_reproduced).toBe(1);
    expect(result.entries[0].status).toBe("open_not_reproduced");
    expect(result.entries[0].observed).toMatchObject({
      matchedTests: 2,
      executedTests: 1,
      passedTests: 1,
      failedTests: 0,
      skippedTests: 1,
      notRunTests: 0,
      reproduced: false,
    });
  });

  it("falls back to warning when validation is unavailable", async () => {
    const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-gh-unavailable-"));
    tempDirs.push(tempDir);
    const document = normalizeKnownFailuresDocument({
      schemaVersion: 1,
      entries: [
        {
          id: "bad-message",
          title: "Bad message bug",
          classification: "product_bug",
          state: "open",
          issue: {
            repo: "acme/repo",
            number: 12,
            url: "https://github.com/acme/repo/issues/12",
          },
          description: "Wrong message",
          whyFailing: "Payload is wrong",
          lastReviewedAt: "2026-04-27",
          matches: [{ path: "src/api/routes/__testkit__/failing.int.testkit.ts" }],
        },
      ],
    });

    const result = await validateKnownFailureIssues({
      productDir: tempDir,
      document,
      config: {
        provider: "github",
        mode: "warn",
      },
      transport: null,
    });

    expect(result.summary.byCode.validation_unavailable).toBe(1);
    expect(result.summary.errors).toBe(0);
    expect(result.summary.warnings).toBeGreaterThan(0);
  });
});
