// Mock Angular modules before importing the code under test
jest.mock('@angular/core', () => ({
    Injectable: () => jest.fn(),
}))

jest.mock('@angular/common/http', () => ({
    HttpClient: jest.fn(),
    HttpHeaders: jest.fn(),
}))

// Now import the code that uses Angular modules
import { DropboxSyncService, getCredentialsFromEnvironment } from '../angular'
import { createDropboxSyncClient } from '../../core/client'
import { of } from 'rxjs'
import { Observable } from 'rxjs'

// Mock the core client
jest.mock('../../core/client')

describe('Angular adapter', () => {
    let service: DropboxSyncService
    const mockHttpClient = {
        get: jest.fn(),
        post: jest.fn(),
    }

    beforeEach(() => {
        jest.clearAllMocks()
        service = new DropboxSyncService(mockHttpClient as any)
    })

    describe('DropboxSyncService', () => {
        it('should be created', () => {
            expect(service).toBeTruthy()
        })

        it('should initialize with credentials', () => {
            const mockCredentials = {
                clientId: 'test-client-id',
                clientSecret: 'test-client-secret',
                accessToken: 'test-access-token',
                refreshToken: 'test-refresh-token',
            }

            const mockClient = { mock: 'client' }
            ;(createDropboxSyncClient as jest.Mock).mockReturnValue(mockClient)

            const result = service.initialize(mockCredentials)

            expect(createDropboxSyncClient).toHaveBeenCalledWith(
                mockCredentials
            )
            expect(result).toEqual(mockClient)
        })

        it('should get client after initialization', () => {
            const mockCredentials = { clientId: 'test-id' }
            const mockClient = { mock: 'client' }
            ;(createDropboxSyncClient as jest.Mock).mockReturnValue(mockClient)

            service.initialize(mockCredentials)
            const client = service.getClient()

            expect(client).toEqual(mockClient)
        })

        it('should throw error if getClient called before initialization', () => {
            expect(() => service.getClient()).toThrow(
                'DropboxSyncService not initialized. Call initialize() first.'
            )
        })

        it('should check connection status via HTTP', () => {
            mockHttpClient.get.mockReturnValue(of({ connected: true }))

            service.checkConnection().subscribe((result) => {
                expect(result).toBe(true)
            })

            expect(mockHttpClient.get).toHaveBeenCalledWith(
                '/api/dropbox/status'
            )
        })

        it('should handle connection error', () => {
            mockHttpClient.get.mockReturnValue(of(null)) // Simulate error

            service.checkConnection().subscribe((result) => {
                expect(result).toBe(false)
            })
        })

        it('should disconnect via HTTP', () => {
            mockHttpClient.post.mockReturnValue(of({ success: true }))

            service.disconnectDropbox().subscribe((result) => {
                expect(result).toBe(true)
            })

            expect(mockHttpClient.post).toHaveBeenCalledWith(
                '/api/dropbox/logout',
                {}
            )
        })

        it('should start sync process', () => {
            const mockOptions = { localDir: '/test-dir' }
            const mockResult = { uploaded: [], downloaded: [] }

            const mockClient = {
                sync: {
                    syncFiles: jest.fn().mockResolvedValue(mockResult),
                },
                socket: {
                    connect: jest.fn(),
                },
            }

            ;(createDropboxSyncClient as jest.Mock).mockReturnValue(mockClient)
            service.initialize({ clientId: 'test-id' })

            // Mock setupSocketListeners to return an observable
            service.setupSocketListeners = jest.fn().mockReturnValue(of({}))

            service.startSync(mockOptions).subscribe((result) => {
                expect(result).toEqual(mockResult)
            })

            expect(mockClient.socket.connect).toHaveBeenCalled()
            expect(mockClient.sync.syncFiles).toHaveBeenCalledWith(mockOptions)
            expect(service.setupSocketListeners).toHaveBeenCalled()
        })

        it('should cancel sync', () => {
            const mockClient = {
                sync: {
                    cancelSync: jest.fn(),
                },
            }

            ;(createDropboxSyncClient as jest.Mock).mockReturnValue(mockClient)
            service.initialize({ clientId: 'test-id' })

            service.cancelSync()

            expect(mockClient.sync.cancelSync).toHaveBeenCalled()
        })

        // Mock the implementation of setupSocketListeners for testing
        it('should setup socket listeners', () => {
            const mockSocket = {
                on: jest.fn(),
                off: jest.fn(),
            }

            const mockClient = {
                socket: mockSocket,
            }

            ;(createDropboxSyncClient as jest.Mock).mockReturnValue(mockClient)
            service.initialize({ clientId: 'test-id' })

            const observable = service.setupSocketListeners()

            // Should return an Observable
            expect(observable).toBeInstanceOf(Observable)

            // Get the subscriber function that was used to create the Observable
            const observer = {
                next: jest.fn(),
                error: jest.fn(),
                complete: jest.fn(),
            }
            const subscription = observable.subscribe(observer)

            // Since we have direct access to the socket mock, we can verify its behavior
            expect(mockSocket.on).toHaveBeenCalledWith(
                'sync:progress',
                expect.any(Function)
            )
            expect(mockSocket.on).toHaveBeenCalledWith(
                'sync:queue',
                expect.any(Function)
            )
            expect(mockSocket.on).toHaveBeenCalledWith(
                'sync:complete',
                expect.any(Function)
            )
            expect(mockSocket.on).toHaveBeenCalledWith(
                'sync:error',
                expect.any(Function)
            )

            // Clean up the subscription
            subscription.unsubscribe()
        })

        it('should handle OAuth callback', () => {
            const mockCode = 'test-auth-code'
            const mockTokens = {
                accessToken: 'new-access-token',
                refreshToken: 'new-refresh-token',
            }

            const mockClient = {
                auth: {
                    exchangeCodeForToken: jest
                        .fn()
                        .mockResolvedValue(mockTokens),
                },
            }

            ;(createDropboxSyncClient as jest.Mock).mockReturnValue(mockClient)
            service.initialize({ clientId: 'test-id' })

            // Create a proper localStorage mock
            const mockLocalStorage = {
                setItem: jest.fn(),
                getItem: jest.fn(),
                removeItem: jest.fn(),
            }

            Object.defineProperty(global, 'localStorage', {
                value: mockLocalStorage,
                writable: true,
            })

            // Mock window.location.origin
            Object.defineProperty(window, 'location', {
                value: {
                    origin: 'http://localhost',
                },
                writable: true,
            })

            // Subscribe to the observable to trigger the code
            let receivedResult: any
            service.handleOAuthCallback(mockCode).subscribe((result) => {
                receivedResult = result
            })

            // Verify the exchangeCodeForToken was called with the expected args
            expect(mockClient.auth.exchangeCodeForToken).toHaveBeenCalledWith(
                mockCode,
                'http://localhost/api/dropbox/auth/callback'
            )

            // Create and resolve the promise to trigger the localStorage set calls
            const resolvePromise = Promise.resolve(mockTokens)

            // Return a promise that will resolve after our mocked promise
            return resolvePromise.then(() => {
                // Now we can check localStorage was called
                expect(mockLocalStorage.setItem).toHaveBeenCalledWith(
                    'dropbox_access_token',
                    mockTokens.accessToken
                )

                expect(mockLocalStorage.setItem).toHaveBeenCalledWith(
                    'dropbox_refresh_token',
                    mockTokens.refreshToken
                )

                expect(mockLocalStorage.setItem).toHaveBeenCalledWith(
                    'dropbox_connected',
                    'true'
                )

                // Verify we received the tokens
                expect(receivedResult).toEqual(mockTokens)
            })
        })
    })

    describe('getCredentialsFromEnvironment', () => {
        it('should extract credentials from environment', () => {
            const mockEnvironment = {
                dropboxAppKey: 'env-app-key',
                dropboxAppSecret: 'env-app-secret',
            }

            // Mock localStorage
            const mockLocalStorage = {
                getItem: jest.fn().mockImplementation((key) => {
                    if (key === 'dropbox_access_token')
                        return 'storage-access-token'
                    if (key === 'dropbox_refresh_token')
                        return 'storage-refresh-token'
                    return null
                }),
            }

            Object.defineProperty(global, 'localStorage', {
                value: mockLocalStorage,
                writable: true,
            })

            const credentials = getCredentialsFromEnvironment(mockEnvironment)

            expect(credentials).toEqual({
                clientId: 'env-app-key',
                clientSecret: 'env-app-secret',
                accessToken: 'storage-access-token',
                refreshToken: 'storage-refresh-token',
            })

            expect(mockLocalStorage.getItem).toHaveBeenCalledWith(
                'dropbox_access_token'
            )
            expect(mockLocalStorage.getItem).toHaveBeenCalledWith(
                'dropbox_refresh_token'
            )
        })

        it('should handle missing environment values', () => {
            const mockEnvironment = {}

            // Mock localStorage with no tokens
            const mockLocalStorage = {
                getItem: jest.fn().mockReturnValue(null),
            }

            Object.defineProperty(global, 'localStorage', {
                value: mockLocalStorage,
                writable: true,
            })

            const credentials = getCredentialsFromEnvironment(mockEnvironment)

            expect(credentials).toEqual({
                clientId: '',
                clientSecret: undefined,
                accessToken: undefined,
                refreshToken: undefined,
            })
        })
    })
})
