/**
 * execute.ts — Execution logic. Imports from ../lib/. NO validation.
 * Receives validated data, returns typed result.
 */
import type { StatusSpec, StatusResult, BranchInfo, Comparison, WorkingTreeStatus, Action } from './types.js';
import { execGit, detectInProgressOp } from '../lib/git.js';
import { listWorktrees } from '../lib/worktree.js';
import { readConfig, resolveConfigPath } from '../lib/config.js';
import { detectBranchType } from '../lib/branch.js';

export async function execute(spec: StatusSpec, workDir: string = process.cwd()): Promise<StatusResult> {
  try {
    // Get current branch
    const currentBranch = await getCurrentBranch(workDir);

    // Fetch all remotes
    await execGit(['fetch', '--all', '--quiet'], workDir);

    // Get all branches with tracking info
    const branches = await getAllBranches(workDir);

    // Get all tags
    const tags = await getAllTags(workDir);

    // Get working tree status
    const workingTree = await getWorkingTreeStatus(workDir);

    // Get worktrees
    const worktrees = await listWorktrees(workDir);

    // Get comparisons (main vs develop, develop vs current)
    let comparisons: Comparison[] = [];
    try {
      const config = await readConfig(await resolveConfigPath(workDir) ?? undefined);
      comparisons = await getComparisons(config.git.branches.main, config.git.branches.develop, currentBranch, workDir);
    } catch {
      // Config not found, skip comparisons
    }

    // Generate suggested actions
    const actions = generateActions(currentBranch, branches, workingTree, comparisons);

    return {
      success: true,
      currentBranch,
      branches,
      tags,
      comparisons: spec.verbose ? comparisons : undefined,
      workingTree,
      worktrees: spec.verbose ? worktrees : undefined,
      actions: spec.verbose ? actions : undefined,
    };
  } catch (err) {
    return {
      success: false,
      currentBranch: 'unknown',
      branches: [],
      tags: [],
      workingTree: {
        isDirty: false,
        isRebasing: false,
        isMerging: false,
        isCherryPicking: false,
        unstagedCount: 0,
        stagedCount: 0,
      },
      error: (err as Error).message,
    };
  }
}

async function getCurrentBranch(cwd: string): Promise<string> {
  const result = await execGit(['branch', '--show-current'], cwd);
  return result.stdout || 'HEAD';
}

async function getAllBranches(cwd: string): Promise<BranchInfo[]> {
  const result = await execGit(['branch', '-v', '--all', '--format=%(refname:short)|%(upstream:short)|%(committerdate:short)|%(authorname)'], cwd);
  if (result.exitCode !== 0) return [];

  const branches: BranchInfo[] = [];
  for (const line of result.stdout.split('\n')) {
    if (!line.trim()) continue;
    const parts = line.split('|');
    const name = parts[0].trim();
    const tracking = parts[1]?.trim();

    // Get ahead/behind
    const aheadBehind = await getAheadBehind(name, cwd);

    branches.push({
      name,
      type: detectBranchType(name),
      ahead: aheadBehind.ahead,
      behind: aheadBehind.behind,
      tracking: tracking || undefined,
      lastCommit: parts[2]?.trim(),
      lastAuthor: parts[3]?.trim(),
    });
  }

  return branches;
}

async function getAheadBehind(branch: string, cwd: string): Promise<{ ahead: number; behind: number }> {
  const trackingResult = await execGit(['rev-parse', '--abbrev-ref', `${branch}@{upstream}`], cwd);
  if (trackingResult.exitCode !== 0) return { ahead: 0, behind: 0 };

  const tracking = trackingResult.stdout;
  const aheadResult = await execGit(['rev-list', '--count', `${tracking}..${branch}`], cwd);
  const behindResult = await execGit(['rev-list', '--count', `${branch}..${tracking}`], cwd);

  return {
    ahead: aheadResult.exitCode === 0 ? parseInt(aheadResult.stdout, 10) || 0 : 0,
    behind: behindResult.exitCode === 0 ? parseInt(behindResult.stdout, 10) || 0 : 0,
  };
}

async function getAllTags(cwd: string): Promise<string[]> {
  const result = await execGit(['tag', '--list', '--sort=-version:refname'], cwd);
  if (result.exitCode !== 0) return [];
  return result.stdout.split('\n').filter((t) => t.trim());
}

async function getWorkingTreeStatus(cwd: string): Promise<WorkingTreeStatus> {
  const statusResult = await execGit(['status', '--porcelain'], cwd);
  const unstagedCount = statusResult.stdout.split('\n').filter((l) => l.match(/^ [MADRC]/)).length;
  const stagedCount = statusResult.stdout.split('\n').filter((l) => l.match(/^[MADRC]/)).length;

  // `git rev-parse --git-path X` always exits 0 (it only resolves the path,
  // existing or not), so the old exitCode check reported every op as in-progress.
  // Detect by actual state-file existence instead.
  const inProgress = await detectInProgressOp(cwd);

  return {
    isDirty: statusResult.exitCode === 0 && statusResult.stdout.length > 0,
    isRebasing: inProgress === 'rebase',
    isMerging: inProgress === 'merge',
    isCherryPicking: inProgress === 'cherry-pick',
    unstagedCount,
    stagedCount,
  };
}

async function getComparisons(mainBranch: string, developBranch: string, currentBranch: string, cwd: string): Promise<Comparison[]> {
  const comparisons: Comparison[] = [];

  // Main vs develop
  const mainVsDevelop = await getComparison(mainBranch, developBranch, cwd);
  if (mainVsDevelop) {
    comparisons.push({
      branches: [mainBranch, developBranch],
      fromBranch: mainBranch,
      toBranch: developBranch,
      aheadCount: mainVsDevelop.behind,
      behindCount: mainVsDevelop.ahead,
    });
  }

  // Develop vs current
  if (currentBranch !== developBranch && currentBranch !== mainBranch) {
    const developVsCurrent = await getComparison(developBranch, currentBranch, cwd);
    if (developVsCurrent) {
      comparisons.push({
        branches: [developBranch, currentBranch],
        fromBranch: developBranch,
        toBranch: currentBranch,
        aheadCount: developVsCurrent.behind,
        behindCount: developVsCurrent.ahead,
      });
    }
  }

  return comparisons;
}

async function getComparison(branchA: string, branchB: string, cwd: string): Promise<{ ahead: number; behind: number } | null> {
  const aheadResult = await execGit(['rev-list', '--count', `${branchB}..${branchA}`], cwd);
  const behindResult = await execGit(['rev-list', '--count', `${branchA}..${branchB}`], cwd);

  if (aheadResult.exitCode === 0 && behindResult.exitCode === 0) {
    return {
      ahead: parseInt(aheadResult.stdout, 10) || 0,
      behind: parseInt(behindResult.stdout, 10) || 0,
    };
  }

  return null;
}

function generateActions(currentBranch: string, branches: BranchInfo[], workingTree: WorkingTreeStatus, comparisons: Comparison[]): Action[] {
  const actions: Action[] = [];

  // Check for dirty working tree
  if (workingTree.isDirty) {
    actions.push({
      priority: 'high',
      action: 'Commit or stash changes',
      reason: 'Working tree has uncommitted changes',
    });
  }

  // Check for active rebase/merge/cherry-pick
  if (workingTree.isRebasing) {
    actions.push({
      priority: 'critical',
      action: 'Resolve or abort rebase',
      reason: 'Rebase in progress',
    });
  }

  if (workingTree.isMerging) {
    actions.push({
      priority: 'critical',
      action: 'Resolve or abort merge',
      reason: 'Merge in progress',
    });
  }

  if (workingTree.isCherryPicking) {
    actions.push({
      priority: 'critical',
      action: 'Resolve or abort cherry-pick',
      reason: 'Cherry-pick in progress',
    });
  }

  // Check for branches with upstream tracking issues
  for (const branch of branches) {
    if (branch.ahead > 0 && !branch.tracking) {
      actions.push({
        priority: 'medium',
        action: `Push branch: ${branch.name}`,
        reason: `Local branch is ahead by ${branch.ahead} commit(s) and has no upstream`,
      });
    }

    if (branch.behind > 0) {
      actions.push({
        priority: 'medium',
        action: `Update branch: ${branch.name}`,
        reason: `Remote branch is ahead by ${branch.behind} commit(s)`,
      });
    }
  }

  return actions.sort((a, b) => {
    const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
    return priorityOrder[a.priority] - priorityOrder[b.priority];
  });
}
