/**
 * Tests for autoCommitAndPush
 */

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { autoCommitAndPush } from '../infra/task/autoCommit.js';

// Mock child_process.execFileSync
vi.mock('node:child_process', () => ({
  execFileSync: vi.fn(),
}));

import { execFileSync } from 'node:child_process';
const mockExecFileSync = vi.mocked(execFileSync);

beforeEach(() => {
  vi.clearAllMocks();
});

describe('autoCommitAndPush', () => {
  it('should create a commit and push when there are changes', () => {
    mockExecFileSync.mockImplementation((cmd, args) => {
      const argsArr = args as string[];
      if (argsArr[0] === 'status') {
        return 'M src/index.ts\n';
      }
      if (argsArr[0] === 'rev-parse') {
        return 'abc1234\n';
      }
      return Buffer.from('');
    });

    const result = autoCommitAndPush('/tmp/clone', 'my-task', '/project');

    expect(result.success).toBe(true);
    expect(result.commitHash).toBe('abc1234');
    expect(result.message).toContain('abc1234');

    // Verify git add -A was called
    expect(mockExecFileSync).toHaveBeenCalledWith(
      'git',
      ['add', '-A'],
      expect.objectContaining({ cwd: '/tmp/clone' })
    );

    // Verify commit was called with correct message (no co-author)
    expect(mockExecFileSync).toHaveBeenCalledWith(
      'git',
      ['commit', '-m', 'takt: my-task'],
      expect.objectContaining({ cwd: '/tmp/clone' })
    );

    // Verify push was called with projectDir directly (no origin remote)
    expect(mockExecFileSync).toHaveBeenCalledWith(
      'git',
      ['push', '/project', 'HEAD'],
      expect.objectContaining({ cwd: '/tmp/clone' })
    );
  });

  it('should return success with no commit when there are no changes', () => {
    mockExecFileSync.mockImplementation((cmd, args) => {
      const argsArr = args as string[];
      if (argsArr[0] === 'status') {
        return ''; // No changes
      }
      return Buffer.from('');
    });

    const result = autoCommitAndPush('/tmp/clone', 'my-task', '/project');

    expect(result.success).toBe(true);
    expect(result.commitHash).toBeUndefined();
    expect(result.message).toBe('No changes to commit');

    // Verify git add -A was called
    expect(mockExecFileSync).toHaveBeenCalledWith(
      'git',
      ['add', '-A'],
      expect.objectContaining({ cwd: '/tmp/clone' })
    );

    // Verify commit was NOT called
    expect(mockExecFileSync).not.toHaveBeenCalledWith(
      'git',
      ['commit', '-m', expect.any(String)],
      expect.anything()
    );

    // Verify push was NOT called
    expect(mockExecFileSync).not.toHaveBeenCalledWith(
      'git',
      ['push', '/project', 'HEAD'],
      expect.anything()
    );
  });

  it('should return failure when git command fails', () => {
    mockExecFileSync.mockImplementation(() => {
      throw new Error('git error: not a git repository');
    });

    const result = autoCommitAndPush('/tmp/clone', 'my-task', '/project');

    expect(result.success).toBe(false);
    expect(result.commitHash).toBeUndefined();
    expect(result.message).toContain('Auto-commit failed');
    expect(result.message).toContain('not a git repository');
  });

  it('should not include co-author in commit message', () => {
    mockExecFileSync.mockImplementation((cmd, args) => {
      const argsArr = args as string[];
      if (argsArr[0] === 'status') {
        return 'M file.ts\n';
      }
      if (argsArr[0] === 'rev-parse') {
        return 'def5678\n';
      }
      return Buffer.from('');
    });

    autoCommitAndPush('/tmp/clone', 'test-task', '/project');

    // Find the commit call
    const commitCall = mockExecFileSync.mock.calls.find(
      call => (call[1] as string[])[0] === 'commit'
    );

    expect(commitCall).toBeDefined();
    const commitMessage = (commitCall![1] as string[])[2];
    expect(commitMessage).toBe('takt: test-task');
    expect(commitMessage).not.toContain('Co-Authored-By');
  });

  it('should use the correct commit message format', () => {
    mockExecFileSync.mockImplementation((cmd, args) => {
      const argsArr = args as string[];
      if (argsArr[0] === 'status') {
        return 'A new-file.ts\n';
      }
      if (argsArr[0] === 'rev-parse') {
        return 'aaa1111\n';
      }
      return Buffer.from('');
    });

    autoCommitAndPush('/tmp/clone', '認証機能を追加する', '/project');

    const commitCall = mockExecFileSync.mock.calls.find(
      call => (call[1] as string[])[0] === 'commit'
    );
    expect((commitCall![1] as string[])[2]).toBe('takt: 認証機能を追加する');
  });

});
