/**
 * nexus-agents sprint command
 *
 * CLI command for automated sprint planning.
 *
 * (Source: Issue #230, Epic #225)
 */

import { getTimeProvider } from '../core/index.js';
import { safeExecSandboxed } from './sandbox-exec.js';
import type {
  SprintCommandOptions,
  SprintPlanResult,
  SprintProposal,
  SprintIssue,
} from './sprint-types.js';
import { voteCommand } from './vote-command.js';
import {
  colors,
  symbols,
  writeLine,
  fetchOpenIssues,
  parseIssue,
  isNotEpic,
  categorizeByPriority,
  printSprintResult,
} from './sprint-helpers.js';

// Re-export helpers for backward compatibility
export {
  fetchOpenIssues,
  extractPriority,
  extractIssueType,
  extractEstimatedEffort,
  parseIssue,
  isNotEpic,
  categorizeByPriority,
  printProposal,
  printSprintResult,
} from './sprint-helpers.js';

// ============================================================================
// Proposal Generation
// ============================================================================

/**
 * Generate sprint title from date.
 */
export function generateSprintTitle(duration: string): string {
  const now = new Date(getTimeProvider().now());
  const dateStr = now.toLocaleDateString('en-US', {
    timeZone: 'America/New_York',
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
  });
  return `sprint: ${dateStr} (${duration})`;
}

/**
 * Format issue for proposal body.
 */
function formatIssueForBody(issue: SprintIssue): string {
  const effort = issue.estimatedEffort !== undefined ? ` (${issue.estimatedEffort})` : '';
  return `- [ ] #${String(issue.number)} - ${issue.title}${effort}`;
}

/**
 * Generate sprint proposal body.
 */
export function generateProposalBody(
  title: string,
  goals: readonly string[],
  p1: readonly SprintIssue[],
  p2: readonly SprintIssue[],
  p3: readonly SprintIssue[]
): string {
  const lines: string[] = [];

  lines.push('## Goals');
  lines.push('');
  for (const goal of goals) {
    lines.push(`- ${goal}`);
  }
  lines.push('');

  if (p1.length > 0) {
    lines.push('## P1 (Critical)');
    lines.push('');
    for (const issue of p1) {
      lines.push(formatIssueForBody(issue));
    }
    lines.push('');
  }

  if (p2.length > 0) {
    lines.push('## P2 (High Priority)');
    lines.push('');
    for (const issue of p2) {
      lines.push(formatIssueForBody(issue));
    }
    lines.push('');
  }

  if (p3.length > 0) {
    lines.push('## P3 (Nice to Have)');
    lines.push('');
    for (const issue of p3) {
      lines.push(formatIssueForBody(issue));
    }
    lines.push('');
  }

  lines.push('---');
  lines.push('');
  lines.push('*Generated by nexus-agents sprint plan*');

  return lines.join('\n');
}

/**
 * Generate goals from issues.
 */
export function generateGoals(
  p1: readonly SprintIssue[],
  p2: readonly SprintIssue[]
): readonly string[] {
  const goals: string[] = [];

  // Derive goals from P1 issues
  const featCount = [...p1, ...p2].filter((i) => i.type === 'feat').length;
  const bugCount = [...p1, ...p2].filter((i) => i.type === 'bug').length;

  if (featCount > 0) {
    goals.push(`Implement ${String(featCount)} new feature${featCount > 1 ? 's' : ''}`);
  }
  if (bugCount > 0) {
    goals.push(`Fix ${String(bugCount)} bug${bugCount > 1 ? 's' : ''}`);
  }
  if (goals.length === 0) {
    goals.push('Complete prioritized backlog items');
  }

  return goals;
}

/**
 * Generate sprint proposal from issues.
 */
export function generateProposal(
  issues: readonly SprintIssue[],
  options: SprintCommandOptions
): SprintProposal {
  const { p1, p2, p3, p4 } = categorizeByPriority(issues);

  const maxPer = options.maxPerPriority ?? 5;
  const duration = options.duration ?? '1 week';

  const selectedP1 = p1.slice(0, maxPer);
  const selectedP2 = p2.slice(0, maxPer);
  const selectedP3 = p3.slice(0, maxPer);
  const selectedP4 = p4.slice(0, maxPer);

  const title = generateSprintTitle(duration);
  const goals = generateGoals(selectedP1, selectedP2);
  const body = generateProposalBody(title, goals, selectedP1, selectedP2, selectedP3);

  return {
    title,
    goals,
    p1Issues: selectedP1,
    p2Issues: selectedP2,
    p3Issues: selectedP3,
    p4Issues: selectedP4,
    body,
  };
}

// ============================================================================
// Issue Creation
// ============================================================================

/**
 * Create sprint issue on GitHub.
 */
export function createSprintIssue(proposal: SprintProposal): number | null {
  try {
    const escapedTitle = proposal.title.replace(/'/g, "'\\''");
    const escapedBody = proposal.body.replace(/'/g, "'\\''");

    const output = safeExecSandboxed(
      `gh issue create --title '${escapedTitle}' --body '${escapedBody}' --label 'epic'`,
      { context: 'gh' }
    );

    if (output === null) {
      return null;
    }

    const match = /\/issues\/(\d+)/.exec(output);
    const issueNumStr = match?.[1];
    if (issueNumStr !== undefined && issueNumStr !== '') {
      return parseInt(issueNumStr, 10);
    }
    return null;
  } catch {
    return null;
  }
}

// ============================================================================
// Subcommand Handlers
// ============================================================================

/**
 * Handle the plan subcommand.
 */
async function handlePlanSubcommand(options: SprintCommandOptions): Promise<SprintPlanResult> {
  writeLine(`${colors.dim}Fetching open issues...${colors.reset}`);
  const rawIssues = fetchOpenIssues();

  if (rawIssues.length === 0) {
    return { success: false, error: 'No open issues found or unable to access GitHub' };
  }

  const issues = rawIssues.map(parseIssue).filter(isNotEpic);
  writeLine(`${colors.dim}Found ${String(issues.length)} non-epic issues${colors.reset}\n`);

  const proposal = generateProposal(issues, options);

  // Run vote if requested
  let voteOutcome: SprintPlanResult['voteOutcome'] = 'skipped';
  if (options.vote === true) {
    const voteProposal = `Sprint Plan: ${proposal.title}\n\nGoals: ${proposal.goals.join(', ')}\n\nP1: ${String(proposal.p1Issues.length)} items, P2: ${String(proposal.p2Issues.length)} items`;

    writeLine(`${colors.dim}Running consensus vote...${colors.reset}\n`);
    const exitCode = await voteCommand({
      proposal: voteProposal,
      threshold: 'supermajority',
      dryRun: options.dryRun === true,
    });

    voteOutcome = exitCode === 0 ? 'approved' : 'rejected';
  }

  // Create issue if requested and vote passed (or no vote)
  let createdIssueNumber: number | null = null;
  if (options.createIssue === true && (voteOutcome === 'approved' || voteOutcome === 'skipped')) {
    if (options.dryRun === true) {
      writeLine(`${colors.yellow}[DRY RUN]${colors.reset} Would create sprint issue\n`);
    } else {
      writeLine(`${colors.dim}Creating sprint issue...${colors.reset}`);
      createdIssueNumber = createSprintIssue(proposal);
    }
  }

  // Build result with only defined optional properties
  const result: SprintPlanResult = {
    success: true,
    proposal,
    voteOutcome,
  };

  if (createdIssueNumber !== null) {
    return { ...result, createdIssueNumber };
  }
  return result;
}

/**
 * Handle the list subcommand - show prioritized backlog.
 */
function handleListSubcommand(): SprintPlanResult {
  writeLine(`${colors.dim}Fetching open issues...${colors.reset}`);
  const rawIssues = fetchOpenIssues();

  if (rawIssues.length === 0) {
    return { success: false, error: 'No open issues found or unable to access GitHub' };
  }

  const issues = rawIssues.map(parseIssue).filter(isNotEpic);
  const { p1, p2, p3, p4, unassigned } = categorizeByPriority(issues);

  writeLine(`\n${colors.bold}Prioritized Backlog${colors.reset}`);
  writeLine('='.repeat(40));

  const printCategory = (label: string, color: string, items: readonly SprintIssue[]): void => {
    if (items.length === 0) return;
    writeLine(`\n${color}${label} (${String(items.length)}):${colors.reset}`);
    for (const issue of items) {
      const effort =
        issue.estimatedEffort !== undefined
          ? ` ${colors.dim}(${issue.estimatedEffort})${colors.reset}`
          : '';
      writeLine(`  ${symbols.bullet} #${String(issue.number)} - ${issue.title}${effort}`);
    }
  };

  printCategory('P1 Critical', colors.red, p1);
  printCategory('P2 High Priority', colors.yellow, p2);
  printCategory('P3 Normal', colors.cyan, p3);
  printCategory('P4 Low Priority', colors.dim, p4);
  printCategory('Unassigned', colors.dim, unassigned);

  writeLine('');

  return { success: true };
}

// ============================================================================
// Main Command
// ============================================================================

/**
 * Run the sprint command.
 */
export async function sprintCommand(options: SprintCommandOptions): Promise<number> {
  writeLine(`\n${colors.bold}Nexus Agents Sprint Planning${colors.reset}`);
  writeLine('=============================\n');

  let result: SprintPlanResult;

  switch (options.subcommand) {
    case 'plan':
      result = await handlePlanSubcommand(options);
      break;
    case 'list':
      result = handleListSubcommand();
      break;
    default:
      writeLine(`${colors.red}Unknown subcommand${colors.reset}`);
      return 1;
  }

  printSprintResult(result, options.format ?? 'text');

  return result.success ? 0 : 1;
}

// ============================================================================
// Exports
// ============================================================================

export type { SprintCommandOptions, SprintPlanResult, SprintProposal } from './sprint-types.js';
