/**
 * SimpleGit BranchManager Adapter
 *
 * Implementation of BranchManager port using simple-git.
 * Performs branch-level operations (create, merge, diff, delete)
 * on project clones without requiring a worktree.
 */

import { join } from 'node:path';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { simpleGit } from 'simple-git';
import type { BranchManager } from './branch-manager.js';
import type { MergeResult, BranchCommit } from './types.js';
import { createModuleLogger } from '../logger/index.js';

const log = createModuleLogger('branch-manager');

export class SimpleGitBranchManager implements BranchManager {
  async ensureBranch(repoPath: string, branch: string, baseBranch: string): Promise<void> {
    const git = simpleGit(repoPath);

    const exists = await this.branchExists(repoPath, branch);
    if (exists) {
      log.debug({ repoPath, branch }, 'branch already exists');
      return;
    }

    await git.branch([branch, baseBranch]);
    log.info({ repoPath, branch, baseBranch }, 'branch created');
  }

  async mergeBranch(repoPath: string, sourceBranch: string, targetBranch: string): Promise<MergeResult> {
    // Use an ephemeral worktree for merge safety
    const tmpPath = mkdtempSync(join(tmpdir(), 'cw-merge-'));
    const repoGit = simpleGit(repoPath);

    try {
      // Create ephemeral worktree on target branch
      await repoGit.raw(['worktree', 'add', tmpPath, targetBranch]);

      const wtGit = simpleGit(tmpPath);

      try {
        await wtGit.merge([sourceBranch, '--no-edit']);

        log.info({ repoPath, sourceBranch, targetBranch }, 'merge completed cleanly');
        return { success: true, message: `Merged ${sourceBranch} into ${targetBranch}` };
      } catch (mergeErr) {
        // Check for merge conflicts
        const status = await wtGit.status();
        const conflicts = status.conflicted;

        if (conflicts.length > 0) {
          log.warn({ repoPath, sourceBranch, targetBranch, conflicts }, 'merge conflicts detected');
          await wtGit.merge(['--abort']);
          return {
            success: false,
            conflicts,
            message: `Merge conflicts in ${conflicts.length} file(s)`,
          };
        }

        // Non-conflict merge failure
        throw mergeErr;
      }
    } finally {
      // Clean up ephemeral worktree
      try {
        await repoGit.raw(['worktree', 'remove', tmpPath, '--force']);
      } catch {
        // Best-effort cleanup — force-remove the directory if worktree remove fails
        try { rmSync(tmpPath, { recursive: true, force: true }); } catch { /* ignore */ }
        try { await repoGit.raw(['worktree', 'prune']); } catch { /* ignore */ }
      }
    }
  }

  async diffBranches(repoPath: string, baseBranch: string, headBranch: string): Promise<string> {
    const git = simpleGit(repoPath);
    const diff = await git.diff([`${baseBranch}...${headBranch}`]);
    return diff;
  }

  async deleteBranch(repoPath: string, branch: string): Promise<void> {
    const git = simpleGit(repoPath);
    const exists = await this.branchExists(repoPath, branch);
    if (!exists) return;

    try {
      await git.deleteLocalBranch(branch, true);
      log.info({ repoPath, branch }, 'branch deleted');
    } catch (err) {
      log.warn({ repoPath, branch, err: err instanceof Error ? err.message : String(err) }, 'failed to delete branch');
    }
  }

  async branchExists(repoPath: string, branch: string): Promise<boolean> {
    const git = simpleGit(repoPath);
    try {
      const branches = await git.branchLocal();
      return branches.all.includes(branch);
    } catch {
      return false;
    }
  }

  async remoteBranchExists(repoPath: string, branch: string): Promise<boolean> {
    try {
      const git = simpleGit(repoPath);
      const result = await git.branch(['-r']);
      return result.all.some(
        (ref) => ref === `origin/${branch}` || ref === `remotes/origin/${branch}`,
      );
    } catch {
      return false;
    }
  }

  async listCommits(repoPath: string, baseBranch: string, headBranch: string): Promise<BranchCommit[]> {
    const git = simpleGit(repoPath);
    const logResult = await git.log({ from: baseBranch, to: headBranch, '--stat': null });

    return logResult.all.map((entry) => {
      const diffStat = entry.diff;
      return {
        hash: entry.hash,
        shortHash: entry.hash.slice(0, 7),
        message: entry.message,
        author: entry.author_name,
        date: entry.date,
        filesChanged: diffStat?.files?.length ?? 0,
        insertions: diffStat?.insertions ?? 0,
        deletions: diffStat?.deletions ?? 0,
      };
    });
  }

  async diffCommit(repoPath: string, commitHash: string): Promise<string> {
    const git = simpleGit(repoPath);
    return git.diff([`${commitHash}~1`, commitHash]);
  }

  async getMergeBase(repoPath: string, branch1: string, branch2: string): Promise<string> {
    const git = simpleGit(repoPath);
    const result = await git.raw(['merge-base', branch1, branch2]);
    return result.trim();
  }

  async pushBranch(repoPath: string, branch: string, remote = 'origin'): Promise<void> {
    const git = simpleGit(repoPath);
    await git.push(remote, branch);
    log.info({ repoPath, branch, remote }, 'branch pushed to remote');
  }
}
