import { mock } from "bun:test";
import type {
  PrCommentJobPayload,
  PrMentionJobPayload,
  SlackMentionJobPayload,
} from "@/types/index.js";

// ── SlackMentionPipeline deps factory ─────────────────────────────────────────

export interface SlackMentionDepsOverrides {
  agentOutput?: string;
  agentError?: Error;
  replyError?: Error;
  repos?: Array<{
    name: string;
    url: string;
    defaultBranch: string;
    description?: string;
    keywords?: string[];
  }>;
  jiraIssue?: Record<string, unknown> | null;
}

/**
 * Creates fresh mocks for SlackMentionPipeline dependencies.
 * All mocks are re-created on each call — use inside beforeEach or per test.
 */
export function createSlackMentionDeps(overrides: SlackMentionDepsOverrides = {}) {
  const {
    agentOutput = "REPLY_START\nHere is my answer.\nREPLY_END",
    agentError,
    replyError,
    repos = [],
  } = overrides;

  const agentCli = {
    runAgent: mock(async () => {
      if (agentError) throw agentError;
      return { output: agentOutput, exitCode: 0, success: true };
    }),
  };

  const repoRegistry = {
    getAll: mock(() => repos),
    getByName: mock(() => undefined),
  };

  const slackNotifier = {
    replyInThread: mock(async () => {
      if (replyError) throw replyError;
    }),
    notifyTicketStarted: mock(async () => {}),
    notifyPrCreated: mock(async () => {}),
    notifyCommentAttended: mock(async () => {}),
    addReaction: mock(async () => {}),
    removeReaction: mock(async () => {}),
  };

  const jiraClient = {
    getIssue: mock(async (_key: string) => {
      if (overrides.jiraIssue === null) throw new Error("not found");
      return (
        overrides.jiraIssue ?? {
          id: "1",
          key: "SQDP-1",
          summary: "Test issue",
          description: null,
          issueType: "Bug",
          status: "To Do",
          created: null,
          acceptanceCriteria: null,
          stacktrace: null,
          flow: null,
          assignee: null,
          parent: null,
          subtasks: [],
          children: [],
          attachments: [],
          comments: [],
          components: [],
        }
      );
    }),
  };

  const datadogLogsClient = null;

  return { agentCli, repoRegistry, slackNotifier, jiraClient, datadogLogsClient };
}

export function buildSlackMentionJob(
  overrides: Partial<SlackMentionJobPayload> = {},
): SlackMentionJobPayload {
  return {
    jobKind: "slack_mention",
    channelId: "C01TEST",
    threadTs: "1234567890.000100",
    messageTs: "1234567890.000200",
    userId: "U01USER",
    text: "Hey @hu-agent what is the status of SQDP-1?",
    language: "en",
    ...overrides,
  };
}

// ── PrCommentPipeline deps factory ────────────────────────────────────────────

export interface PrCommentDepsOverrides {
  repoFound?: boolean;
  agentOutput?: string;
  hasChanges?: boolean;
  pushError?: Error;
  diffStats?: { passed: boolean; violations: string[] };
}

export function buildPrCommentJob(
  overrides: Partial<PrCommentJobPayload> = {},
): PrCommentJobPayload {
  return {
    jobKind: "pr_comment",
    repoName: "my-api",
    prNumber: 42,
    branch: "feature/SQDP-123-fix",
    commentId: 100,
    commentBody: "Please fix the type errors",
    isReviewComment: false,
    prUrl: "https://github.com/org/my-api/pull/42",
    inReplyToId: null,
    ...overrides,
  };
}

export function createPrCommentDeps(overrides: PrCommentDepsOverrides = {}) {
  const {
    repoFound = true,
    agentOutput = "REPLY_START\nFixed the type errors.\nREPLY_END",
    hasChanges = false,
  } = overrides;

  const repoRegistry = {
    getByName: mock((_name: string) =>
      repoFound
        ? {
            name: "my-api",
            url: "https://github.com/org/my-api",
            defaultBranch: "main",
            description: "",
            keywords: [],
          }
        : undefined,
    ),
    getAll: mock(() => []),
  };

  const agentCli = {
    runAgent: mock(async () => ({ output: agentOutput, exitCode: 0, success: true })),
  };

  const slackNotifier = {
    notifyCommentAttended: mock(async () => {}),
  };

  // Mock manager returned by new RepoManagerImpl(...)
  const mockManager = {
    ensureCloned: mock(async () => {}),
    checkoutBranch: mock(async () => {}),
    checkoutBase: mock(async () => {}),
    installDependencies: mock(async () => {}),
    getChangedFiles: mock(async () => (hasChanges ? ["src/foo.ts"] : [])),
    hasChanges: mock(async () => hasChanges),
    getDiffStats: mock(async () => overrides.diffStats ?? { passed: true, violations: [] }),
    commitAndPush: mock(async () => {
      if (overrides.pushError) throw overrides.pushError;
    }),
    cleanStaleBranches: mock(async () => {}),
  };

  // Mock github client returned by new GitHubClientImpl(...)
  const mockGithubClient = {
    getPrDiff: mock(async () => "diff --git a/src/foo.ts b/src/foo.ts"),
    listIssueComments: mock(async () => []),
    listPullReviewComments: mock(async () => []),
    createIssueComment: mock(async () => {}),
    createReviewCommentReply: mock(async () => {}),
    getPullReviewComment: mock(async () => ({
      id: 1,
      body: "parent comment",
      user: { login: "dev" },
      path: "src/foo.ts",
      line: 1,
      created_at: "2026-05-01T10:00:00Z",
      in_reply_to_id: null,
    })),
    getCommentBodyHtml: mock(async () => null as string | null),
  };

  return { repoRegistry, agentCli, slackNotifier, mockManager, mockGithubClient };
}

// ── PrMentionPipeline deps factory ────────────────────────────────────────────

export interface PrMentionDepsOverrides {
  repoFound?: boolean;
  agentOutput?: string;
  hasChanges?: boolean;
  safetyPassed?: boolean;
  pushError?: Error;
}

export function buildMentionJob(overrides: Partial<PrMentionJobPayload> = {}): PrMentionJobPayload {
  return {
    jobKind: "pr_mention",
    repoName: "my-api",
    prNumber: 42,
    branch: "feature/SQDP-123-foo",
    commentId: 100,
    commentBody: "Hey @hu-agent can you fix the lint errors?",
    isReviewComment: false,
    prUrl: "https://github.com/org/my-api/pull/42",
    mentionAuthor: "human-dev",
    inReplyToId: null,
    ...overrides,
  };
}

export function createMentionPipelineDeps(overrides: PrMentionDepsOverrides = {}) {
  const {
    repoFound = true,
    agentOutput = "REPLY_START\nDone!\nREPLY_END",
    hasChanges = false,
    safetyPassed = true,
  } = overrides;

  const agentCli = {
    runAgent: mock(async () => ({ output: agentOutput, exitCode: 0, success: true })),
  };

  const repoRegistry = {
    getByName: mock((_name: string) =>
      repoFound
        ? {
            name: "my-api",
            url: "https://github.com/org/my-api",
            defaultBranch: "main",
            description: "",
            keywords: [],
          }
        : undefined,
    ),
    getAll: mock(() => []),
  };

  const mockManager = {
    ensureCloned: mock(async () => {}),
    checkoutBranch: mock(async () => {}),
    checkoutBase: mock(async () => {}),
    installDependencies: mock(async () => {}),
    getChangedFiles: mock(async () => (hasChanges ? ["src/foo.ts"] : [])),
    hasChanges: mock(async () => hasChanges),
    getDiffStats: mock(async () => ({
      passed: safetyPassed,
      violations: safetyPassed ? [] : ["Diff too large"],
      totalLinesChanged: safetyPassed ? 10 : 600,
      filesChanged: 1,
      filesDeleted: 0,
    })),
    commitAndPush: mock(async () => {
      if (overrides.pushError) throw overrides.pushError;
    }),
    cleanStaleBranches: mock(async () => {}),
  };

  const slackNotifier = {
    notifyCommentAttended: mock(async () => {}),
  };

  const mockGithubClient = {
    getPrDiff: mock(async () => "diff --git ..."),
    listIssueComments: mock(async () => []),
    listPullReviewComments: mock(async () => []),
    createIssueComment: mock(async () => {}),
    createReviewCommentReply: mock(async () => {}),
    getPullReviewComment: mock(async () => ({
      id: 1,
      body: "parent",
      user: { login: "dev" },
      path: "src/foo.ts",
      line: 1,
      created_at: "2026-05-01T10:00:00Z",
      in_reply_to_id: null,
    })),
    getCommentBodyHtml: mock(async () => null as string | null),
  };

  return { agentCli, repoRegistry, mockManager, slackNotifier, mockGithubClient };
}
