import { describe, it, expect, vi, beforeEach } from 'vitest';
import { SyncService } from '../../src/main/services/SyncService.js';

vi.mock('../../src/main/services/GitClient.js');
import { GitClient } from '../../src/main/services/GitClient.js';

describe('SyncService.sync — 양방향', () => {
  let svc: SyncService;
  let exportSvc: { export: ReturnType<typeof vi.fn> };
  let importSvc: { applySyncFromDir: ReturnType<typeof vi.fn> };
  let gitInstance: {
    isRepo: ReturnType<typeof vi.fn>;
    hasRemote: ReturnType<typeof vi.fn>;
    addAll: ReturnType<typeof vi.fn>;
    hasUncommittedChanges: ReturnType<typeof vi.fn>;
    commit: ReturnType<typeof vi.fn>;
    fetch: ReturnType<typeof vi.fn>;
    refExists: ReturnType<typeof vi.fn>;
    rebaseOnto: ReturnType<typeof vi.fn>;
    rebaseAbort: ReturnType<typeof vi.fn>;
    listConflicts: ReturnType<typeof vi.fn>;
    push: ReturnType<typeof vi.fn>;
    run: ReturnType<typeof vi.fn>;
  };

  beforeEach(() => {
    exportSvc = { export: vi.fn(async () => {}) };
    importSvc = { applySyncFromDir: vi.fn(async () => ({ changedCount: 0 })) };
    gitInstance = {
      isRepo: vi.fn(async () => true),
      hasRemote: vi.fn(async () => true),
      addAll: vi.fn(async () => {}),
      hasUncommittedChanges: vi.fn(async () => true),
      commit: vi.fn(async () => ({ changed: true, sha: 'abc' })),
      fetch: vi.fn(async () => ({ stdout: '', stderr: '', exitCode: 0 })),
      refExists: vi.fn(async () => true),
      rebaseOnto: vi.fn(async () => ({ stdout: '', stderr: '', exitCode: 0 })),
      rebaseAbort: vi.fn(async () => ({ stdout: '', stderr: '', exitCode: 0 })),
      listConflicts: vi.fn(async () => []),
      push: vi.fn(async () => {}),
      run: vi.fn(async () => ({ stdout: '', stderr: '', exitCode: 0 }))
    };
    (GitClient as unknown as ReturnType<typeof vi.fn>).mockImplementation(function () { return gitInstance; });
    svc = new SyncService(
      '/tmp/profile',
      exportSvc as unknown as never,
      importSvc as unknown as never
    );
  });

  it('happy path — 6단계 모두 호출, ok:true', async () => {
    const r = await svc.sync();
    expect(exportSvc.export).toHaveBeenCalled();
    expect(gitInstance.addAll).toHaveBeenCalled();
    expect(gitInstance.commit).toHaveBeenCalled();
    expect(gitInstance.fetch).toHaveBeenCalled();
    expect(gitInstance.rebaseOnto).toHaveBeenCalledWith('origin/main');
    expect(importSvc.applySyncFromDir).toHaveBeenCalled();
    expect(gitInstance.push).toHaveBeenCalled();
    expect(r.ok).toBe(true);
    expect(r.pushed).toBe(true);
  });

  it('local 변경 없음 → commit skip + 다음 단계 진행', async () => {
    gitInstance.hasUncommittedChanges.mockResolvedValueOnce(false);
    const r = await svc.sync();
    expect(gitInstance.commit).not.toHaveBeenCalled();
    expect(gitInstance.fetch).toHaveBeenCalled();
    expect(r.ok).toBe(true);
  });

  it('rebase 실패 → abort + reason=conflict + conflicts 포함 (path + localText/remoteText)', async () => {
    gitInstance.rebaseOnto.mockResolvedValueOnce({ stdout: '', stderr: 'CONFLICT', exitCode: 1 });
    gitInstance.listConflicts.mockResolvedValueOnce(['notes/aaa.md', 'notes/bbb.md']);
    // Cut E final review fix — runSync calls git.run(['show', ':2:path']) and ':3:path'
    // for each conflict. Mock returns ours/theirs text per call.
    gitInstance.run
      .mockResolvedValueOnce({ stdout: 'aaa local', stderr: '', exitCode: 0 })  // :2:notes/aaa.md
      .mockResolvedValueOnce({ stdout: 'aaa remote', stderr: '', exitCode: 0 }) // :3:notes/aaa.md
      .mockResolvedValueOnce({ stdout: 'bbb local', stderr: '', exitCode: 0 })  // :2:notes/bbb.md
      .mockResolvedValueOnce({ stdout: 'bbb remote', stderr: '', exitCode: 0 }); // :3:notes/bbb.md
    const r = await svc.sync();
    expect(gitInstance.rebaseAbort).toHaveBeenCalled();
    expect(r.ok).toBe(false);
    expect(r.reason).toBe('conflict');
    expect(r.conflicts).toEqual([
      { path: 'notes/aaa.md', localText: 'aaa local', remoteText: 'aaa remote' },
      { path: 'notes/bbb.md', localText: 'bbb local', remoteText: 'bbb remote' }
    ]);
    expect(gitInstance.push).not.toHaveBeenCalled();
  });

  it('fetch 실패 → reason 반환', async () => {
    gitInstance.fetch.mockResolvedValueOnce({ stdout: '', stderr: 'no network', exitCode: 1 });
    const r = await svc.sync();
    expect(r.ok).toBe(false);
    expect(r.reason).toContain('fetch failed');
    expect(gitInstance.rebaseOnto).not.toHaveBeenCalled();
  });

  it('not configured → ok:false + reason=not_configured', async () => {
    gitInstance.isRepo.mockResolvedValueOnce(false);
    const r = await svc.sync();
    expect(r.ok).toBe(false);
    expect(r.reason).toBe('not_configured');
  });
});
