import type { Logger } from "pino";
import { SQUAD_TRIBE_MAP, DAILY_REPORT } from "@/core/constants.js";
import { GitHubClientImpl } from "@/github/client.js";
import type { RepoConfig } from "@/types/index.js";

export interface PrMergedWeeklyRow {
  weekStart: string;
  weekEnd: string;
  prsMergedTotal: number;
  prsMergedBot: number;
  prsMergedOtherBots: number;
}

export interface PrMergedRepoRow {
  repo: string;
  prsMergedTotal: number;
  prsMergedBot: number;
  prsMergedOtherBots: number;
}

export interface PrMergedSquadRow {
  /** Project key (SQDP, SQRN, …) or "unmapped" for PRs without a recognisable ticket prefix. */
  squad: string;
  /** Tribe from SQUAD_TRIBE_MAP, or "" when unmapped. */
  tribe: string;
  prsMergedTotal: number;
  prsMergedBot: number;
  prsMergedOtherBots: number;
}

export interface CollectGithubMergedPrsDeps {
  repos: readonly RepoConfig[];
  getGitHubToken: () => Promise<string>;
  botLogin: string;
  buckets: ReadonlyArray<{ weekStart: string; weekEnd: string }>;
  logger: Logger;
}

export interface CollectGithubMergedPrsResult {
  weekly: PrMergedWeeklyRow[];
  byRepo: PrMergedRepoRow[];
  bySquad: PrMergedSquadRow[];
}

/** Spacing between repo scans — GitHub's secondary rate-limit trips under bursty
 *  `pulls.list` traffic across many repos for a wide window. Combined with the
 *  per-page retry-on-403 in `listMergedPrs`, this keeps us comfortably below the
 *  secondary limit. */
const REPO_DELAY_MS = 5_000;

function findBucketIndex(
  mergedMs: number,
  edges: readonly { startMs: number; endMs: number }[],
): number {
  for (let i = 0; i < edges.length; i++) {
    const e = edges[i]!;
    if (mergedMs >= e.startMs && mergedMs < e.endMs) return i;
  }
  return -1;
}

function extractSquadKey(branchName: string): string | null {
  const match = branchName.match(DAILY_REPORT.TICKET_PREFIX_RE);
  const prefix = match?.[1];
  if (!prefix) return null;
  return prefix.toUpperCase();
}

/**
 * For each managed repo, list PRs merged inside the full analytics window and
 * roll them up three ways:
 *  - weekly counts (same shape the CSV already uses),
 *  - per-repo aggregates across the full window,
 *  - per-squad aggregates derived from the PR's branch name (regex on ticket
 *    key prefix → SQUAD_TRIBE_MAP lookup).
 *
 * A single-repo failure is logged and skipped — it shouldn't void the whole scan.
 */
export async function collectGithubMergedPrs(
  deps: CollectGithubMergedPrsDeps,
): Promise<CollectGithubMergedPrsResult> {
  const { repos, getGitHubToken, botLogin, buckets, logger } = deps;

  const weekly: PrMergedWeeklyRow[] = buckets.map((b) => ({
    weekStart: b.weekStart,
    weekEnd: b.weekEnd,
    prsMergedTotal: 0,
    prsMergedBot: 0,
    prsMergedOtherBots: 0,
  }));

  if (weekly.length === 0) return { weekly, byRepo: [], bySquad: [] };

  const sinceIso = buckets[0]!.weekStart;
  const untilIso = buckets[buckets.length - 1]!.weekEnd;
  const edges = weekly.map((r) => ({
    startMs: Date.parse(`${r.weekStart}T00:00:00Z`),
    endMs: Date.parse(`${r.weekEnd}T00:00:00Z`),
  }));

  const token = await getGitHubToken();
  const lowerBotLogin = botLogin.toLowerCase();

  const byRepo: PrMergedRepoRow[] = [];
  /** squad key → aggregate row (squad key may be "unmapped"). */
  const bySquadMap = new Map<string, PrMergedSquadRow>();

  function bumpSquad(squadKey: string, kind: "total" | "bot" | "otherBots"): void {
    let row = bySquadMap.get(squadKey);
    if (!row) {
      row = {
        squad: squadKey,
        tribe: squadKey === "unmapped" ? "" : (SQUAD_TRIBE_MAP[squadKey] ?? ""),
        prsMergedTotal: 0,
        prsMergedBot: 0,
        prsMergedOtherBots: 0,
      };
      bySquadMap.set(squadKey, row);
    }
    if (kind === "total") row.prsMergedTotal += 1;
    else if (kind === "bot") row.prsMergedBot += 1;
    else row.prsMergedOtherBots += 1;
  }

  let first = true;
  for (const repo of repos) {
    if (!first) await new Promise<void>((r) => setTimeout(r, REPO_DELAY_MS));
    first = false;

    const repoLogger = logger.child({ repo: repo.name });
    const repoRow: PrMergedRepoRow = {
      repo: repo.name,
      prsMergedTotal: 0,
      prsMergedBot: 0,
      prsMergedOtherBots: 0,
    };
    byRepo.push(repoRow);

    try {
      const client = new GitHubClientImpl(token, repo.url, "", repoLogger);
      const prs = await client.listMergedPrs({ sinceIso, untilIso });
      for (const pr of prs) {
        const mergedMs = Date.parse(pr.mergedAt);
        if (!Number.isFinite(mergedMs)) continue;
        const idx = findBucketIndex(mergedMs, edges);
        if (idx < 0) continue;

        const wk = weekly[idx]!;
        wk.prsMergedTotal += 1;
        repoRow.prsMergedTotal += 1;

        const login = pr.authorLogin ?? "";
        const lowerLogin = login.toLowerCase();
        const isBot = lowerLogin === lowerBotLogin;
        const isOtherBot = !isBot && login.endsWith("[bot]");

        if (isBot) {
          wk.prsMergedBot += 1;
          repoRow.prsMergedBot += 1;
        } else if (isOtherBot) {
          wk.prsMergedOtherBots += 1;
          repoRow.prsMergedOtherBots += 1;
        }

        const squadKey = extractSquadKey(pr.headRef) ?? "unmapped";
        bumpSquad(squadKey, "total");
        if (isBot) bumpSquad(squadKey, "bot");
        else if (isOtherBot) bumpSquad(squadKey, "otherBots");
      }
      repoLogger.debug({ merged: prs.length }, "analytics_gh_repo_scanned");
    } catch (err) {
      repoLogger.warn(
        { err: err instanceof Error ? err.message : String(err) },
        "analytics_gh_repo_scan_failed",
      );
    }
  }

  const bySquad = [...bySquadMap.values()].sort((a, b) => b.prsMergedTotal - a.prsMergedTotal);

  logger.info(
    {
      repos: repos.length,
      weeks: weekly.length,
      totalMerged: weekly.reduce((acc, r) => acc + r.prsMergedTotal, 0),
      botMerged: weekly.reduce((acc, r) => acc + r.prsMergedBot, 0),
      otherBotsMerged: weekly.reduce((acc, r) => acc + r.prsMergedOtherBots, 0),
      squads: bySquad.length,
    },
    "analytics_gh_scan_done",
  );

  return { weekly, byRepo, bySquad };
}
