/**
 * Route guard for:
 * 1. ensuring app bridge connection
 * 2. loading identity information
 * 3. redirecting users to pages they can access
 */

import { apiGetFreightThreshold, getUserIdentity, getMemberProfile } from '../api/wallet.js'
import { isPublicPath } from '@/config/security.js'
import { isDebugMode } from './env.js'
import { permissionManager, PAGES, USER_ROLES } from './permissionManager.js'
import { getUserId, setUserInfo } from './userStore.js'
import {
  connectApplication,
  isInApp,
  parseAccessOrigin,
  parseHeader,
  setHttpHeaders,
} from './appBridge.js'
import { appConnectionManager, isAppConnected } from './appConnectionManager.js'

class AppConnectionChecker {
  async ensureConnection() {
    console.debug('Checking APP connection...')

    if (!isInApp()) {
      if (!isDebugMode()) {
        console.warn('Blocked protected route outside official app')
        return false
      }

      console.debug('Browser environment detected in debug mode')
      return true
    }

    if (isAppConnected()) {
      console.debug('APP already connected')
      return true
    }

    if (appConnectionManager.isConnecting()) {
      console.debug('Connection already in progress, waiting...')
      try {
        await appConnectionManager.getConnectionPromise()
        return isAppConnected()
      } catch (error) {
        console.error('Connection wait failed:', error)
        return false
      }
    }

    return this._performConnection()
  }

  async _performConnection() {
    try {
      const connectionPromise = new Promise((resolve, reject) => {
        connectApplication(async (access) => {
          try {
            const result = parseAccessOrigin(access)

            if (!result.success) {
              appConnectionManager.reset()
              reject(new Error(result.error || 'accessOrigin Error.'))
              return
            }

            const headerInfo = parseHeader(result.data)
            await setHttpHeaders(headerInfo)
            appConnectionManager.setConnected(headerInfo)
            await this._fetchUserInfoAfterConnection()

            resolve(true)
          } catch (error) {
            appConnectionManager.reset()
            reject(error)
          }
        })
      })

      appConnectionManager.setConnecting(connectionPromise)
      return await connectionPromise
    } catch (error) {
      console.error('APP connection failed:', error)
      appConnectionManager.reset()
      return false
    }
  }

  async _fetchUserInfoAfterConnection() {
    try {
      const response = await getMemberProfile()

      if (response && response.status && response.body) {
        setUserInfo(response.body, null)
      }
    } catch (error) {
      console.warn('Failed to fetch user info after connection:', error)
    }
  }

  reset() {
    appConnectionManager.reset()
  }
}

class IdentityChecker {
  constructor() {
    this.isChecking = false
    this.checkPromise = null
  }

  async checkUserIdentity(userId) {
    if (!userId) {
      permissionManager.setUserIdentity(null)
      return USER_ROLES.GUEST
    }

    if (this.isChecking && this.checkPromise) {
      return await this.checkPromise
    }

    this.isChecking = true
    this.checkPromise = this._performIdentityCheck(userId)

    try {
      return await this.checkPromise
    } finally {
      this.isChecking = false
      this.checkPromise = null
    }
  }

  async _performIdentityCheck(userId) {
    try {
      console.debug('Checking user identity for:', userId)
      const response = await getUserIdentity()

      if (response && response.status && response.body) {
        permissionManager.setUserIdentity(response.body)
        return permissionManager.getPrimaryRole()
      }

      permissionManager.setUserIdentity(null)
      return USER_ROLES.GUEST
    } catch (error) {
      console.error('Failed to fetch user identity:', error)
      permissionManager.setUserIdentity(null)
      return USER_ROLES.GUEST
    }
  }

  reset() {
    this.isChecking = false
    this.checkPromise = null
  }
}

class SuperDealerRouteChecker {
  async check(userId) {
    if (!userId) return false

    const response = await apiGetFreightThreshold(userId)
    return Boolean(response?.status && response.body?.superDealer)
  }
}

class PageRedirector {
  async redirectToCorrectPage(router, targetPath, currentPath) {
    const hasPermission = permissionManager.hasPagePermission(targetPath)

    if (hasPermission) {
      return false
    }

    const defaultPage = permissionManager.getDefaultPage()

    if (defaultPage !== currentPath) {
      await router.replace(defaultPage)
      return true
    }

    return false
  }

  async handleUnauthorizedAccess(router) {
    await router.replace(permissionManager.getDefaultPage())
  }
}

class RouteGuard {
  constructor() {
    this.appConnectionChecker = new AppConnectionChecker()
    this.identityChecker = new IdentityChecker()
    this.superDealerRouteChecker = new SuperDealerRouteChecker()
    this.pageRedirector = new PageRedirector()
    this.isGuardActive = false
  }

  install(router) {
    router.beforeEach(async (to, from, next) => {
      await this.handleRouteChange(to, from, next)
    })

    this.isGuardActive = true
    console.debug('Route guard installed')
  }

  async handleRouteChange(to, from, next) {
    console.group('Route Guard Check')
    console.debug('Route change:', from.path, '->', to.path)

    try {
      if (to.meta?.isPublic || this.isPublicPage(to.path)) {
        console.groupEnd()
        next()
        return
      }

      const isConnected = await this.appConnectionChecker.ensureConnection()

      if (!isConnected) {
        console.groupEnd()
        next({
          path: PAGES.NOT_APP,
          query: {
            message: 'Please open this page inside the official app',
          },
        })
        return
      }

      if (!permissionManager.isLoaded()) {
        const userId = getUserId()

        if (!userId) {
          console.groupEnd()
          next(PAGES.APPLY)
          return
        }

        await this.identityChecker.checkUserIdentity(userId)
      }

      if (permissionManager.hasPagePermission(to.path)) {
        if (to.meta?.requiresSuperDealer) {
          // Super dealer pages need a second check because the base role only tells us the user is a coin seller.
          const isSuperDealer = await this.superDealerRouteChecker.check(getUserId())
          if (!isSuperDealer) {
            console.groupEnd()
            next(PAGES.COIN_SELLER)
            return
          }
        }

        console.groupEnd()
        next()
        return
      }

      const defaultPage = permissionManager.getDefaultPage()
      console.groupEnd()
      next(defaultPage)
    } catch (error) {
      console.error('Route guard error:', error)
      console.groupEnd()

      const message = String(error?.message || '').toLowerCase()
      if (message.includes('connection') || message.includes('app required')) {
        next(PAGES.NOT_APP)
        return
      }

      next(PAGES.APPLY)
    }
  }

  isPublicPage(path) {
    return isPublicPath(path)
  }

  async checkAndRedirect(router, userId) {
    try {
      const primaryRole = await this.identityChecker.checkUserIdentity(userId)
      const currentPath = router.currentRoute.value.path

      if (!permissionManager.hasPagePermission(currentPath)) {
        await this.pageRedirector.redirectToCorrectPage(router, currentPath, currentPath)
      }

      return primaryRole
    } catch (error) {
      console.error('Manual check and redirect failed:', error)
      throw error
    }
  }

  reset() {
    this.appConnectionChecker.reset()
    this.identityChecker.reset()
    permissionManager.reset()
    console.debug('Route guard reset')
  }
}

export const routeGuard = new RouteGuard()

export const installRouteGuard = (router) => routeGuard.install(router)
export const checkAndRedirect = (router, userId) => routeGuard.checkAndRedirect(router, userId)
export const resetRouteGuard = () => routeGuard.reset()
