import { describe, it, expect, vi, beforeEach } from 'vitest'
import { ProjectSyncManager, type SyncResult } from './remote-sync.js'
import type { ProjectRepository } from '../db/repositories/project-repository.js'

vi.mock('simple-git', () => ({
  simpleGit: vi.fn(),
}))

vi.mock('./project-clones.js', () => ({
  ensureProjectClone: vi.fn().mockResolvedValue('/tmp/fake-clone'),
}))

vi.mock('../logger/index.js', () => ({
  createModuleLogger: () => ({
    info: vi.fn(),
    warn: vi.fn(),
    error: vi.fn(),
  }),
}))

function makeRepo(overrides: Partial<ProjectRepository> = {}): ProjectRepository {
  return {
    findAll: vi.fn().mockResolvedValue([]),
    findById: vi.fn().mockResolvedValue(null),
    create: vi.fn(),
    update: vi.fn().mockResolvedValue({}),
    delete: vi.fn(),
    findProjectsByInitiativeId: vi.fn().mockResolvedValue([]),
    setInitiativeProjects: vi.fn().mockResolvedValue(undefined),
    ...overrides,
  } as unknown as ProjectRepository
}

const project1 = {
  id: 'proj-1',
  name: 'alpha',
  url: 'https://github.com/org/alpha',
  defaultBranch: 'main',
  lastFetchedAt: null,
  createdAt: new Date(),
  updatedAt: new Date(),
}

const project2 = {
  id: 'proj-2',
  name: 'beta',
  url: 'https://github.com/org/beta',
  defaultBranch: 'main',
  lastFetchedAt: null,
  createdAt: new Date(),
  updatedAt: new Date(),
}

describe('ProjectSyncManager', () => {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  let simpleGitMock: any

  beforeEach(async () => {
    const mod = await import('simple-git')
    simpleGitMock = vi.mocked(mod.simpleGit)
    simpleGitMock.mockReset()
  })

  describe('syncAllProjects', () => {
    it('returns empty array when no projects exist', async () => {
      const repo = makeRepo({ findAll: vi.fn().mockResolvedValue([]) })
      const manager = new ProjectSyncManager(repo, '/workspace')
      const results = await manager.syncAllProjects()
      expect(results).toEqual([])
    })

    it('returns success result for each project when all succeed', async () => {
      const mockGit = {
        fetch: vi.fn().mockResolvedValue({}),
        raw: vi.fn().mockResolvedValue(''),
      }
      simpleGitMock.mockReturnValue(mockGit)

      const repo = makeRepo({
        findAll: vi.fn().mockResolvedValue([project1, project2]),
        findById: vi.fn()
          .mockResolvedValueOnce(project1)
          .mockResolvedValueOnce(project2),
        update: vi.fn().mockResolvedValue({}),
      })
      const manager = new ProjectSyncManager(repo, '/workspace')
      const results = await manager.syncAllProjects()

      expect(results).toHaveLength(2)
      expect(results[0]).toMatchObject({
        projectId: 'proj-1',
        projectName: 'alpha',
        success: true,
        fetched: true,
      })
      expect(results[1]).toMatchObject({
        projectId: 'proj-2',
        projectName: 'beta',
        success: true,
        fetched: true,
      })
    })

    it('returns partial failure when the second project fetch throws', async () => {
      const mockGitSuccess = {
        fetch: vi.fn().mockResolvedValue({}),
        raw: vi.fn().mockResolvedValue(''),
      }
      const mockGitFail = {
        fetch: vi.fn().mockRejectedValue(new Error('network error')),
        raw: vi.fn().mockResolvedValue(''),
      }
      simpleGitMock
        .mockReturnValueOnce(mockGitSuccess)
        .mockReturnValueOnce(mockGitFail)

      const repo = makeRepo({
        findAll: vi.fn().mockResolvedValue([project1, project2]),
        findById: vi.fn()
          .mockResolvedValueOnce(project1)
          .mockResolvedValueOnce(project2),
        update: vi.fn().mockResolvedValue({}),
      })
      const manager = new ProjectSyncManager(repo, '/workspace')
      const results = await manager.syncAllProjects()

      expect(results).toHaveLength(2)
      expect(results[0]).toMatchObject({ projectId: 'proj-1', success: true })
      expect(results[1]).toMatchObject({
        projectId: 'proj-2',
        success: false,
        error: expect.any(String),
      })
    })
  })

  describe('SyncResult shape', () => {
    it('result always contains projectId and success fields', async () => {
      const mockGit = {
        fetch: vi.fn().mockResolvedValue({}),
        raw: vi.fn().mockResolvedValue(''),
      }
      simpleGitMock.mockReturnValue(mockGit)

      const repo = makeRepo({
        findAll: vi.fn().mockResolvedValue([project1]),
        findById: vi.fn().mockResolvedValue(project1),
        update: vi.fn().mockResolvedValue({}),
      })
      const manager = new ProjectSyncManager(repo, '/workspace')
      const results = await manager.syncAllProjects()

      expect(results[0]).toMatchObject({
        projectId: expect.any(String),
        success: expect.any(Boolean),
      })
    })
  })

  describe('failure counting logic', () => {
    it('counts failures from SyncResult array', () => {
      const results: Pick<SyncResult, 'success'>[] = [
        { success: true },
        { success: false },
        { success: true },
        { success: false },
      ]
      const failed = results.filter(r => !r.success)
      expect(failed.length).toBe(2)
    })
  })
})
