import { Injectable } from '@angular/core'
import { HttpClient, HttpHeaders } from '@angular/common/http'
import { Observable, from, of } from 'rxjs'
import { catchError, map, switchMap, tap } from 'rxjs/operators'
import { createDropboxSyncClient } from '../core/client'
import type {
    DropboxCredentials,
    DropboxSyncClient,
    SyncOptions,
    SyncResult,
    TokenResponse,
} from '../core/types'

@Injectable({
    providedIn: 'root',
})
export class DropboxSyncService {
    private client: DropboxSyncClient | null = null

    constructor(private http: HttpClient) {}

    /**
     * Initialize the Dropbox sync client with credentials
     */
    initialize(credentials: DropboxCredentials): DropboxSyncClient {
        this.client = createDropboxSyncClient(credentials)
        return this.client
    }

    /**
     * Get the current Dropbox sync client instance, or initialize with credentials if not exists
     */
    getClient(credentials?: DropboxCredentials): DropboxSyncClient {
        if (!this.client && credentials) {
            return this.initialize(credentials)
        }

        if (!this.client) {
            throw new Error(
                'DropboxSyncService not initialized. Call initialize() first.'
            )
        }

        return this.client
    }

    /**
     * Check the connection status with Dropbox
     */
    checkConnection(): Observable<boolean> {
        return this.http
            .get<{ connected: boolean }>('/api/dropbox/status')
            .pipe(
                map((response: any) => response.connected),
                catchError(() => of(false))
            )
    }

    /**
     * Start the OAuth flow to connect to Dropbox
     */
    connectDropbox(): void {
        window.location.href = '/api/dropbox/auth/start'
    }

    /**
     * Disconnect from Dropbox
     */
    disconnectDropbox(): Observable<boolean> {
        return this.http
            .post<{ success: boolean }>('/api/dropbox/logout', {})
            .pipe(
                map((response: any) => response.success),
                catchError(() => of(false))
            )
    }

    /**
     * Start the sync process
     */
    startSync(options?: Partial<SyncOptions>): Observable<SyncResult> {
        if (!this.client) {
            throw new Error(
                'DropboxSyncService not initialized. Call initialize() first.'
            )
        }

        // Connect to socket before starting sync
        this.client.socket.connect()

        // Setup socket listeners for sync events
        this.setupSocketListeners()

        return from(this.client.sync.syncFiles(options))
    }

    /**
     * Cancel an ongoing sync process
     */
    cancelSync(): void {
        if (!this.client) {
            return
        }

        this.client.sync.cancelSync()
    }

    /**
     * Set up socket listeners for sync events
     * Returns an RxJS Observable that emits sync progress events
     */
    setupSocketListeners(): Observable<any> {
        if (!this.client) {
            throw new Error(
                'DropboxSyncService not initialized. Call initialize() first.'
            )
        }

        // Create observable for sync progress events
        return new Observable((observer: any) => {
            // Listen for progress updates
            this.client!.socket.on('sync:progress', (data: any) => {
                observer.next({ type: 'progress', data })
            })

            // Listen for queue information
            this.client!.socket.on('sync:queue', (data: any) => {
                observer.next({ type: 'queue', data })
            })

            // Listen for sync completion
            this.client!.socket.on('sync:complete', (data: any) => {
                observer.next({ type: 'complete', data })
                observer.complete()
            })

            // Listen for sync errors
            this.client!.socket.on('sync:error', (data: any) => {
                if (!data.continue) {
                    observer.error(data.message)
                } else {
                    observer.next({ type: 'error', data })
                }
            })

            // Cleanup function - remove event listeners when subscription is disposed
            return () => {
                this.client!.socket.off('sync:progress')
                this.client!.socket.off('sync:queue')
                this.client!.socket.off('sync:complete')
                this.client!.socket.off('sync:error')
            }
        })
    }

    /**
     * Handle OAuth callback on the client side
     */
    handleOAuthCallback(code: string): Observable<TokenResponse> {
        if (!this.client) {
            throw new Error(
                'DropboxSyncService not initialized. Call initialize() first.'
            )
        }

        const redirectUri =
            window.location.origin + '/api/dropbox/auth/callback'

        return from(
            this.client.auth.exchangeCodeForToken(code, redirectUri)
        ).pipe(
            tap((tokens: any) => {
                // Store tokens in local storage for client-side access
                localStorage.setItem('dropbox_access_token', tokens.accessToken)

                if (tokens.refreshToken) {
                    localStorage.setItem(
                        'dropbox_refresh_token',
                        tokens.refreshToken
                    )
                }

                localStorage.setItem('dropbox_connected', 'true')
            })
        )
    }

    /**
     * Create a sync queue to determine which files need to be uploaded/downloaded
     */
    createSyncQueue(
        options?: Partial<SyncOptions>
    ): Observable<{ uploadQueue: string[]; downloadQueue: string[] }> {
        if (!this.client) {
            throw new Error(
                'DropboxSyncService not initialized. Call initialize() first.'
            )
        }

        return from(this.client.sync.createSyncQueue(options))
    }
}

/**
 * Function to extract credentials from Angular environment
 */
export function getCredentialsFromEnvironment(
    environment: any
): DropboxCredentials {
    return {
        clientId: environment.dropboxAppKey || '',
        clientSecret: environment.dropboxAppSecret,
        accessToken: localStorage.getItem('dropbox_access_token') || undefined,
        refreshToken:
            localStorage.getItem('dropbox_refresh_token') || undefined,
    }
}
