import type {
  AuthProvider,
  AuthCredentials,
  SignUpCredentials,
  AuthResult,
  Session,
  User,
  AuthState,
  AuthConfig,
  UserRole,
  RolePermissions,
} from '../types'
import { SessionManager, CookieSessionStorage, LocalSessionStorage } from '../utils/session'
import { hasPermission, hasRole } from '../utils/permissions'

export class AuthManager {
  private provider: AuthProvider
  private sessionManager: SessionManager
  private config: AuthConfig
  private state: AuthState
  private listeners: ((state: AuthState) => void)[] = []

  constructor(config: AuthConfig) {
    this.config = config
    this.provider = config.provider
    this.sessionManager = new SessionManager(
      config.persistSession ? 
        new CookieSessionStorage(config.cookieOptions) :
        new LocalSessionStorage(),
      config.sessionKey
    )

    this.state = {
      user: null,
      session: null,
      isLoading: true,
      isAuthenticated: false,
      error: null,
    }

    this.initializeAuth()
  }

  private async initializeAuth(): Promise<void> {
    try {
      let session = await this.sessionManager.getSession()
      
      if (!session) {
        session = await this.provider.getSession()
        if (session) {
          await this.sessionManager.setSession(session)
        }
      }

      this.updateState({
        user: session?.user || null,
        session: session || null,
        isLoading: false,
        isAuthenticated: !!session,
        error: null,
      })

      if (session && this.config.autoRefreshToken) {
        this.startTokenRefresh(session)
      }

      this.provider.onAuthStateChange((providerState) => {
        this.handleAuthStateChange(providerState)
      })
    } catch (error) {
      this.updateState({
        user: null,
        session: null,
        isLoading: false,
        isAuthenticated: false,
        error: {
          code: 'initialization_error',
          message: 'Failed to initialize authentication',
          details: error,
        },
      })
    }
  }

  private async handleAuthStateChange(providerState: AuthState): Promise<void> {
    if (providerState.session) {
      await this.sessionManager.setSession(providerState.session)
      if (this.config.autoRefreshToken) {
        this.startTokenRefresh(providerState.session)
      }
    } else {
      await this.sessionManager.clearSession()
    }

    this.updateState(providerState)
  }

  private startTokenRefresh(session: Session): void {
    const refreshInterval = (session.expiresAt - Date.now() / 1000) * 1000 * 0.8

    setTimeout(async () => {
      try {
        const newSession = await this.provider.refreshSession()
        if (newSession) {
          await this.sessionManager.setSession(newSession)
          this.updateState({
            ...this.state,
            session: newSession,
            user: newSession.user,
          })
          this.startTokenRefresh(newSession)
        }
      } catch (error) {
        this.updateState({
          ...this.state,
          error: {
            code: 'refresh_error',
            message: 'Failed to refresh token',
            details: error,
          },
        })
      }
    }, refreshInterval)
  }

  async signIn(credentials: AuthCredentials): Promise<AuthResult> {
    this.updateState({ ...this.state, isLoading: true, error: null })

    try {
      const result = await this.provider.signIn(credentials)
      
      if (result.session) {
        await this.sessionManager.setSession(result.session)
        if (this.config.autoRefreshToken) {
          this.startTokenRefresh(result.session)
        }
      }

      this.updateState({
        user: result.user,
        session: result.session,
        isLoading: false,
        isAuthenticated: !!result.session,
        error: result.error,
      })

      return result
    } catch (error) {
      const authError = {
        code: 'signin_error',
        message: 'Sign in failed',
        details: error,
      }

      this.updateState({
        ...this.state,
        isLoading: false,
        error: authError,
      })

      return {
        user: null,
        session: null,
        error: authError,
      }
    }
  }

  async signUp(credentials: SignUpCredentials): Promise<AuthResult> {
    this.updateState({ ...this.state, isLoading: true, error: null })

    try {
      const result = await this.provider.signUp(credentials)
      
      if (result.session) {
        await this.sessionManager.setSession(result.session)
        if (this.config.autoRefreshToken) {
          this.startTokenRefresh(result.session)
        }
      }

      this.updateState({
        user: result.user,
        session: result.session,
        isLoading: false,
        isAuthenticated: !!result.session,
        error: result.error,
      })

      return result
    } catch (error) {
      const authError = {
        code: 'signup_error',
        message: 'Sign up failed',
        details: error,
      }

      this.updateState({
        ...this.state,
        isLoading: false,
        error: authError,
      })

      return {
        user: null,
        session: null,
        error: authError,
      }
    }
  }

  async signOut(): Promise<void> {
    this.updateState({ ...this.state, isLoading: true, error: null })

    try {
      await this.provider.signOut()
      await this.sessionManager.clearSession()

      this.updateState({
        user: null,
        session: null,
        isLoading: false,
        isAuthenticated: false,
        error: null,
      })
    } catch (error) {
      this.updateState({
        ...this.state,
        isLoading: false,
        error: {
          code: 'signout_error',
          message: 'Sign out failed',
          details: error,
        },
      })
    }
  }

  async resetPassword(email: string): Promise<void> {
    try {
      await this.provider.resetPassword(email)
    } catch (error) {
      throw new Error('Failed to send reset password email')
    }
  }

  async updatePassword(password: string): Promise<void> {
    try {
      await this.provider.updatePassword(password)
    } catch (error) {
      throw new Error('Failed to update password')
    }
  }

  async refreshSession(): Promise<Session | null> {
    try {
      const session = await this.provider.refreshSession()
      if (session) {
        await this.sessionManager.setSession(session)
        this.updateState({
          ...this.state,
          session,
          user: session.user,
        })
      }
      return session
    } catch (error) {
      return null
    }
  }

  getState(): AuthState {
    return this.state
  }

  getUser(): User | null {
    return this.state.user
  }

  getSession(): Session | null {
    return this.state.session
  }

  isAuthenticated(): boolean {
    return this.state.isAuthenticated
  }

  hasPermission(permission: keyof RolePermissions): boolean {
    if (!this.state.user) return false
    return hasPermission(this.state.user.roleType, permission)
  }

  hasRole(role: UserRole): boolean {
    if (!this.state.user) return false
    return hasRole(this.state.user.roleType, role)
  }

  requireAuth(): void {
    if (!this.state.isAuthenticated) {
      throw new Error('Authentication required')
    }
  }

  requireRole(role: UserRole): void {
    this.requireAuth()
    if (!this.hasRole(role)) {
      throw new Error(`Role ${role} required`)
    }
  }

  onAuthStateChange(callback: (state: AuthState) => void): () => void {
    this.listeners.push(callback)
    
    return () => {
      const index = this.listeners.indexOf(callback)
      if (index > -1) {
        this.listeners.splice(index, 1)
      }
    }
  }

  private updateState(newState: AuthState): void {
    this.state = newState
    this.listeners.forEach(listener => listener(newState))
  }
}

export function createAuthManager(config: AuthConfig): AuthManager {
  return new AuthManager(config)
}