import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { TokenService } from './token.service';
import {
  OAuthProvider,
  OAuthProvidersResponse,
  OAuthLinkRequest,
  ApiResponse,
  ApiError
} from '../models/auth.models';

@Injectable({
  providedIn: 'root'
})
export class OAuthService {
  private baseUrl = '';

  constructor(
    private http: HttpClient,
    private tokenService: TokenService
  ) {}

  /**
   * Set the base URL for the auth service
   */
  setBaseUrl(url: string): void {
    this.baseUrl = url.replace(/\/$/, ''); // Remove trailing slash
  }

  /**
   * Get API URL with version prefix
   */
  private getApiUrl(path: string): string {
    return `${this.baseUrl}/api/v1${path}`;
  }

  /**
   * Get HTTP headers with authorization if available
   */
  private getHeaders(includeAuth = true): { [header: string]: string } {
    const headers: { [header: string]: string } = {
      'Content-Type': 'application/json'
    };

    if (includeAuth) {
      const authHeader = this.tokenService.getAuthorizationHeader();
      if (authHeader) {
        headers['Authorization'] = authHeader;
      }
    }

    return headers;
  }

  /**
   * Handle HTTP errors and extract API error information
   */
  private handleError(error: any): Observable<never> {
    let apiError: ApiError;

    if (error.error && typeof error.error === 'object') {
      apiError = error.error;
    } else {
      apiError = {
        error: error.message || 'An unknown error occurred'
      };
    }

    return throwError(() => apiError);
  }

  /**
   * Get available OAuth providers
   */
  getProviders(): Observable<OAuthProvider[]> {
    return this.http.get<OAuthProvidersResponse>(
      this.getApiUrl('/oauth/providers'),
      { headers: this.getHeaders(false) }
    ).pipe(
      map(response => response.providers),
      catchError(this.handleError)
    );
  }

  /**
   * Get OAuth authorization URL for a provider
   */
  getAuthorizationUrl(provider: string, redirectUri?: string, state?: string): Observable<string> {
    let url = this.getApiUrl(`/oauth/${provider}`);
    
    const params = new URLSearchParams();
    if (redirectUri) {
      params.append('redirect_uri', redirectUri);
    }
    if (state) {
      params.append('state', state);
    }

    if (params.toString()) {
      url += `?${params.toString()}`;
    }

    return this.http.get(url, { 
      headers: this.getHeaders(false),
      responseType: 'text'
    }).pipe(
      catchError(this.handleError)
    );
  }

  /**
   * Handle OAuth callback (exchange code for tokens)
   */
  handleCallback(provider: string, code: string, state?: string): Observable<any> {
    const params = new URLSearchParams();
    params.append('code', code);
    if (state) {
      params.append('state', state);
    }

    const url = `${this.getApiUrl(`/oauth/${provider}/callback`)}?${params.toString()}`;

    return this.http.get(url, {
      headers: this.getHeaders(false)
    }).pipe(
      catchError(this.handleError)
    );
  }

  /**
   * Link OAuth provider to existing account (requires authentication)
   */
  linkProvider(provider: string, code: string, state?: string): Observable<ApiResponse> {
    const request: OAuthLinkRequest = {
      provider,
      code,
      state
    };

    return this.http.post<ApiResponse>(
      this.getApiUrl(`/oauth/${provider}/link`),
      request,
      { headers: this.getHeaders() }
    ).pipe(
      catchError(this.handleError)
    );
  }

  /**
   * Unlink OAuth provider from account (requires authentication)
   */
  unlinkProvider(provider: string): Observable<ApiResponse> {
    return this.http.delete<ApiResponse>(
      this.getApiUrl(`/oauth/${provider}`),
      { headers: this.getHeaders() }
    ).pipe(
      catchError(this.handleError)
    );
  }

  /**
   * Initiate OAuth flow by redirecting to provider
   */
  initiateOAuthFlow(provider: string, redirectUri?: string, state?: string): void {
    this.getAuthorizationUrl(provider, redirectUri, state).subscribe({
      next: (authUrl) => {
        window.location.href = authUrl;
      },
      error: (error) => {
        console.error('Failed to initiate OAuth flow:', error);
        throw error;
      }
    });
  }

  /**
   * Open OAuth flow in popup window
   */
  initiateOAuthPopup(
    provider: string, 
    redirectUri?: string, 
    state?: string,
    popupFeatures = 'width=500,height=600,scrollbars=yes,resizable=yes'
  ): Promise<any> {
    return new Promise((resolve, reject) => {
      this.getAuthorizationUrl(provider, redirectUri, state).subscribe({
        next: (authUrl) => {
          const popup = window.open(authUrl, 'oauth', popupFeatures);
          
          if (!popup) {
            reject(new Error('Failed to open popup window'));
            return;
          }

          // Poll for popup closure or message
          const checkClosed = setInterval(() => {
            if (popup.closed) {
              clearInterval(checkClosed);
              reject(new Error('OAuth popup was closed by user'));
            }
          }, 1000);

          // Listen for messages from popup
          const messageListener = (event: MessageEvent) => {
            if (event.origin !== window.location.origin) {
              return;
            }

            if (event.data.type === 'OAUTH_SUCCESS') {
              clearInterval(checkClosed);
              window.removeEventListener('message', messageListener);
              popup.close();
              resolve(event.data.payload);
            } else if (event.data.type === 'OAUTH_ERROR') {
              clearInterval(checkClosed);
              window.removeEventListener('message', messageListener);
              popup.close();
              reject(new Error(event.data.error));
            }
          };

          window.addEventListener('message', messageListener);
        },
        error: (error) => {
          reject(error);
        }
      });
    });
  }

  /**
   * Generate state parameter for OAuth security
   */
  generateState(): string {
    const array = new Uint8Array(32);
    crypto.getRandomValues(array);
    return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
  }

  /**
   * Store state in session storage for verification
   */
  storeState(state: string): void {
    sessionStorage.setItem('oauth_state', state);
  }

  /**
   * Verify state parameter
   */
  verifyState(state: string): boolean {
    const storedState = sessionStorage.getItem('oauth_state');
    sessionStorage.removeItem('oauth_state');
    return storedState === state;
  }

  /**
   * Extract code and state from URL params (for redirect handling)
   */
  extractCallbackParams(): { code?: string; state?: string; error?: string } {
    const urlParams = new URLSearchParams(window.location.search);
    return {
      code: urlParams.get('code') || undefined,
      state: urlParams.get('state') || undefined,
      error: urlParams.get('error') || undefined
    };
  }

  /**
   * Complete OAuth flow with extracted parameters
   */
  completeOAuthFlow(provider: string): Observable<any> {
    const params = this.extractCallbackParams();

    if (params.error) {
      return throwError(() => ({ error: params.error } as ApiError));
    }

    if (!params.code) {
      return throwError(() => ({ error: 'No authorization code received' } as ApiError));
    }

    if (params.state && !this.verifyState(params.state)) {
      return throwError(() => ({ error: 'Invalid state parameter' } as ApiError));
    }

    return this.handleCallback(provider, params.code, params.state);
  }
}