"use server"
import { generateTokenForVsCode } from "@/app/(dashboard)/apikeys/tokenfuncs"
import { getUserOrganizations } from "@/components/dashboard/action"
import { getUserId } from "@/app/utils/auth"
import crypto from "crypto"
import jwt from "jsonwebtoken"
import { cookies } from "next/headers"
import { organizationMemberRepository } from "@codeflash-ai/common"
import { getRedis } from "@/lib/redis"

const RATE_LIMIT = 5
const RATE_LIMIT_WINDOW_SECONDS = 60

function getJwtSecret(): string {
  const secret = process.env.JWT_SECRET
  if (!secret) {
    throw new Error("JWT_SECRET environment variable is required")
  }
  return secret
}

interface OAuthStatePayload {
  userId: string
  redirectUri: string
  codeChallenge: string
  codeChallengeMethod: string
  clientId: string
  orgId?: string
  type: "oauth_state"
}

interface AuthCodePayload {
  userId: string
  codeChallenge: string
  codeChallengeMethod: string
  redirectUri: string
  clientId: string
  orgId?: string
  type: "auth_code"
}

export interface Organization {
  id: string
  name: string
  role: "admin" | "owner" | "member"
  avatarUrl?: string
}

export interface UserInfo {
  id: string
  name: string
  email: string
  avatarUrl?: string
}

export async function fetchUserInfo(): Promise<{
  user?: UserInfo
  error?: string
}> {
  try {
    const { auth0 } = await import("@/lib/auth0")
    const session = await auth0.getSession()

    if (!session?.user) {
      return { error: "Unauthorized" }
    }

    return {
      user: {
        id: session.user.sub,
        name: session.user.name || session.user.nickname || "User",
        email: session.user.email || "",
        avatarUrl: session.user.picture,
      },
    }
  } catch {
    return { error: "Failed to fetch user info" }
  }
}

export async function fetchUserOrganizations(): Promise<{
  organizations?: Organization[]
  error?: string
}> {
  try {
    const userId = await getUserId()
    if (!userId) {
      return { error: "Unauthorized" }
    }

    const result = await getUserOrganizations(userId)
    if (!result.success) {
      return { error: result.error || "Failed to fetch organizations" }
    }

    return { organizations: result.organizations }
  } catch {
    return { error: "Failed to fetch organizations" }
  }
}

const OAUTH_COOKIE_NAME = "oauth_params"

interface OAuthParams {
  redirectUri: string
  codeChallenge: string
  codeChallengeMethod: string
  clientId: string
  vscodeState: string
}

const ALLOWED_CODE_CHALLENGE_METHODS = new Set(["S256", "sha256"])

const ALLOWED_CLIENT_IDS = new Set(["cf_vscode_app", "cf-cli-app"])

const ALLOWED_REDIRECT_URI_PATTERNS = [
  /^vscode:\/\/codeflash\.codeflash\//,
  /^http:\/\/localhost(:\d+)?\//, // local dev callbacks
]

function isAllowedRedirectUri(uri: string): boolean {
  return ALLOWED_REDIRECT_URI_PATTERNS.some(pattern => pattern.test(uri))
}

export async function storeOAuthParams(params: OAuthParams): Promise<{ error?: string }> {
  try {
    if (!ALLOWED_CODE_CHALLENGE_METHODS.has(params.codeChallengeMethod)) {
      return { error: "Invalid code challenge method" }
    }
    if (!ALLOWED_CLIENT_IDS.has(params.clientId)) {
      return { error: "Invalid client application" }
    }
    if (!isAllowedRedirectUri(params.redirectUri)) {
      return { error: "Invalid redirect URI" }
    }

    const userId = await getUserId()
    if (!userId) {
      return { error: "Unauthorized" }
    }

    const signed = jwt.sign({ ...params, type: "oauth_params" }, getJwtSecret(), {
      expiresIn: "10m",
      algorithm: "HS256",
    })

    const cookieStore = await cookies()
    cookieStore.set(OAUTH_COOKIE_NAME, signed, {
      httpOnly: true,
      sameSite: "strict",
      secure: process.env.NODE_ENV !== "development",
      path: "/codeflash/auth",
      maxAge: 600,
    })

    return {}
  } catch {
    return { error: "Failed to store OAuth parameters" }
  }
}

export async function getStoredOAuthParams(): Promise<{
  params?: OAuthParams
  error?: string
}> {
  try {
    const cookieStore = await cookies()
    const cookie = cookieStore.get(OAUTH_COOKIE_NAME)
    if (!cookie?.value) {
      return { error: "Session expired. Please refresh the page and try again." }
    }

    const payload = jwt.verify(cookie.value, getJwtSecret(), {
      algorithms: ["HS256"],
    }) as unknown as OAuthParams & {
      type: string
    }
    if (payload.type !== "oauth_params") {
      return { error: "Invalid session" }
    }

    return {
      params: {
        redirectUri: payload.redirectUri,
        codeChallenge: payload.codeChallenge,
        codeChallengeMethod: payload.codeChallengeMethod,
        clientId: payload.clientId,
        vscodeState: payload.vscodeState,
      },
    }
  } catch {
    return { error: "Session expired. Please refresh the page and try again." }
  }
}

async function clearOAuthCookie() {
  const cookieStore = await cookies()
  cookieStore.delete(OAUTH_COOKIE_NAME)
}

export async function isRateLimited(userId: string): Promise<boolean> {
  const redis = getRedis()
  const key = `rate_limit:vsc_signin:${userId}`
  const pipeline = redis.pipeline()
  pipeline.incr(key)
  pipeline.expire(key, RATE_LIMIT_WINDOW_SECONDS)
  const results = await pipeline.exec()
  const count = (results?.[0]?.[1] as number) ?? 1
  return count > RATE_LIMIT
}

async function markJtiUsed(jti: string, ttlSeconds: number): Promise<boolean> {
  const redis = getRedis()
  const key = `jti:${jti}`
  const wasSet = await redis.set(key, "1", "EX", ttlSeconds, "NX")
  return wasSet === "OK"
}

export async function createOAuthState(params: {
  redirectUri: string
  codeChallenge: string
  codeChallengeMethod: string
  clientId: string
  orgId?: string
}): Promise<{ state: string; error?: string }> {
  try {
    const userId = await getUserId()
    if (!userId) {
      return { state: "", error: "Unauthorized" }
    }
    if (params.orgId) {
      const isMember = await organizationMemberRepository.isMember(params.orgId, userId)
      if (!isMember) {
        return { state: "", error: "Unauthorized" }
      }
    }

    const limited = await isRateLimited(userId)
    if (limited) {
      return { state: "", error: "Rate limit exceeded" }
    }

    const statePayload: OAuthStatePayload = {
      userId,
      redirectUri: params.redirectUri,
      codeChallenge: params.codeChallenge,
      codeChallengeMethod: params.codeChallengeMethod,
      clientId: params.clientId,
      orgId: params.orgId,
      type: "oauth_state",
    }

    const state = jwt.sign(statePayload, getJwtSecret(), {
      expiresIn: "2m",
      algorithm: "HS256",
      jwtid: crypto.randomBytes(16).toString("hex"),
    })

    return { state }
  } catch {
    return { state: "", error: "Internal server error" }
  }
}

export async function authorizeOAuth(state: string): Promise<{
  code?: string
  redirectUri?: string
  error?: string
}> {
  try {
    const userId = await getUserId()
    if (!userId) {
      return { error: "Unauthorized" }
    }

    let oauthState: OAuthStatePayload
    try {
      oauthState = jwt.verify(state, getJwtSecret(), {
        algorithms: ["HS256"],
      }) as unknown as OAuthStatePayload
    } catch {
      return { error: "Invalid or expired state" }
    }

    if (oauthState.type !== "oauth_state") {
      return { error: "Invalid state token" }
    }

    if (oauthState.userId !== userId) {
      return { error: "User mismatch" }
    }

    const authCodePayload: AuthCodePayload = {
      userId,
      codeChallenge: oauthState.codeChallenge,
      codeChallengeMethod: oauthState.codeChallengeMethod,
      redirectUri: oauthState.redirectUri,
      clientId: oauthState.clientId,
      orgId: oauthState.orgId,
      type: "auth_code",
    }

    const code = jwt.sign(authCodePayload, getJwtSecret(), {
      expiresIn: "2m",
      algorithm: "HS256",
      jwtid: crypto.randomBytes(16).toString("hex"),
    })

    await clearOAuthCookie()

    return {
      code,
      redirectUri: oauthState.redirectUri,
    }
  } catch {
    return { error: "Internal server error" }
  }
}

interface TokenExchangeParams {
  code: string
  codeVerifier: string
  redirectUri: string
  clientId: string
}

export async function exchangeCodeForToken(
  params: TokenExchangeParams,
): Promise<{ accessToken?: string; error?: string }> {
  try {
    let codeData: AuthCodePayload
    try {
      codeData = jwt.verify(params.code, getJwtSecret(), {
        algorithms: ["HS256"],
      }) as unknown as AuthCodePayload
    } catch {
      return { error: "Invalid or expired authorization code" }
    }

    if (codeData.type !== "auth_code") {
      return { error: "Invalid authorization code" }
    }

    // Prevent auth code replay — each jti can only be used once
    const jti = (codeData as unknown as { jti?: string }).jti
    if (!jti || !(await markJtiUsed(jti, 120))) {
      return { error: "Authorization code has already been used" }
    }

    if (codeData.clientId !== params.clientId) {
      return { error: "Client ID mismatch" }
    }

    if (codeData.redirectUri !== params.redirectUri) {
      return { error: "Redirect URI mismatch" }
    }

    if (!ALLOWED_CODE_CHALLENGE_METHODS.has(codeData.codeChallengeMethod)) {
      return { error: "Unsupported code challenge method" }
    }
    const computedChallenge = crypto
      .createHash("sha256")
      .update(params.codeVerifier)
      .digest("base64url")
    if (computedChallenge !== codeData.codeChallenge) {
      return { error: "Code verifier validation failed" }
    }

    try {
      const apiKey = await generateTokenForVsCode(codeData.userId, codeData.orgId)
      return { accessToken: apiKey.token }
    } catch (tokenError: unknown) {
      if (tokenError instanceof Error && tokenError.message === "NEXT_REDIRECT") {
        return { error: "Authentication required" }
      }
      return { error: "Failed to generate API token" }
    }
  } catch {
    return { error: "Internal server error" }
  }
}
