import { Octokit } from "@octokit/rest";
import cron from "node-cron";
import dotenv from "dotenv";

dotenv.config();

const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
const VIBEWORK_API_URL = process.env.VIBEWORK_API_URL || "https://agent.wooo.work/api";
const SCOUT_API_KEY = process.env.SCOUT_API_KEY;
const SCOUT_AGENT_ID = process.env.SCOUT_AGENT_ID || "scout_official_1";
const SCOUT_CRON_EXPRESSION = process.env.SCOUT_CRON_EXPRESSION || "*/3 * * * *";
const SCOUT_ISSUE_LABELS_RAW = process.env.SCOUT_ISSUE_LABELS || process.env.SCOUT_ISSUE_LABEL || "";
const SCOUT_ENABLED = (process.env.SCOUT_ENABLED || "true").toLowerCase() !== "false";
const SCOUT_TARGET_REPOS_RAW =
  process.env.SCOUT_TARGET_REPOS ||
  "open-webui/open-webui,microsoft/vscode,vercel/next.js,langchain-ai/langgraph,facebook/react,microsoft/TypeScript,openai/openai-cookbook,astral-sh/ruff,sequelize/sequelize,pnpm/pnpm,prisma/prisma";
const SCOUT_PER_PAGE = Math.min(Math.max(parseInt(process.env.SCOUT_PER_PAGE || "50", 10), 1), 100);
const SCOUT_MAX_ISSUES_PER_SCAN = Math.max(parseInt(process.env.SCOUT_MAX_ISSUES_PER_SCAN || "60", 10), 1);
const SCOUT_COMMENT_DELAY_SECONDS = Math.max(parseInt(process.env.SCOUT_COMMENT_DELAY_SECONDS || "2", 10), 0);
const SCOUT_COMMENT_REPOS_RAW = process.env.SCOUT_COMMENT_REPOS || "";

const SCOUT_COMMENT_REPOS = SCOUT_COMMENT_REPOS_RAW === "*" 
  ? ["*"] 
  : SCOUT_COMMENT_REPOS_RAW.split(",").map(r => r.trim().toLowerCase()).filter(Boolean);


const SCOUT_ISSUE_LABELS = SCOUT_ISSUE_LABELS_RAW
  .split(",")
  .map((entry) => entry.trim())
  .filter(Boolean);

const SCOUT_TARGET_REPOS = SCOUT_TARGET_REPOS_RAW
  .split(",")
  .map((entry) => entry.trim())
  .filter(Boolean)
  .map((entry) => {
    const [owner, repo] = entry.split("/");
    return { owner, repo };
  })
  .filter((repo) => repo.owner && repo.repo);

const fallbackRepo = { owner: "open-webui", repo: "open-webui" };
const TARGET_REPOS = SCOUT_TARGET_REPOS.length > 0 ? SCOUT_TARGET_REPOS : [fallbackRepo];

if (!SCOUT_API_KEY) {
  console.warn("WARNING: SCOUT_API_KEY is not set. Draft API may be rejected by server.");
}
if (!GITHUB_TOKEN) {
  console.warn("WARNING: GITHUB_TOKEN is not set. Scout bot cannot post comments.");
}

function normalizeText(value: string, maxLength: number, fallback = "N/A") {
  const cleanText = (value || "").replace(/\s+/g, " ").trim();
  if (!cleanText) {
    return fallback;
  }
  return cleanText.length <= maxLength ? cleanText : `${cleanText.slice(0, Math.max(maxLength - 1, 0))}…`;
}

function buildDraftPayload(issueTitle: string, issueBody: string, issueUrl: string) {
  const prefix = "GitHub Issue: ";
  const titleMax = 120;
  const descriptionMax = 2000;
  const sourceSuffix = `\n\nSource: ${issueUrl}`;
  const sourceAllowance = Math.max(descriptionMax - sourceSuffix.length, 20);

  const safeTitle = normalizeText(issueTitle, Math.max(titleMax - prefix.length, 20), "Open Source Issue");
  const cleanBody = normalizeText(issueBody || "", sourceAllowance, "請參考 issue 連結取得完整需求內容。");

  return {
    title: `${prefix}${safeTitle}`,
    description: `${cleanBody}${sourceSuffix}`,
    reward_amount: 0, // $0.00 Free AI Bounty
    reward_currency: "USD",
    required_stack: ["TypeScript", "GitHub"],
    test_file_content: "// Needs manual test writing based on issue",
  };
}

const octokit = new Octokit({
  auth: GITHUB_TOKEN,
});

async function wait(seconds: number) {
  return new Promise((resolve) => setTimeout(resolve, seconds * 1000));
}

function getGitHubBackoffSeconds(error: unknown): number {
  const status =
    typeof error === "object" &&
    error !== null &&
    "status" in error &&
    typeof (error as { status?: unknown }).status === "number"
      ? (error as { status: number }).status
      : undefined;

  const message =
    typeof error === "object" &&
    error !== null &&
    "message" in error &&
    typeof (error as { message?: unknown }).message === "string"
      ? (error as { message: string }).message.toLowerCase()
      : "";

  const responseHeaders =
    typeof error === "object" &&
    error !== null &&
    "response" in error &&
    error.response &&
    typeof error.response === "object" &&
    "headers" in error.response
      ? (error.response as { headers?: Record<string, string> }).headers
      : undefined;

  const lowerCaseHeaders = responseHeaders
    ? Object.fromEntries(
        Object.entries(responseHeaders).map(([key, value]) => [key.toLowerCase(), value])
      )
    : undefined;

  if (
    typeof status === "number" &&
    status === 403 &&
    message.includes("secondary rate limit")
  ) {
    const resetAtRaw = lowerCaseHeaders?.["x-ratelimit-reset"];
    if (resetAtRaw) {
      const resetAt = parseInt(resetAtRaw, 10);
      const now = Math.floor(Date.now() / 1000);
      const resetWait = resetAt - now + 5;
      if (resetWait > 0) {
        return resetWait;
      }
    }
    return 60;
  }

  if (typeof status === "number" && status === 403 && message.includes("rate limit exceeded")) {
    const resetAtRaw = lowerCaseHeaders?.["x-ratelimit-reset"];
    if (resetAtRaw) {
      const resetAt = parseInt(resetAtRaw, 10);
      const now = Math.floor(Date.now() / 1000);
      const resetWait = resetAt - now + 5;
      if (resetWait > 0) {
        return resetWait;
      }
    }
    return 60;
  }

  return 0;
}

async function postDraftWithRetry(payload: {
  scout_id: string;
  title: string;
  description: string;
  reward_amount: number;
  reward_currency: string;
  required_stack: string[];
  test_file_content: string;
}) {
  let lastError: unknown = null;

  for (let attempt = 0; attempt < 3; attempt++) {
    try {
      const response = await fetch(`${VIBEWORK_API_URL}/scout/draft`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "Authorization": `Bearer ${SCOUT_API_KEY}`
        },
        body: JSON.stringify(payload),
      });

      if (!response.ok) {
        const bodyText = await response.text();
        throw new Error(`Draft API failed (HTTP ${response.status}): ${bodyText}`);
      }

      return await response.json();
    } catch (error) {
      lastError = error;
      const attemptText = attempt + 1;
      const maxAttempts = 3;
      console.error(`Failed to draft bounty task (attempt ${attemptText}/${maxAttempts}):`, error);
      if (attempt < maxAttempts - 1) {
        await wait(Math.min(5 * (attempt + 1), 15));
      }
    }
  }

  console.error("Failed to draft bounty task after retries:", lastError);
  return null;
}

async function isIssueAlreadyDrafted(issueUrl: string) {
  try {
    const response = await fetch(
      `${VIBEWORK_API_URL}/scout/issue-exists?issue_url=${encodeURIComponent(issueUrl)}`,
      {
        headers: {
          "Authorization": `Bearer ${SCOUT_API_KEY}`,
        },
      }
    );

    if (!response.ok) {
      const bodyText = await response.text();
      console.warn(`Issue existence check failed (HTTP ${response.status}): ${bodyText}`);
      return false;
    }

    const result = await response.json() as { exists?: boolean };
    return Boolean(result.exists);
  } catch (error) {
    console.warn("Issue existence check error:", error);
    return false;
  }
}

async function draftBountyTask(issueTitle: string, issueBody: string, issueUrl: string) {
  const payload = {
    scout_id: SCOUT_AGENT_ID,
    ...buildDraftPayload(issueTitle, issueBody || "", issueUrl),
  };

  return postDraftWithRetry(payload);
}

async function processIssue(owner: string, repo: string, issue: any) {
  console.log(`Processing issue #${issue.number}: ${issue.title}`);
  const issueUrl = issue.html_url;

  if (!issueUrl) {
    console.log("Issue URL missing, skip.");
    return;
  }

  const alreadyDrafted = await isIssueAlreadyDrafted(issueUrl);
  if (alreadyDrafted) {
    console.log(`Issue already has draft task: ${issueUrl}, skipping.`);
    return;
  }

  // Check if we already commented
  if (GITHUB_TOKEN) {
    const comments = await octokit.issues.listComments({
      owner,
      repo,
      issue_number: issue.number,
    });

    const alreadyCommented = comments.data.some((c) => c.body?.includes("agent.wooo.work"));
    if (alreadyCommented) {
      console.log(`Already commented on #${issue.number}, skipping.`);
      return;
    }
  }

  // Generate draft task on VibeWork
  const draft = await draftBountyTask(issue.title, issue.body || "", issueUrl);

  if (!draft) {
    console.log(`Failed to generate draft for #${issue.number}, skipping comment.`);
    return;
  }

  const commentBody = `Hi there! 👋 我們注意到這個 issue 被標記為需要社群協助。

**VibeWork** 是一個開源的 AI Agent 協定，目前已經連結了全球大量的自主 AI Agent。這些 Agent 可以為您**免費 (FREE)** 嘗試解決這個問題。

任何人類開發者或是 AI Agent 都可以認領這個 issue，並透過我們的 MCP 伺服器自動提交 Pull Request：
\`\`\`bash
npx -y @agent-bounty/mcp-server --endpoint https://agent.wooo.work
\`\`\`

🔗 [點此追蹤 AI 的解題進度](${draft.checkout_url}) (Task ID: \`${draft.task_id}\`)

*(備註：我們是一個開源的防詐欺 Bounty 協定，旨在幫助開源專案維護者更快地利用 AI 解決問題。如果您不希望我們在此張貼此資訊，請隨時告知！)*
`;

  if (GITHUB_TOKEN) {
    const isAllowedToComment = SCOUT_COMMENT_REPOS.includes("*") || SCOUT_COMMENT_REPOS.includes(`${owner}/${repo}`.toLowerCase());
    
    if (!isAllowedToComment) {
      console.log(`[SILENT MODE] Successfully generated draft task for #${issue.number}, but skipping GitHub comment (repo not in SCOUT_COMMENT_REPOS).`);
      return;
    }

    try {
      await octokit.issues.createComment({
        owner,
        repo,
        issue_number: issue.number,
        body: commentBody,
      });
      console.log(`Successfully commented on #${issue.number}`);
      await wait(SCOUT_COMMENT_DELAY_SECONDS);
    } catch (error) {
      const backoffSeconds = getGitHubBackoffSeconds(error);
      if (backoffSeconds > 0) {
        console.warn(`GitHub API rate limited; sleeping ${backoffSeconds}s before continuing.`);
        await wait(backoffSeconds);
      }
      console.error(`Failed to comment on #${issue.number}:`, error);
    }
  } else {
    console.log(`[DRY RUN] Would have posted scout comment for #${issue.number}: ${draft.status}`);
  }
}

async function scanRepositories() {
  console.log("Starting GitHub scan...");

  for (const target of TARGET_REPOS) {
    try {
      const issues = await octokit.issues.listForRepo({
        owner: target.owner,
        repo: target.repo,
        state: "open",
        ...(SCOUT_ISSUE_LABELS.length > 0 ? { labels: SCOUT_ISSUE_LABELS.join(",") } : {}),
        per_page: SCOUT_PER_PAGE
      });

      console.log(`Found ${issues.data.length} open issues in ${target.owner}/${target.repo}`);

      const issueCandidates = issues.data.slice(0, SCOUT_MAX_ISSUES_PER_SCAN);
      for (const issue of issueCandidates) {
        if (!issue.pull_request) { // Ignore PRs
          await processIssue(target.owner, target.repo, issue);
        }
      }

      if (issues.data.length > issueCandidates.length) {
        console.log(`Limited scan to first ${issueCandidates.length} issues in ${target.owner}/${target.repo}`);
      }
    } catch (error) {
      const backoffSeconds = getGitHubBackoffSeconds(error);
      if (backoffSeconds > 0) {
        console.warn(`GitHub API rate limited on ${target.owner}/${target.repo}; sleeping ${backoffSeconds}s and skip remaining repos.`);
        await wait(backoffSeconds);
        return;
      }
      console.error(`Error scanning ${target.owner}/${target.repo}:`, error);
    }
  }
}

async function bootstrap() {
  if (!SCOUT_ENABLED) {
    console.log("SCOUT_ENABLED is false. Scout bot is disabled.");
    return;
  }

  // Delay first scan a bit so main web API is ready after startup.
  await wait(10);
  await scanRepositories();
}

bootstrap();

if (SCOUT_ENABLED) {
  // Default: every 1 minute for phase-1 high-velocity inbound traffic
  cron.schedule(SCOUT_CRON_EXPRESSION, () => {
    console.log(`Running scheduled GitHub scan (${SCOUT_CRON_EXPRESSION})...`);
    scanRepositories();
  });
} else {
  console.log("Cron schedule is disabled because SCOUT_ENABLED=false.");
}

console.log("VibeWork Scout Bot started and scheduled.");
