import { nanoid } from "nanoid"
import getEnvVariable from "@racletteCore/lib/getEnvVariable"
import {
  httpClient,
  configureClient,
  configureHttpInterceptors,
  apiHealthcheck,
  setHttpToken,
  setWorkSessionId,
} from "@racletteCore/lib/httpClient"

import { chadsayFE } from "@racletteCore"
import createStore, { registerStoreEffects } from "@racletteCore/store"
import type { RacletteStore, CorePluginApi } from "@racletteCore"
import type { AxiosError, AxiosInstance } from "axios"
import createSocket from "@racletteCore/setup/socket"
import EventEmitter from "@racletteCore/lib/eventEmitter"
import {
  eventWhitelist,
  eventWildcards,
} from "@racletteCore/lib/eventWhitelist"
import type { Socket } from "socket.io-client"
import registerSocketEvents from "@racletteCore/setup/socketEvents"
import registerSocketClient from "@racletteCore/setup/socketClient"
import { AUTH_TOKEN_KEY } from "@racletteCore/setup/sessionHandler"
import $log from "@racletteCore/lib/logger"
import { awaitTime } from "@racletteCore/lib/dataHelper"
import type { User } from "@shared/types/core/User.types"
import {
  checkToken,
  setServerConnecting,
} from "@racletteCore/lib/userNotifier"
import configService from "@racletteCore/lib/configService"
import { isDevelopmentMode } from "@racletteCore/helpers/devMode"
import {
  setActiveSocket,
  teardownSocket,
} from "@racletteCore/setup/socketRegistry"
import {
  registerPlugins,
  registeredPlugins,
} from "@racletteCore/setup/plugin-system"

const SOCKET_URL = getEnvVariable("RACLETTE_SOCKET_URL")
const API_BASE_URL = getEnvVariable("RACLETTE_SERVER_BASE_URL")

/** First warning while waiting for socket bootstrap to finish. */
const SETUP_PROJECT_WARN_MS = 5_000
/** Hard timeout in production (dev mode never rejects — easier backend debugging). */
const SETUP_PROJECT_TIMEOUT_MS = 60_000
const SETUP_PROJECT_DEV_WARN_INTERVAL_MS = 10_000

export type CreateAppResponse = {
  authenticated: boolean
  setupComplete: boolean
  error?: string
  login?: (loginParameters: {
    email: string
    password: string
  }) => Promise<LoginResponse>
  spaceId?: string
  code?: string
}
export type LoginResponse = {
  success: boolean
  token?: string
  error?: string
  message?: string
  redirect?: () => void
}

const appSessionId = nanoid()
const eventbus: EventEmitter = new EventEmitter(eventWhitelist, eventWildcards)
let axiosClient: AxiosInstance
let socket: Socket
let corePluginApi: CorePluginApi
let store: RacletteStore = createStore(eventbus, appSessionId)
const initPluginRegistration = async () => {
  if (corePluginApi && !Object.keys(registeredPlugins).length) {
    await registerPlugins(corePluginApi)
  }
}
const createRacletteApp = async (
  options: { requireAuth?: boolean; sessionId?: string } = {
    requireAuth: true,
  },
): Promise<CreateAppResponse> => {
  setupHttpClient()
  if (options.sessionId) {
    setWorkSessionId(options.sessionId)
  } else {
    setWorkSessionId(null)
  }

  let user: User | null = null
  await configService.init()

  if (options.requireAuth && !options.sessionId) {
    const { authenticated, error, user: _user, code } = await checkAuthStatus()

    if (!authenticated) {
      return {
        authenticated,
        error,
        setupComplete: false,
        login: generateLoginCallback(),
        code,
      }
    }

    user = _user!
  }

  await setupDependencies(options.sessionId)

  store.dispatch({ type: "user/set", payload: user })

  setServerConnecting()

  return new Promise<CreateAppResponse>((resolve, reject) => {
    let settled = false

    const onSetupProject = (spaceId: string) => {
      if (settled) {
        return
      }

      settled = true
      eventbus.removeListener("ui/setupProject", onSetupProject)
      resolve({
        authenticated: !!user || !!options.sessionId,
        setupComplete: true,
        spaceId,
      })
      console.log(chadsayFE("racletteJS is up and running!"))
    }

    eventbus.on("ui/setupProject", onSetupProject)

    void (async () => {
      await awaitTime(SETUP_PROJECT_WARN_MS)
      if (settled) {
        return
      }

      console.warn(
        "[bootstrap] Still waiting for ui/setupProject (socket bootstrap). " +
          "Check server joinConfirmation / projectInit in the network tab.",
      )

      if (isDevelopmentMode()) {
        while (!settled) {
          await awaitTime(SETUP_PROJECT_DEV_WARN_INTERVAL_MS)
          if (settled) {
            return
          }
          console.warn(
            "[bootstrap] Bootstrap still pending (dev mode — no timeout).",
          )
        }
        return
      }

      await awaitTime(SETUP_PROJECT_TIMEOUT_MS - SETUP_PROJECT_WARN_MS)
      if (settled) {
        return
      }

      settled = true
      eventbus.removeListener("ui/setupProject", onSetupProject)
      reject(
        new Error(
          `createRacletteApp timed out after ${SETUP_PROJECT_TIMEOUT_MS / 1000}s waiting for ui/setupProject`,
        ),
      )
    })()
  })
}

const setupHttpClient = () => {
  axiosClient = httpClient.axios
  const token = getCookie(AUTH_TOKEN_KEY)

  configureClient(API_BASE_URL, appSessionId, token)
}

const handleSuccessfulLogin = (token) => {
  const expiryDate = new Date()

  const expiryDays = configService.getAuthTokenExpiryDays()
  expiryDate.setDate(expiryDate.getDate() + expiryDays)
  document.cookie = `${AUTH_TOKEN_KEY}=${token}; expires=${expiryDate.toUTCString()}; path=/; SameSite=Strict`

  setHttpToken(token)
}

const generateLoginCallback =
  () =>
  async (
    loginParameters: { email: string; password: string },
    callbackUrl?: string,
  ): Promise<LoginResponse> => {
    try {
      const _callbackUrl = callbackUrl || window.location.origin

      // Make the login request to the backend
      const response = await axiosClient.post(
        "/core/user/login",
        loginParameters,
      )

      if (response.data && response.data.token) {
        const { token } = response.data

        // store token
        handleSuccessfulLogin(token)

        // Setup all dependencies now that we have authentication
        setupDependencies()

        store.dispatch({ type: "user/set", payload: response.data.user })

        // Return success with token & redirect function
        return {
          success: true,
          token: token,
          redirect: () => {
            const params = new URLSearchParams(window.location.search)
            const redirect = params.get("redirect") || ""
            // If we're in the same origin, we keep the history
            if (
              _callbackUrl.startsWith(window.location.origin) ||
              _callbackUrl.startsWith("/")
            ) {
              if (redirect.length && redirect.startsWith("/:")) {
                window.location.href = _callbackUrl + redirect
              } else {
                window.location.reload()
              }
            } else {
              // Otherwise do a full page reload to the callback URL
              window.location.replace(_callbackUrl)
            }
          },
        }
      } else {
        // Handle successful response but missing token
        return {
          success: false,
          // TODO: Constants would be nice here
          error: "Invalid response: Token not found",
          message: "Authentication failed: Invalid server response",
        }
      }
    } catch (error: unknown) {
      const axiosError = error as AxiosError<{
        error?: string
        message?: string
      }>

      // Extract error message from response if available
      // TODO: Constants would be nice here
      const errorMessage =
        axiosError.response?.data?.message ||
        axiosError.response?.data?.error ||
        "Authentication failed"

      return {
        success: false,
        error: errorMessage,
        message: errorMessage,
      }
    }
  }

const getCookie = (name: string): string | null => {
  const match = document.cookie.match(new RegExp("(^| )" + name + "=([^;]+)"))

  return match && match[2] ? match[2] : null
}

const checkAuthStatus = async (): Promise<{
  authenticated: boolean
  error?: string
  user?: User
  code?: string
}> => {
  try {
    const { status, data: user } = await axiosClient.get(
      "/core/user/check-token",
    )
    checkToken({ status, response: user })
    return { authenticated: status === 200, user }
  } catch (error: unknown) {
    const axiosError = error as AxiosError<{
      error?: string
      message?: string
      code?: string
    }>

    console.error("Auth validation failed:", axiosError)

    checkToken({
      status: axiosError.status,
      response: axiosError?.response?.data,
    })
    return {
      authenticated: false,
      error: axiosError.response?.data.error,
      code: axiosError.response?.data.code,
    }
  }
}

const setupDependencies = async (sessionId?: string) => {
  registerStoreEffects(store)

  // api client - remaining config
  configureHttpInterceptors(store)
  apiHealthcheck(store)

  // socket — replace connection; detach old listeners before disconnect to avoid
  // a stale disconnect handler marking the server offline after the new socket connects
  const previousSocket = socket

  socket = createSocket(SOCKET_URL, appSessionId, { sessionId })
  setActiveSocket(socket)
  setServerConnecting()
  registerSocketClient(socket, eventbus)
  registerSocketEvents(socket, store, eventbus)

  if (previousSocket) {
    teardownSocket(previousSocket)
  }

  if (socket.connected) {
    eventbus.emit("socket/join/room")
  }

  // TODO: improve this code
  corePluginApi = {
    $store: store,
    $api: httpClient,
    $eventbus: eventbus,
    $socket: socket,
    $log,
  }

  return { appSessionId, store, httpClient, eventbus, socket }
}

export default createRacletteApp

export {
  appSessionId,
  store as $store,
  httpClient as $api,
  eventbus as $eventbus,
  socket as $socket,
  corePluginApi,
  initPluginRegistration,
}
