import { readConfig } from '../config/store';
import { cleanPrompt } from '../lib/clean-prompt';
import { detectProject } from '../device/project';
import { submitPrompt, ApiError } from '../api/client';
import { findTranscriptFiles, extractPromptsFromFile, HistoricalPrompt } from '../backfill/scan';

function sleep(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

export async function backfill(days: number, dryRun: boolean): Promise<void> {
  const config = readConfig();

  if (!config) {
    console.error('Not logged in. Run `prompt-agent login` first.');
    process.exitCode = 1;

    return;
  }

  const sinceIso = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
  const files = findTranscriptFiles();

  console.log(`Scanning ${files.length} Claude Code session file(s) for prompts since ${sinceIso.slice(0, 10)}...`);

  const prompts: HistoricalPrompt[] = files
    .flatMap((file) => {
      try {
        return extractPromptsFromFile(file, sinceIso);
      } catch {
        return [];
      }
    })
    .sort((a, b) => a.submittedAt.localeCompare(b.submittedAt));

  console.log(`Found ${prompts.length} prompt(s) across the last ${days} day(s).`);

  if (dryRun) {
    let previewSkipped = 0;

    for (const p of prompts) {
      const cleaned = cleanPrompt(p.text);

      if (!cleaned) {
        previewSkipped++;
        continue;
      }

      const preview = cleaned.slice(0, 80).replace(/\s+/g, ' ');
      console.log(`  [${p.submittedAt}] (${p.sessionId.slice(0, 8)}) ${preview}`);
    }

    console.log(`(${previewSkipped} would be skipped - empty after cleaning injected context.)`);
    console.log('Dry run - nothing uploaded. Re-run without --dry-run to submit these.');

    return;
  }

  let uploaded = 0;
  let skipped = 0;
  let failed = 0;

  for (const p of prompts) {
    const cleaned = cleanPrompt(p.text);

    if (!cleaned) {
      skipped++;
      continue;
    }

    const project = detectProject(p.cwd);

    const outcome = await submitOne(config, p, cleaned, project);

    if (outcome === 'uploaded') {
      uploaded++;
    } else if (outcome === 'stop') {
      break;
    } else {
      failed++;
    }

    // Stay comfortably under the API's rate limit on a large backfill.
    await sleep(60);
  }

  console.log(`Done. Uploaded ${uploaded}, skipped ${skipped} (empty after cleaning), failed ${failed}.`);
}

async function submitOne(
  config: NonNullable<ReturnType<typeof readConfig>>,
  p: HistoricalPrompt,
  cleaned: string,
  project: ReturnType<typeof detectProject>,
  attempt = 1,
): Promise<'uploaded' | 'failed' | 'stop'> {
  try {
    await submitPrompt(config.apiBaseUrl, config.deviceToken, {
      promptId: `backfill-${p.uuid}`,
      sessionId: p.sessionId,
      prompt: cleaned,
      submittedAt: p.submittedAt,
      projectName: project.name,
      projectPathHash: project.pathHash,
      projectDetectionSource: project.detectionSource,
      clientVersion: config.agentVersion,
    });

    return 'uploaded';
  } catch (error) {
    if (error instanceof ApiError && (error.status === 401 || error.status === 403)) {
      console.error('Device token was rejected. Run `prompt-agent login` again.');

      return 'stop';
    }

    if (error instanceof ApiError && error.status === 429 && attempt < 3) {
      await sleep(2000 * attempt);

      return submitOne(config, p, cleaned, project, attempt + 1);
    }

    return 'failed';
  }
}
