import type { RepoManagerImpl } from "@/repo/manager.js";

// ── Job / Queue ──────────────────────────────────────────────────────────────

export type JobKind = "jira" | "pr_comment" | "slack_mention" | "pr_mention" | "jira_ticket_review";

export interface JiraJobPayload {
  jobKind: "jira";
  issueId: string;
  issueKey: string;
  summary: string;
  description: string;
}

export interface PrCommentJobPayload {
  jobKind: "pr_comment";
  repoName: string;
  prNumber: number;
  branch: string;
  commentId: number;
  commentBody: string;
  isReviewComment: boolean;
  prUrl?: string;
  /** For review-comment replies: id of the parent review comment being replied to. */
  inReplyToId?: number | null;
}

export interface SlackMentionJobPayload {
  jobKind: "slack_mention";
  channelId: string;
  /** Timestamp of the thread parent (used as thread_ts for replies). For top-level mentions this equals messageTs. */
  threadTs: string;
  /** Timestamp of the specific message that contains the mention. */
  messageTs: string;
  userId: string;
  text: string;
  language: "es" | "en";
}

/** A single message rendered into the slack-mention prompt's thread-context block. */
export interface ThreadContextEntry {
  /** Display name (resolved from Slack user ID) or "[hu-agent]" for bot replies. */
  author: string;
  /** Message text (already stripped of bot mention tags by the pipeline). */
  text: string;
  /** Slack message timestamp (e.g. "1714935600.001200"). */
  ts: string;
  /** Whether this entry is a previous reply from the bot itself. */
  isBot: boolean;
}

export interface PrMentionJobPayload {
  jobKind: "pr_mention";
  repoName: string;
  prNumber: number;
  branch: string;
  commentId: number;
  commentBody: string;
  isReviewComment: boolean;
  prUrl?: string;
  /** The GitHub login of the user who mentioned the bot. */
  mentionAuthor: string;
  /** For review-comment replies: id of the parent review comment being replied to. */
  inReplyToId?: number | null;
}

/** Triggered when a Jira comment @-mentions the bot and contains `review-ticket` (implementation readiness). */
export interface JiraTicketReviewJobPayload {
  jobKind: "jira_ticket_review";
  issueId: string;
  issueKey: string;
  summary: string;
  /** Comment id that triggered the review (dedupe). */
  triggeredByCommentId: string;
}

export type JobPayload =
  | JiraJobPayload
  | PrCommentJobPayload
  | SlackMentionJobPayload
  | PrMentionJobPayload
  | JiraTicketReviewJobPayload;

export type JobOutcome = "completed" | "needs_info" | "run_failed";

export type JobHandler = (job: JobPayload, signal: AbortSignal) => Promise<JobOutcome>;

export interface IJobQueue {
  enqueue(job: JobPayload): Promise<void>;
  start(handler: JobHandler): void;
  stop(): void;
  /** Stop accepting work and drain the in-flight job, aborting it if it exceeds graceMs. */
  shutdown(graceMs: number): Promise<void>;
  isBusy(): boolean;
  getCurrentJobKind(): JobKind | null;
  pendingCount(): number;
  getCurrentJobKey(): string | null;
  getPendingJobKeys(): string[];
}

// ── Jira ─────────────────────────────────────────────────────────────────────

export interface JiraUser {
  accountId: string;
  displayName: string;
  emailAddress?: string;
}

export interface JiraComment {
  id: string;
  author: JiraUser;
  body: string;
  created: string;
}

export interface JiraAttachment {
  id: string;
  filename: string;
  mimeType: string;
  size: number;
  content: string;
  thumbnail: string | null;
  created: string;
}

export interface JiraParent {
  id: string;
  key: string;
  summary: string;
  description: string | null;
  issueType: string;
  status: string;
  flow?: IssueFlow | null;
  attachments: JiraAttachment[];
}

export interface JiraSubtask {
  id: string;
  key: string;
  summary: string;
  status: string;
  issueType: string;
}

export type IssueFlow = "bugfix" | "hotfix" | "regular";

export interface JiraIssue {
  id: string;
  key: string;
  summary: string;
  description: string | null;
  issueType: string;
  status: string;
  created: string | null;
  updated: string | null;
  acceptanceCriteria: string | null;
  stacktrace: string | null;
  flow?: IssueFlow | null;
  assignee: JiraUser | null;
  parent: JiraParent | null;
  subtasks: JiraSubtask[];
  children: JiraSubtask[];
  attachments: JiraAttachment[];
  comments: JiraComment[];
  components: Array<{ id: string; name?: string }>;
}

export interface JiraSearchResult {
  issues: JiraIssue[];
  total: number;
}

export interface JiraTransition {
  id: string;
  name: string;
}

export interface JiraRawAttachment {
  id: string;
  filename: string;
  mimeType: string;
  size: number;
  content: string;
  thumbnail?: string | null;
  created: string;
}

export interface ADFNode {
  type: string;
  text?: string;
  content?: ADFNode[];
  marks?: Array<{ type: string; attrs?: Record<string, unknown> }>;
  attrs?: Record<string, unknown>;
}

// ── Repo ─────────────────────────────────────────────────────────────────────

export interface RepoConfig {
  name: string;
  url: string;
  description: string;
  keywords: string[];
  /** Path inside the cloned repo to the team-owned index file (defaults to .cursor/hu-developer-bot.md). */
  rulesIndexFile?: string;
  /** Fallback: path relative to bot cwd (e.g. rules/humand-backoffice.md). Used when rulesIndexFile is absent. */
  rulesFile?: string;
  /** Base branch to check out before creating feature branches (defaults to "develop"). */
  defaultBranch?: string;
  subtaskPrefix?: string;
  prTitleTemplate?: string;
  prLabels?: {
    bugfix?: string[];
    hotfix?: string[];
    default?: string[];
  };
  /** npm script name(s) to run as pre-PR check (e.g. "tsc:strict:diff"). Replaces generic "test". */
  checkScripts?: string[];
  /** Override the install command (e.g. ["yarn", "install", "--immutable"] for Yarn 4 repos). */
  installCmd?: string[];
  /** Git SSH URL of a remote rules repo to clone and symlink into .cursor/rules/. */
  remoteRulesRepo?: string;
}

export interface RepoContext {
  name: string;
  description: string;
  keywords: string[];
  rules: string | null;
  checkScripts: string[];
}

export interface RepoHandle {
  repo: RepoConfig;
  manager: RepoManagerImpl;
}

// ── Pipeline ─────────────────────────────────────────────────────────────────

export interface BuildValidationResult {
  success: boolean;
  installOutput?: string;
  buildOutput?: string;
  testOutput?: string;
  checkOutputs?: Record<string, string>;
  failedStep?: "install" | "build" | "test" | "check";
  failedCheck?: string;
  testSkipped?: boolean;
}

export interface SafetyReport {
  passed: boolean;
  totalLinesChanged: number;
  filesChanged: number;
  filesDeleted: number;
  violations: string[];
}

export interface RepoPrResult {
  repoName: string;
  prUrl: string;
  subtaskKey?: string;
}

export interface PipelineResult {
  success: boolean;
  issueKey: string;
  outcome: JobOutcome;
  repos?: RepoPrResult[];
  abortReason?: string;
}

export type TriageVerdict = "PROCEED" | "NEEDS_INFO" | "RUN_FAILED";

export interface TriageResult {
  verdict: TriageVerdict;
  title: string | null;
  summary: string;
  questions: string | null;
  raw: string;
}

// ── Cursor ───────────────────────────────────────────────────────────────────

export type AgentStatus = "CREATING" | "RUNNING" | "FINISHED" | "STOPPED" | "FAILED";

export interface CursorAgent {
  id: string;
  name: string;
  status: AgentStatus;
  source: {
    repository: string;
    ref?: string;
  };
  target: {
    branchName?: string;
    url?: string;
    prUrl?: string;
    autoCreatePr?: boolean;
    openAsCursorGithubApp?: boolean;
    skipReviewerRequest?: boolean;
  };
  summary?: string;
  createdAt: string;
}

export interface LaunchAgentRequest {
  prompt: {
    text: string;
    images?: Array<{
      data: string;
      dimension: { width: number; height: number };
    }>;
  };
  model?: string;
  source: {
    repository: string;
    ref?: string;
  };
  target?: {
    autoCreatePr?: boolean;
    openAsCursorGithubApp?: boolean;
    skipReviewerRequest?: boolean;
    branchName?: string;
  };
  webhook?: {
    url: string;
    secret?: string;
  };
}

export interface FollowUpRequest {
  prompt: {
    text: string;
  };
}

export interface ConversationMessage {
  id: string;
  type: "user_message" | "assistant_message";
  text: string;
}

export interface AgentConversation {
  id: string;
  messages: ConversationMessage[];
}

export interface CliRunResult {
  success: boolean;
  output: string;
  exitCode: number | null;
}

// ── Config ───────────────────────────────────────────────────────────────────

export interface AppConfig {
  jiraEmail: string;
  jiraApiToken: string;
  jiraDomain: string;
  jiraPollEnabled: boolean;
  /** When false, the PR comment poller does not start (reviews and PR comments won't be handled). */
  prReviewEnabled: boolean;
  /** When true, poll all open PRs for @hu-agent mentions. Default false. */
  prMentionEnabled: boolean;
  /** Project keys to restrict Jira polling to (e.g. ["SQDP", "SQZB"]). Empty = all projects. */
  squadScope: string[];
  /** GitHub bot user login (e.g. hu-agent[bot]) for PR comment poller. */
  githubBotUser: string;
  /** GitHub logins whose PR comments should be ignored (e.g. ["cursor[bot]", "hu-reviewer[bot]"]). */
  ignoredCommentLogins: string[];
  /** PAT; optional when GitHub App credentials are set. */
  githubToken: string;
  /** GitHub App ID (optional). */
  githubAppId?: number;
  /** GitHub App installation ID (optional). */
  githubAppInstallationId?: number;
  /** GitHub App private key PEM (optional). */
  githubAppPrivateKey?: string;
  /** Slack bot token used for notifications (optional). */
  slackBotToken?: string;
  /** Slack channel ID where notifications are sent (optional). */
  slackChannelId?: string;
  /** Slack channel ID for daily PR report (optional). Defaults to C0ALG71FCHY when Slack is enabled. */
  slackDailyReportChannelId?: string;
  /** Slack channel ID for high-queue-load alerts. Defaults to C0ALL925DJ8. */
  slackAlertChannelId?: string;
  /** Slack channel ID for PR comment attended notifications. Defaults to C0ATP60H09E. */
  slackPrCommentChannelId?: string;
  /** When true, send daily PR status to Slack at dailyReportHours. Default false. */
  dailyReportEnabled: boolean;
  /** Hours (0–23) in `dailyReportTimezone` to send the daily report. Default [9, 17]. */
  dailyReportHours: number[];
  /** IANA timezone for `dailyReportHours` (e.g. `America/Argentina/Buenos_Aires`). */
  dailyReportTimezone: string;
  workdirPath: string;
  cursorApiKey: string;
  cursorModel: string;
  /** Which agentic backend to drive. Default "cursor" until Claude is validated in prod. */
  agentProvider: AgentProvider;
  /** Anthropic API key. Required when agentProvider === "claude". */
  anthropicApiKey: string;
  /** Claude model id/alias. Default "claude-opus-4-8". */
  anthropicModel: string;
  /**
   * Override for the Claude API endpoint (sets ANTHROPIC_BASE_URL for the `claude` subprocess).
   * Unset → the CLI talks directly to api.anthropic.com. Set it to route through an
   * Anthropic-compatible gateway (e.g. Humand's internal LLM gateway, "Glados").
   */
  anthropicBaseUrl?: string;
  port: number;
  logLevel: string;
  codeartifactDomain: string;
  codeartifactDomainOwner: string;
  codeartifactRegion: string;
  /** Datadog API key for Logs API (optional). */
  datadogApiKey: string;
  /** Datadog Application key for Logs API (optional). */
  datadogAppKey: string;
  /** Datadog site host, e.g. `us5.datadoghq.com` (API base: `https://api.<site>`). */
  datadogSite: string;
  /** When true, poll Slack channels for @hu-agent mentions. Default false. */
  slackMentionsEnabled: boolean;
  /** Slack bot user ID (e.g. U07XXXXXXXX) for detecting mentions. */
  slackBotUserId: string;
  /** DynamoDB table name for persisting daily report state. Empty = no persistence. */
  dynamodbTableName: string;
  /** Poll Jira for @bot + `review-ticket` in comment and run implementation readiness + Jira reply. */
  jiraTicketReviewEnabled: boolean;
  /**
   * True when `NODE_ENV !== "production"`. In dev mode the bot forces all
   * pollers + the daily-report scheduler off and redirects every Slack post
   * to `slackDevChannelId` with a `[DEV]` prefix.
   */
  isDev: boolean;
  /** Slack channel that receives every post when `isDev` is true. */
  slackDevChannelId: string;
  /** When true, download Jira video attachments and extract frames via ffmpeg. Default false. */
  videoAttachmentsEnabled: boolean;
  /** Path or PATH-resolvable name of the ffmpeg binary. Defaults to "ffmpeg". */
  ffmpegPath: string;
  /** Path or PATH-resolvable name of the ffprobe binary. Defaults to "ffprobe". */
  ffprobePath: string;
  /** When true, emit custom DogStatsD metrics to the Datadog agent. Default: true in production, false otherwise. */
  metricsEnabled: boolean;
  /** Hostname of the DogStatsD agent. Defaults to "localhost". */
  ddAgentHost: string;
  /** UDP port of the DogStatsD agent. Defaults to 8125. */
  ddDogstatsdPort: number;
  /** Deployment environment tag sent to Datadog (DD_ENV). Defaults to "dev". */
  ddEnv: string;
  /** When true (dev sandbox only), suppress all Jira writes and open PRs as draft. */
  sandboxMode: boolean;
}

// ── Package Manager ──────────────────────────────────────────────────────────

export interface PackageManagerInfo {
  cmd: string;
  installArgs: string[];
}

export interface ResolvedNode {
  binPath: string;
  requested: string;
  resolved: string;
}

export interface ScoredRepo {
  repo: RepoConfig;
  score: number;
}

// ── Agent runner (provider-agnostic) ─────────────────────────────────────────

/** Which agentic backend the bot drives. */
export type AgentProvider = "claude" | "cursor";

export interface RunAgentOptions {
  /** Issue/ticket key to tag all logs and stderr output with. */
  issueKey?: string;
  /**
   * Relative path (from workDir) to a per-job directory where the prompt file
   * should be written when the prompt exceeds the argv limit. The directory
   * must already exist (the pipeline creates it for images).
   */
  jobDir?: string;
  /** When aborted, the runner kills the agent subprocess and throws JobAbortedError. */
  signal?: AbortSignal;
}

/**
 * A subprocess-backed coding agent. Implementations spawn a CLI inside `workDir`,
 * run the full agentic loop (edit files, run commands), and return the final
 * assistant text in `CliRunResult.output` (with any prompt markers intact).
 */
export interface AgentRunner {
  runAgent(
    prompt: string,
    workDir: string,
    timeoutMs?: number,
    options?: RunAgentOptions,
  ): Promise<CliRunResult>;
}

// ── Agent CLI (shared by both providers) ────────────────────────────────────

export interface StreamJsonEvent {
  type: string;
  content?: string;
  tool_name?: string;
  tool_call_id?: string;
  arguments?: string;
  output?: string;
  status?: string;
  message?: string;
  name?: string;
  input?: Record<string, unknown>;
  role?: string;
  [key: string]: unknown;
}
