import { createDropboxSyncClient } from '../client'
import { createAuthMethods } from '../auth'
import { createSyncMethods } from '../sync'
import { createSocketMethods } from '../socket'

// First setup all the mocks before importing the actual code
let dropboxInstanceCreated = false

// Mock the auth module to return a mock auth object that requires client initialization
jest.mock('../auth', () => ({
    createAuthMethods: jest.fn().mockImplementation((getClient) => ({
        mockAuth: true,
        getAuthUrl: jest.fn().mockImplementation((redirectUri) => {
            // This will force the getClient() function to be called
            getClient()
            return Promise.resolve('https://dropbox.com/oauth')
        }),
    })),
}))

jest.mock('../sync', () => ({
    createSyncMethods: jest.fn().mockReturnValue({ mockSync: true }),
}))

jest.mock('../socket', () => ({
    createSocketMethods: jest.fn().mockReturnValue({ mockSocket: true }),
}))

// Mock Dropbox constructor and track constructor options
interface DropboxOptions {
    accessToken?: string
    clientId?: string
}

const mockDropboxOptions: DropboxOptions[] = []
const mockDropboxClient = {
    auth: {
        getAuthenticationUrl: jest.fn(),
        getAccessTokenFromCode: jest.fn(),
    },
    filesListFolder: jest.fn(),
    filesUpload: jest.fn(),
    filesDownload: jest.fn(),
}

// Mock the Dropbox class
jest.mock('dropbox', () => {
    return {
        Dropbox: jest.fn().mockImplementation((options: DropboxOptions) => {
            mockDropboxOptions.push(options)
            dropboxInstanceCreated = true
            return mockDropboxClient
        }),
    }
})

describe('createDropboxSyncClient', () => {
    beforeEach(() => {
        jest.clearAllMocks()
        mockDropboxOptions.length = 0
        dropboxInstanceCreated = false
    })

    it('should create a client with accessToken', async () => {
        const credentials = { clientId: 'test-id', accessToken: 'test-token' }
        const client = createDropboxSyncClient(credentials)

        expect(client).toHaveProperty('auth')
        expect(client).toHaveProperty('sync')
        expect(client).toHaveProperty('socket')

        // Force client instantiation by calling getAuthUrl which calls getClient internally
        await client.auth.getAuthUrl('https://example.com/callback')

        // Check that Dropbox was constructed with the correct options
        expect(dropboxInstanceCreated).toBe(true)
        expect(mockDropboxOptions.length).toBe(1)
        expect(mockDropboxOptions[0]).toEqual({ accessToken: 'test-token' })
        expect(createAuthMethods).toHaveBeenCalled()
        expect(createSyncMethods).toHaveBeenCalled()
        expect(createSocketMethods).toHaveBeenCalled()
    })

    it('should create a client with clientId when accessToken is not provided', async () => {
        const credentials = { clientId: 'test-id' }
        const client = createDropboxSyncClient(credentials)

        expect(client).toHaveProperty('auth')
        expect(client).toHaveProperty('sync')
        expect(client).toHaveProperty('socket')

        // Force client instantiation by calling getAuthUrl which calls getClient internally
        await client.auth.getAuthUrl('https://example.com/callback')

        // Check that Dropbox was constructed with the correct options
        expect(dropboxInstanceCreated).toBe(true)
        expect(mockDropboxOptions.length).toBe(1)
        expect(mockDropboxOptions[0]).toEqual({ clientId: 'test-id' })
        expect(createAuthMethods).toHaveBeenCalled()
        expect(createSyncMethods).toHaveBeenCalled()
        expect(createSocketMethods).toHaveBeenCalled()
    })

    it('should throw error if neither clientId nor accessToken is provided', async () => {
        // Mock the auth implementation for this test specifically
        ;(createAuthMethods as jest.Mock).mockImplementationOnce(
            (getClient) => ({
                getAuthUrl: jest.fn().mockImplementation(() => {
                    // This will throw when called with empty credentials
                    try {
                        getClient()
                    } catch (error) {
                        return Promise.reject(error)
                    }
                    return Promise.resolve('https://dropbox.com/oauth')
                }),
            })
        )

        const credentials = {} as any
        const client = createDropboxSyncClient(credentials)

        // This should throw when getClient() is called inside getAuthUrl
        await expect(
            client.auth.getAuthUrl('https://example.com/callback')
        ).rejects.toThrow('Either clientId or accessToken must be provided')
    })
})
