import { Dropbox } from 'dropbox'
import type { AuthMethods, DropboxCredentials, TokenResponse } from './types'

// Define missing types for Dropbox SDK
interface DropboxAuthExtended {
    getAuthenticationUrl(
        redirectUri: string,
        state: string,
        authType?: string,
        tokenAccessType?: string,
        scope?: string | string[],
        includeGrantedScopes?: string,
        usePKCE?: boolean
    ): string

    getAccessTokenFromCode(
        redirectUri: string,
        code: string
    ): Promise<{
        result: {
            access_token: string
            refresh_token?: string
            expires_in?: number
        }
    }>
}

export function createAuthMethods(
    getClient: () => Dropbox,
    credentials: DropboxCredentials,
    setAccessToken: (token: string) => void
): AuthMethods {
    return {
        /**
         * Generate an authentication URL for Dropbox OAuth flow
         */
        async getAuthUrl(redirectUri: string, state?: string): Promise<string> {
            const dropbox: any = getClient()
            const randomState =
                state || Math.random().toString(36).substring(2, 15)

            // Cast to extended auth type to access missing methods
            const auth = dropbox.auth as unknown as DropboxAuthExtended

            // Generate the authentication URL
            const authUrl = auth.getAuthenticationUrl(
                redirectUri,
                randomState,
                'code', // Use authorization code flow
                'offline', // Request a refresh token for long-lived access
                undefined, // No scope specified, request full access
                undefined, // No include_granted_scopes
                true // Force reapproval to ensure we get fresh tokens
            )

            return authUrl
        },

        /**
         * Exchange authorization code for access token
         */
        async exchangeCodeForToken(
            code: string,
            redirectUri: string
        ): Promise<TokenResponse> {
            const dropbox: any = getClient()
            // Cast to extended auth type to access missing methods
            const auth = dropbox.auth as unknown as DropboxAuthExtended

            // Exchange the code for an access token
            const response = await auth.getAccessTokenFromCode(
                redirectUri,
                code
            )

            const tokenResponse = {
                accessToken: response.result.access_token,
                refreshToken: response.result.refresh_token,
                expiresAt: response.result.expires_in
                    ? Date.now() + response.result.expires_in * 1000
                    : undefined,
            }

            // Update the client with the new token
            setAccessToken(tokenResponse.accessToken)

            return tokenResponse
        },

        /**
         * Refresh an expired access token using the refresh token
         */
        async refreshAccessToken(): Promise<TokenResponse> {
            if (
                !credentials.refreshToken ||
                !credentials.clientId ||
                !credentials.clientSecret
            ) {
                throw new Error(
                    'Refresh token, client ID, and client secret are required to refresh access token'
                )
            }

            // Dropbox API endpoint for token refresh
            const tokenUrl = 'https://api.dropboxapi.com/oauth2/token'

            // Prepare the form data for the token request
            const formData = new URLSearchParams({
                grant_type: 'refresh_token',
                refresh_token: credentials.refreshToken,
                client_id: credentials.clientId,
                client_secret: credentials.clientSecret,
            })

            // Make the request to refresh the token
            const response = await fetch(tokenUrl, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                },
                body: formData.toString(),
            })

            if (!response.ok) {
                await response.text() // Consume the response body
                throw new Error(
                    `Failed to refresh token: ${response.status} ${response.statusText}`
                )
            }

            // Parse the response
            const data = await response.json()

            const tokenResponse = {
                accessToken: data.access_token,
                refreshToken: data.refresh_token || credentials.refreshToken,
                expiresAt: data.expires_in
                    ? Date.now() + data.expires_in * 1000
                    : undefined,
            }

            // Update the client with the new token
            setAccessToken(tokenResponse.accessToken)

            return tokenResponse
        },
    }
}
