/**
 * Authentication Provider
 *
 * Manages authentication state and token lifecycle
 */

import type { AuthTokenResponse } from '@breakpilot/compliance-sdk-types'
import { ComplianceClient } from './client'

// =============================================================================
// TYPES
// =============================================================================

export interface AuthProviderOptions {
  client: ComplianceClient
  clientId: string
  clientSecret?: string
  storage?: Storage
  onAuthStateChange?: (state: AuthState) => void
}

export interface AuthState {
  isAuthenticated: boolean
  accessToken: string | null
  refreshToken: string | null
  expiresAt: Date | null
  user?: {
    id: string
    email: string
    name: string
    role: string
  }
}

// =============================================================================
// CONSTANTS
// =============================================================================

const STORAGE_KEY = 'breakpilot-sdk-auth'
const TOKEN_REFRESH_BUFFER = 5 * 60 * 1000 // 5 minutes before expiry

// =============================================================================
// AUTH PROVIDER
// =============================================================================

export class AuthProvider {
  private client: ComplianceClient
  private clientId: string
  private clientSecret?: string
  private storage: Storage | null
  private state: AuthState
  private refreshTimeout: ReturnType<typeof setTimeout> | null = null
  private onAuthStateChange?: (state: AuthState) => void

  constructor(options: AuthProviderOptions) {
    this.client = options.client
    this.clientId = options.clientId
    this.clientSecret = options.clientSecret
    this.storage = options.storage ?? (typeof localStorage !== 'undefined' ? localStorage : null)
    this.onAuthStateChange = options.onAuthStateChange

    this.state = {
      isAuthenticated: false,
      accessToken: null,
      refreshToken: null,
      expiresAt: null,
    }

    this.loadFromStorage()
  }

  // ---------------------------------------------------------------------------
  // Private Methods
  // ---------------------------------------------------------------------------

  private loadFromStorage(): void {
    if (!this.storage) return

    try {
      const stored = this.storage.getItem(STORAGE_KEY)
      if (stored) {
        const data = JSON.parse(stored)
        if (data.expiresAt) {
          data.expiresAt = new Date(data.expiresAt)
        }

        // Check if token is still valid
        if (data.expiresAt && data.expiresAt > new Date()) {
          this.state = data
          this.client.setAccessToken(data.accessToken)
          this.scheduleTokenRefresh()
          this.notifyStateChange()
        } else if (data.refreshToken) {
          // Try to refresh
          this.refreshToken().catch(() => {
            this.clearAuth()
          })
        }
      }
    } catch (error) {
      console.error('Failed to load auth from storage:', error)
    }
  }

  private saveToStorage(): void {
    if (!this.storage) return

    try {
      this.storage.setItem(STORAGE_KEY, JSON.stringify(this.state))
    } catch (error) {
      console.error('Failed to save auth to storage:', error)
    }
  }

  private clearStorage(): void {
    if (!this.storage) return

    try {
      this.storage.removeItem(STORAGE_KEY)
    } catch (error) {
      console.error('Failed to clear auth from storage:', error)
    }
  }

  private updateState(tokenResponse: AuthTokenResponse): void {
    const expiresAt = new Date(Date.now() + tokenResponse.expiresIn * 1000)

    this.state = {
      isAuthenticated: true,
      accessToken: tokenResponse.accessToken,
      refreshToken: tokenResponse.refreshToken ?? this.state.refreshToken,
      expiresAt,
    }

    this.client.setAccessToken(tokenResponse.accessToken)
    this.saveToStorage()
    this.scheduleTokenRefresh()
    this.notifyStateChange()
  }

  private scheduleTokenRefresh(): void {
    if (this.refreshTimeout) {
      clearTimeout(this.refreshTimeout)
    }

    if (!this.state.expiresAt || !this.state.refreshToken) return

    const timeUntilRefresh = this.state.expiresAt.getTime() - Date.now() - TOKEN_REFRESH_BUFFER

    if (timeUntilRefresh > 0) {
      this.refreshTimeout = setTimeout(() => {
        this.refreshToken().catch(() => {
          this.clearAuth()
        })
      }, timeUntilRefresh)
    }
  }

  private notifyStateChange(): void {
    this.onAuthStateChange?.(this.state)
  }

  // ---------------------------------------------------------------------------
  // Public Methods
  // ---------------------------------------------------------------------------

  /**
   * Authenticate using client credentials (server-to-server)
   */
  async authenticateWithCredentials(): Promise<void> {
    if (!this.clientSecret) {
      throw new Error('Client secret is required for credentials authentication')
    }

    const response = await this.client.authenticate({
      grantType: 'client_credentials',
      clientId: this.clientId,
      clientSecret: this.clientSecret,
    })

    this.updateState(response)
  }

  /**
   * Authenticate using authorization code (OAuth flow)
   */
  async authenticateWithCode(code: string, redirectUri: string): Promise<void> {
    const response = await this.client.authenticate({
      grantType: 'authorization_code',
      clientId: this.clientId,
      clientSecret: this.clientSecret,
      code,
      redirectUri,
    })

    this.updateState(response)
  }

  /**
   * Refresh the access token
   */
  async refreshToken(): Promise<void> {
    if (!this.state.refreshToken) {
      throw new Error('No refresh token available')
    }

    const response = await this.client.refreshToken(this.state.refreshToken)
    this.updateState(response)
  }

  /**
   * Set access token manually (e.g., from API key)
   */
  setAccessToken(token: string, expiresIn?: number): void {
    const expiresAt = expiresIn ? new Date(Date.now() + expiresIn * 1000) : null

    this.state = {
      isAuthenticated: true,
      accessToken: token,
      refreshToken: null,
      expiresAt,
    }

    this.client.setAccessToken(token)
    this.saveToStorage()
    this.notifyStateChange()
  }

  /**
   * Clear authentication state
   */
  clearAuth(): void {
    if (this.refreshTimeout) {
      clearTimeout(this.refreshTimeout)
    }

    this.state = {
      isAuthenticated: false,
      accessToken: null,
      refreshToken: null,
      expiresAt: null,
    }

    this.client.clearAccessToken()
    this.clearStorage()
    this.notifyStateChange()
  }

  /**
   * Get current authentication state
   */
  getState(): AuthState {
    return { ...this.state }
  }

  /**
   * Check if currently authenticated
   */
  isAuthenticated(): boolean {
    if (!this.state.isAuthenticated || !this.state.accessToken) {
      return false
    }

    if (this.state.expiresAt && this.state.expiresAt <= new Date()) {
      return false
    }

    return true
  }

  /**
   * Get the authorization URL for OAuth flow
   */
  getAuthorizationUrl(redirectUri: string, scope?: string, state?: string): string {
    const params = new URLSearchParams({
      client_id: this.clientId,
      redirect_uri: redirectUri,
      response_type: 'code',
      ...(scope && { scope }),
      ...(state && { state }),
    })

    // This would be configured based on the API endpoint
    return `/oauth/authorize?${params.toString()}`
  }

  /**
   * Destroy the auth provider
   */
  destroy(): void {
    if (this.refreshTimeout) {
      clearTimeout(this.refreshTimeout)
    }
  }
}
