import User from '#models/user'
import Organization from '#models/organization'
import { provisionDefaultPlans } from '#services/default_plans'
import { issueRefreshToken } from '#services/refresh_token'
import db from '@adonisjs/lucid/services/db'
import crypto from 'node:crypto'
import type { HttpContext } from '@adonisjs/core/http'

export type SsoProvider = 'google' | 'microsoft'

/**
 * Identité minimale extraite d'un provider OAuth (cf. AllyUserContract).
 * Une seule shape pour tous les providers — on normalise au boundary du
 * driver Ally vers ce type.
 */
export type SsoIdentity = {
  provider: SsoProvider
  /** sub stable du provider (google_id ou microsoft_id en DB). */
  providerId: string
  /** Email — peut être null pour certains comptes Microsoft, dans ce cas
   *  on le rejette en amont. */
  email: string
  /** Nom complet si dispo. */
  fullName: string | null
}

/**
 * Trouve ou crée l'utilisateur pour une identité SSO :
 *  1. Match par `<provider>_id` (canonique)
 *  2. Sinon match par email → lie l'ID provider au compte existant
 *  3. Sinon crée un nouvel user + org + plans par défaut
 *
 * Retourne `{ user, isNewUser }`. Aux callers de poser le refresh cookie
 * et de décider la redirection.
 */
export async function findOrCreateUserFromSso(
  identity: SsoIdentity
): Promise<{ user: User; isNewUser: boolean }> {
  const idColumn = identity.provider === 'google' ? 'googleId' : 'microsoftId'

  // 1. Match canonique provider_id
  let user = await User.findBy(idColumn, identity.providerId)
  if (user) return { user, isNewUser: false }

  // 2. Match email — première connexion via ce provider d'un user existant.
  user = await User.findBy('email', identity.email.toLowerCase())
  if (user) {
    ;(user as unknown as Record<string, unknown>)[idColumn] = identity.providerId
    if (!user.fullName && identity.fullName) {
      user.fullName = identity.fullName
    }
    await user.save()
    return { user, isNewUser: false }
  }

  // 3. Création : nouvelle org + plans par défaut + user (mdp aléatoire
  //    inutilisable, le user pourra activer email/password via "mdp oublié").
  user = await db.transaction(async (trx) => {
    const org = await Organization.create({ name: '' }, { client: trx })
    await provisionDefaultPlans(org.id, trx)
    return User.create(
      {
        email: identity.email.toLowerCase(),
        fullName: identity.fullName,
        password: crypto.randomBytes(48).toString('base64url'),
        [idColumn]: identity.providerId,
        organizationId: org.id,
      } as unknown as Partial<User>,
      { client: trx }
    )
  })
  return { user, isNewUser: true }
}

/**
 * Calcule la prochaine étape d'onboarding pour un user qui vient de se
 * connecter via SSO. La route SPA cible cette URL (passée via ?next=...).
 *
 *  - new user                → /onboarding/entreprise (compte déjà rempli
 *                              par les infos SSO, pas besoin de cette étape)
 *  - org name vide           → /onboarding/entreprise
 *  - signature manquante     → /onboarding/signature
 *  - sinon                   → / (dashboard)
 */
export async function nextRouteAfterSso(
  user: User,
  isNewUser: boolean
): Promise<string> {
  if (isNewUser) return '/onboarding/entreprise'
  const org = user.organizationId ? await Organization.find(user.organizationId) : null
  if (!org || !org.name) return '/onboarding/entreprise'
  if (!user.signature) return '/onboarding/signature'
  return '/'
}

/**
 * Pose le refresh cookie httpOnly puis redirige vers le SPA en passant
 * la route cible. Utilisé en fin de callback SSO.
 */
export async function emitSsoSessionAndRedirect(
  ctx: HttpContext,
  user: User,
  next: string,
  webUrl: string
): Promise<void> {
  await issueRefreshToken(user, ctx)
  ctx.response.redirect(`${webUrl}/auth/sso/complete?next=${encodeURIComponent(next)}`)
}
