import {
    useNextDropboxSync,
    getCredentialsFromCookies,
    handleOAuthCallback,
    createNextDropboxApiHandlers,
} from '../next'
import { createDropboxSyncClient } from '../../core/client'
import { cookies } from 'next/headers'
import { NextRequest, NextResponse } from 'next/server'

// Mock dependencies
jest.mock('../../core/client')
jest.mock('next/headers')
jest.mock('next/server')

describe('Next.js adapter', () => {
    beforeEach(() => {
        jest.clearAllMocks()

        // Mock process.env
        process.env.DROPBOX_APP_KEY = 'test-app-key'
        process.env.DROPBOX_APP_SECRET = 'test-app-secret'
        process.env.NEXT_PUBLIC_APP_URL = 'https://example.com'
    })

    describe('useNextDropboxSync', () => {
        it('should create a Dropbox sync client with provided credentials', () => {
            const mockCredentials = {
                clientId: 'custom-client-id',
                clientSecret: 'custom-client-secret',
            }

            ;(createDropboxSyncClient as jest.Mock).mockReturnValue({
                mock: 'client',
            })

            const result = useNextDropboxSync(mockCredentials)

            expect(createDropboxSyncClient).toHaveBeenCalledWith(
                mockCredentials
            )
            expect(result).toEqual({ mock: 'client' })
        })
    })

    describe('getCredentialsFromCookies', () => {
        it('should get credentials from cookies', async () => {
            const mockCookieStore = {
                get: jest.fn((name) => {
                    if (name === 'dropbox_access_token')
                        return { value: 'test-access-token' }
                    if (name === 'dropbox_refresh_token')
                        return { value: 'test-refresh-token' }
                    return null
                }),
            }

            ;(cookies as jest.Mock).mockResolvedValue(mockCookieStore)

            const credentials = await getCredentialsFromCookies()

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

        it('should handle missing cookie values', async () => {
            const mockCookieStore = {
                get: jest.fn().mockReturnValue(null),
            }

            ;(cookies as jest.Mock).mockResolvedValue(mockCookieStore)

            const credentials = await getCredentialsFromCookies()

            expect(credentials).toEqual({
                clientId: 'test-app-key',
                clientSecret: 'test-app-secret',
                accessToken: undefined,
                refreshToken: undefined,
            })
        })
    })

    describe('handleOAuthCallback', () => {
        it('should handle successful OAuth callback', async () => {
            // Mock URL with auth code
            const mockRequest = {
                url: 'https://example.com/callback?code=test-auth-code',
            } as unknown as NextRequest

            // Mock URL constructor behavior
            ;(global as any).URL = jest.fn().mockImplementation(() => {
                return {
                    searchParams: {
                        get: jest.fn().mockReturnValue('test-auth-code'),
                    },
                }
            })

            // Mock Dropbox client
            const mockClient = {
                auth: {
                    exchangeCodeForToken: jest.fn().mockResolvedValue({
                        accessToken: 'new-access-token',
                        refreshToken: 'new-refresh-token',
                        expiresAt: Date.now() + 14400 * 1000,
                    }),
                },
            }

            ;(createDropboxSyncClient as jest.Mock).mockReturnValue(mockClient)

            // Mock NextResponse
            const mockResponseCookies = {
                set: jest.fn(),
            }

            const mockResponse = {
                cookies: mockResponseCookies,
            }

            ;(NextResponse.redirect as jest.Mock).mockReturnValue(mockResponse)

            await handleOAuthCallback(mockRequest)

            expect(createDropboxSyncClient).toHaveBeenCalled()
            expect(mockClient.auth.exchangeCodeForToken).toHaveBeenCalledWith(
                'test-auth-code',
                expect.any(String)
            )
            expect(NextResponse.redirect).toHaveBeenCalled()
            expect(mockResponseCookies.set).toHaveBeenCalledTimes(3) // Access, refresh, and connected cookies
        })

        it('should redirect to error page if code is missing', async () => {
            const mockRequest = {
                url: 'https://example.com/callback',
            } as unknown as NextRequest

            ;(global as any).URL = jest.fn().mockImplementation(() => {
                return {
                    searchParams: {
                        get: jest.fn().mockReturnValue(null),
                    },
                }
            })

            // Create a proper URL string for the mock response
            const errorUrl = 'https://example.com/auth/error'

            // Mock the redirect implementation to return this URL
            ;(NextResponse.redirect as jest.Mock).mockReturnValue({
                url: errorUrl,
            })

            const result = await handleOAuthCallback(mockRequest)

            // Now we'll check that the redirect was called, and the result contains our URL
            expect(NextResponse.redirect).toHaveBeenCalled()
            expect(result.url).toBe(errorUrl)
        })
    })

    describe('createNextDropboxApiHandlers', () => {
        it('should create API handlers with necessary methods', () => {
            const handlers = createNextDropboxApiHandlers()

            expect(handlers).toHaveProperty('status')
            expect(handlers).toHaveProperty('oauthStart')
            expect(handlers).toHaveProperty('logout')
        })

        it('should check status from cookies', async () => {
            const mockCookieStore = {
                get: jest.fn().mockReturnValue({ value: 'test-token' }),
            }

            ;(cookies as jest.Mock).mockResolvedValue(mockCookieStore)
            ;(NextResponse.json as jest.Mock).mockReturnValue({
                json: 'response',
            })

            const handlers = createNextDropboxApiHandlers()
            const response = await handlers.status()

            expect(cookies).toHaveBeenCalled()
            expect(mockCookieStore.get).toHaveBeenCalledWith(
                'dropbox_access_token'
            )
            expect(NextResponse.json).toHaveBeenCalledWith({ connected: true })
        })

        it('should start OAuth flow', async () => {
            const mockClient = {
                auth: {
                    getAuthUrl: jest
                        .fn()
                        .mockResolvedValue('https://dropbox.com/oauth'),
                },
            }

            ;(createDropboxSyncClient as jest.Mock).mockReturnValue(mockClient)
            ;(NextResponse.redirect as jest.Mock).mockReturnValue({
                redirect: 'response',
            })

            const handlers = createNextDropboxApiHandlers()
            const response = await handlers.oauthStart()

            expect(createDropboxSyncClient).toHaveBeenCalled()
            expect(mockClient.auth.getAuthUrl).toHaveBeenCalled()
            expect(NextResponse.redirect).toHaveBeenCalledWith(
                'https://dropbox.com/oauth'
            )
        })

        it('should handle logout', async () => {
            const mockResponseCookies = {
                delete: jest.fn(),
            }

            const mockResponse = {
                cookies: mockResponseCookies,
            }

            ;(NextResponse.json as jest.Mock).mockReturnValue(mockResponse)

            const handlers = createNextDropboxApiHandlers()
            const response = await handlers.logout()

            expect(NextResponse.json).toHaveBeenCalledWith({ success: true })
            expect(mockResponseCookies.delete).toHaveBeenCalledTimes(3) // Should delete three cookies
        })
    })
})
