import { getSurrealDB, serializeData, withRetry } from "./db";
import { nanoid } from "nanoid";
import type { User, Session, Company } from "$lib/types";
import { createCompanyFolder, generateCompanyFolderName } from "./cloudflare";

const SESSION_DURATION_DAYS = 30;

// Lazy-loaded bcrypt to avoid blocking on import
let _bcrypt: typeof import("bcrypt") | null = null;
async function getBcrypt() {
  if (!_bcrypt) {
    _bcrypt = await import("bcrypt");
  }
  return _bcrypt.default || _bcrypt;
}

/**
 * Hash un mot de passe avec Bun
 */
export async function hashPassword(password: string): Promise<string> {
  // Prefer Bun's built-in password API when available; fallback to bcrypt under Node
  const bunPwd = (globalThis as any)?.Bun?.password;
  if (bunPwd?.hash) {
    return await bunPwd.hash(password, {
      algorithm: "bcrypt",
      cost: 10,
    });
  }
  const bcrypt = await getBcrypt();
  return await bcrypt.hash(password, 10);
}

/**
 * Vérifie un mot de passe
 */
export async function verifyPassword(password: string, hash: string): Promise<boolean> {
  const bunPwd = (globalThis as any)?.Bun?.password;
  if (bunPwd?.verify) {
    return await bunPwd.verify(password, hash);
  }
  const bcrypt = await getBcrypt();
  return await bcrypt.compare(password, hash);
}

/**
 * Génère un token de session
 */
export function generateSessionToken(): string {
  return nanoid(64);
}

/**
 * Crée une company et un utilisateur associé (owner)
 */
export async function createUserWithCompany(data: {
  user: {
    email: string;
    password: string;
    first_name: string;
    last_name: string;
    phone?: string;
    job_title?: string;
  };
  company: {
    name: string;
    legal_type: string;
    account_type: "advertiser" | "agency" | "broadcaster" | "provider";
    siret?: string;
    rna?: string;
    address?: string;
    postal_code?: string;
    city?: string;
    phone?: string;
    email?: string;
  };
}): Promise<{ success: true; user: User; company: Company } | { success: false; error: string }> {
  const db = await getSurrealDB();
  
  // Vérifier si l'email existe déjà
  const existingUser = await db.query<[User[]]>(
    `SELECT * FROM user WHERE email = $email`,
    { email: data.user.email.toLowerCase() }
  );
  
  if (existingUser[0]?.length > 0) {
    return { success: false, error: "Cette adresse email est déjà utilisée" };
  }
  
  // Vérifier si le SIRET existe déjà
  if (data.company.siret) {
    const existingSiret = await db.query<[Company[]]>(
      `SELECT * FROM company WHERE siret = $siret`,
      { siret: data.company.siret }
    );
    
    if (existingSiret[0]?.length > 0) {
      return { success: false, error: "Ce numéro SIRET est déjà enregistré" };
    }
  }
  
  // Vérifier si le RNA existe déjà
  if (data.company.rna) {
    const existingRna = await db.query<[Company[]]>(
      `SELECT * FROM company WHERE rna = $rna`,
      { rna: data.company.rna }
    );
    
    if (existingRna[0]?.length > 0) {
      return { success: false, error: "Ce numéro RNA est déjà enregistré" };
    }
  }
  
  try {
    // Générer le nom de dossier R2 pour l'entreprise
    const r2FolderName = generateCompanyFolderName(data.company.name, data.company.siret);
    
    // Créer la company
    const companyContent: Record<string, any> = {
      name: data.company.name,
      legal_type: data.company.legal_type,
      account_type: data.company.account_type,
      is_active: true,
      r2_folder_name: r2FolderName, // Stocker le nom du dossier R2
    };
    
    if (data.company.siret) companyContent.siret = data.company.siret;
    if (data.company.rna) companyContent.rna = data.company.rna;
    if (data.company.address) companyContent.address = data.company.address;
    if (data.company.postal_code) companyContent.postal_code = data.company.postal_code;
    if (data.company.city) companyContent.city = data.company.city;
    if (data.company.phone) companyContent.phone = data.company.phone;
    if (data.company.email) companyContent.email = data.company.email;
    
    const companyResult = await db.query<[Company[]]>(
      `CREATE company CONTENT $content`,
      { content: companyContent }
    );
    
    const company = companyResult[0]?.[0];
    if (!company) {
      return { success: false, error: "Erreur lors de la création de la société" };
    }
    
    // Créer le dossier R2 pour l'entreprise (avec sous-dossier ads/)
    try {
      await createCompanyFolder(data.company.name, data.company.siret);
      console.log(`[Auth] Created R2 folder for company: ${r2FolderName}`);
    } catch (r2Error) {
      console.error(`[Auth] Failed to create R2 folder for company ${company.id}:`, r2Error);
      // On ne bloque pas la création de l'entreprise si le dossier R2 échoue
    }
    
    // Créer l'utilisateur avec lien vers la company
    const password_hash = await hashPassword(data.user.password);
    
    const userContent: Record<string, any> = {
      email: data.user.email.toLowerCase(),
      password_hash,
      first_name: data.user.first_name,
      last_name: data.user.last_name,
      company: company.id,
      company_role: "owner",
      role: "user",
      is_active: true,
      email_verified: false,
    };
    
    if (data.user.phone) userContent.phone = data.user.phone;
    if (data.user.job_title) userContent.job_title = data.user.job_title;
    
    const userResult = await db.query<[User[]]>(
      `CREATE user CONTENT $content`,
      { content: userContent }
    );
    
    const user = userResult[0]?.[0];
    if (!user) {
      // Rollback: supprimer la company créée
      await db.query(`DELETE $company`, { company: company.id });
      return { success: false, error: "Erreur lors de la création de l'utilisateur" };
    }
    
    return {
      success: true,
      user: serializeData(user),
      company: serializeData(company),
    };
  } catch (error) {
    console.error("Erreur createUserWithCompany:", error);
    return { success: false, error: "Une erreur est survenue lors de l'inscription" };
  }
}

/**
 * Crée un nouvel utilisateur
 */
export async function createUser(data: {
  email: string;
  password: string;
  first_name: string;
  last_name: string;
  company?: string;
  phone?: string;
  role?: "client" | "admin";
}): Promise<User | null> {
  const db = await getSurrealDB();
  
  // Vérifier si l'email existe déjà
  const existing = await db.query<[User[]]>(
    `SELECT * FROM user WHERE email = $email`,
    { email: data.email.toLowerCase() }
  );
  
  if (existing[0]?.length > 0) {
    return null; // Email déjà utilisé
  }
  
  const password_hash = await hashPassword(data.password);
  
  // Construire l'objet de contenu sans les champs optionnels vides
  const content: Record<string, any> = {
    email: data.email.toLowerCase(),
    password_hash,
    first_name: data.first_name,
    last_name: data.last_name,
    role: data.role || "client",
    is_active: true,
    email_verified: false,
  };
  
  // Ajouter les champs optionnels seulement s'ils ont une valeur
  if (data.company) content.company = data.company;
  if (data.phone) content.phone = data.phone;
  
  const result = await db.query<[User[]]>(
    `CREATE user CONTENT $content`,
    { content }
  );
  
  return serializeData(result[0]?.[0]) || null;
}

/**
 * Authentifie un utilisateur et crée une session
 */
export async function authenticateUser(
  email: string,
  password: string,
  metadata?: { ip_address?: string; user_agent?: string }
): Promise<{ user: User; session: Session; token: string; company?: any } | null> {
  const db = await getSurrealDB();
  
  // Récupérer l'utilisateur avec sa company
  const result = await db.query<[(User & { password_hash: string })[]]>(
    `SELECT *, company.* as company_data FROM user WHERE email = $email AND is_active = true`,
    { email: email.toLowerCase() }
  );
  
  const user = result[0]?.[0];
  if (!user) return null;
  
  // Vérifier le mot de passe
  const isValid = await verifyPassword(password, user.password_hash);
  if (!isValid) return null;
  
  // Créer une session
  const token = generateSessionToken();
  const expires_at = new Date();
  expires_at.setDate(expires_at.getDate() + SESSION_DURATION_DAYS);
  
  // Extraire l'ID sans le préfixe "user:"
  const oderId = String(user.id).replace("user:", "");
  
  const sessionResult = await db.query<[Session[]]>(
    `CREATE session CONTENT {
      user: type::thing("user", $userId),
      session_token: $session_token,
      expires_at: type::datetime($expires_at),
      ip_address: $ip_address,
      user_agent: $user_agent,
      created_at: time::now()
    }`,
    {
      userId: oderId,
      session_token: token,
      expires_at: expires_at.toISOString(),
      ip_address: metadata?.ip_address || null,
      user_agent: metadata?.user_agent || null,
    }
  );
  
  const session = sessionResult[0]?.[0];
  if (!session) return null;
  
  // Retirer le password_hash de l'objet user
  const { password_hash: _, company_data, ...safeUser } = user as any;
  
  return {
    user: serializeData(safeUser) as User,
    session: serializeData(session),
    token,
    company: company_data ? serializeData(company_data) : undefined,
  };
}

/**
 * Récupère une session et son utilisateur par token
 */
export async function getSessionByToken(token: string): Promise<{ user: User; session: Session; company?: Company } | null> {
  return withRetry(async (db) => {
    const result = await db.query<[Array<Session & { user_data: User }>]>(
      `SELECT *, 
        (SELECT id, email, first_name, last_name, company, company_role, phone, role, avatar_url, is_active, email_verified, created_at, updated_at 
         FROM user WHERE id = $parent.user)[0] AS user_data
       FROM session 
       WHERE session_token = $session_token AND expires_at > time::now()`,
      { session_token: token }
    );
    
    const session = result[0]?.[0];
    if (!session || !session.user_data) return null;
    
    // Vérifier que l'utilisateur est actif
    if (!session.user_data.is_active) return null;
    
    const { user_data, ...sessionData } = session;
    
    // Récupérer les données de la company si l'utilisateur en a une
    let company_data = undefined;
    if (user_data.company) {
      const companyId = typeof user_data.company === 'string' 
        ? user_data.company.replace('company:', '') 
        : user_data.company.id || user_data.company;
      
      const companyResult = await db.query<[Company[]]>(
        'SELECT * FROM type::thing("company", $companyId)',
        { companyId }
      );
      
      if (companyResult[0]?.[0]) {
        company_data = companyResult[0][0];
      }
    }
    
    return {
      user: serializeData(user_data),
      session: serializeData(sessionData) as Session,
      company: company_data ? serializeData(company_data) as Company : undefined,
    };
  });
}

/**
 * Supprime une session (déconnexion)
 */
export async function deleteSession(token: string): Promise<void> {
  const db = await getSurrealDB();
  await db.query(`DELETE FROM session WHERE session_token = $session_token`, { session_token: token });
}

/**
 * Supprime toutes les sessions d'un utilisateur
 */
export async function deleteAllUserSessions(userId: string): Promise<void> {
  const db = await getSurrealDB();
  const id = userId.includes(":") ? userId.split(":")[1] : userId;
  await db.query(
    `DELETE FROM session WHERE user = type::thing("user", $userId)`,
    { userId: id }
  );
}

/**
 * Récupère un utilisateur par ID
 */
export async function getUserById(userId: string): Promise<User | null> {
  const db = await getSurrealDB();
  const id = userId.includes(":") ? userId : `user:${userId}`;
  
  const result = await db.query<[User[]]>(
    `SELECT id, email, first_name, last_name, company, phone, role, avatar_url, is_active, email_verified, created_at, updated_at 
     FROM $id`,
    { id }
  );
  
  return serializeData(result[0]?.[0]) || null;
}

/**
 * Met à jour un utilisateur
 */
export async function updateUser(userId: string, data: Partial<User>): Promise<User | null> {
  const db = await getSurrealDB();
  const id = userId.includes(":") ? userId : `user:${userId}`;
  
  // Construire dynamiquement la requête de mise à jour
  const updates: string[] = [];
  const params: Record<string, unknown> = { id };
  
  const allowedFields = ["first_name", "last_name", "company", "phone", "avatar_url"];
  
  for (const field of allowedFields) {
    if (field in data) {
      updates.push(`${field} = $${field}`);
      params[field] = data[field as keyof User];
    }
  }
  
  if (updates.length === 0) return null;
  
  updates.push("updated_at = time::now()");
  
  const result = await db.query<[User[]]>(
    `UPDATE $id SET ${updates.join(", ")} RETURN AFTER`,
    params
  );
  
  return serializeData(result[0]?.[0]) || null;
}

/**
 * Change le mot de passe d'un utilisateur
 */
export async function changePassword(userId: string, newPassword: string): Promise<boolean> {
  const db = await getSurrealDB();
  const id = userId.includes(":") ? userId : `user:${userId}`;
  
  const password_hash = await hashPassword(newPassword);
  
  const result = await db.query<[User[]]>(
    `UPDATE $id SET password_hash = $password_hash, updated_at = time::now() RETURN AFTER`,
    { id, password_hash }
  );
  
  return !!result[0]?.[0];
}
/**
 * Supprime un utilisateur et toutes ses données associées
 */
export async function deleteUser(userId: string): Promise<boolean> {
  const db = await getSurrealDB();
  const id = userId.includes(":") ? userId : `user:${userId}`;
  
  try {
    // Supprimer toutes les sessions de l'utilisateur
    await deleteAllUserSessions(id);
    
    // Supprimer l'utilisateur
    await db.query(`DELETE $id`, { id });
    
    return true;
  } catch (error) {
    console.error("Erreur lors de la suppression de l'utilisateur:", error);
    return false;
  }
}

// ============================================
// MULTI-SESSION MANAGEMENT
// ============================================

export interface MultiSession {
  id: string;
  browser_id: string; // Identifiant unique du navigateur
  linked_sessions: string[]; // Tokens des sessions liées
  active_session_token: string; // Token de la session actuellement active
  created_at: string;
  updated_at: string;
}

/**
 * Crée une multi-session pour gérer plusieurs comptes
 */
export async function createMultiSession(
  initialSessionToken: string,
  browserId?: string
): Promise<{ multiSession: MultiSession; token: string } | null> {
  const db = await getSurrealDB();
  
  const browser_id = browserId || nanoid(32);
  const multi_session_token = nanoid(64);
  
  const result = await db.query<[MultiSession[]]>(
    `CREATE multi_session CONTENT {
      browser_id: $browser_id,
      linked_sessions: [$session_token],
      active_session_token: $session_token,
      created_at: time::now(),
      updated_at: time::now()
    }`,
    {
      browser_id,
      session_token: initialSessionToken
    }
  );
  
  const multiSession = result[0]?.[0];
  if (!multiSession) return null;
  
  return {
    multiSession: serializeData(multiSession),
    token: multi_session_token
  };
}

/**
 * Récupère une multi-session avec toutes les infos des comptes
 */
export async function getMultiSessionData(
  browserId: string
): Promise<{ multiSession: MultiSession; accounts: Array<{ user: User; company?: Company; session: Session }> } | null> {
  const multiSession = await withRetry(async (db) => {
    const result = await db.query<[MultiSession[]]>(
      `SELECT * FROM multi_session WHERE browser_id = $browser_id LIMIT 1`,
      { browser_id: browserId }
    );
    return result[0]?.[0] || null;
  });
  
  if (!multiSession) return null;
  
  // Récupérer toutes les sessions liées avec leurs users EN PARALLÈLE pour éviter les timeouts
  const sessionPromises = multiSession.linked_sessions.map(sessionToken => 
    getSessionByToken(sessionToken).catch(() => null)
  );
  
  const sessionResults = await Promise.all(sessionPromises);
  
  const accounts: Array<{ user: User; company?: Company; session: Session }> = [];
  const validSessionTokens: string[] = [];
  
  for (let i = 0; i < sessionResults.length; i++) {
    const sessionData = sessionResults[i];
    if (sessionData) {
      accounts.push({
        user: sessionData.user,
        company: sessionData.company,
        session: sessionData.session
      });
      validSessionTokens.push(multiSession.linked_sessions[i]);
    }
  }
  
  // Si des sessions ont expiré, nettoyer la multi-session
  if (validSessionTokens.length !== multiSession.linked_sessions.length) {
    try {
      // Mettre à jour la multi-session avec seulement les sessions valides
      if (validSessionTokens.length === 0) {
        // Plus aucune session valide, supprimer la multi-session
        await withRetry(async (db) => {
          await db.query(`DELETE FROM multi_session WHERE browser_id = $browser_id`, { browser_id: browserId });
        });
        return null;
      } else {
        // Mettre à jour avec les sessions valides
        const newActiveToken = validSessionTokens.includes(multiSession.active_session_token)
          ? multiSession.active_session_token
          : validSessionTokens[0];
        
        await withRetry(async (db) => {
          await db.query(
            `UPDATE multi_session 
             SET linked_sessions = $sessions, active_session_token = $active_token, updated_at = time::now()
             WHERE browser_id = $browser_id`,
            {
              browser_id: browserId,
              sessions: validSessionTokens,
              active_token: newActiveToken
            }
          );
        });
        
        multiSession.linked_sessions = validSessionTokens;
        multiSession.active_session_token = newActiveToken;
      }
    } catch (cleanupError) {
      console.error('Erreur lors du nettoyage des sessions expirées:', cleanupError);
    }
  }
  
  return {
    multiSession: serializeData(multiSession),
    accounts
  };
}

/**
 * Ajoute une session à une multi-session existante
 */
export async function addSessionToMultiSession(
  browserId: string,
  newSessionToken: string
): Promise<MultiSession | null> {
  const db = await getSurrealDB();
  
  // Vérifier que la session n'est pas déjà dans une multi-session
  const existingCheck = await db.query<[MultiSession[]]>(
    `SELECT * FROM multi_session WHERE $session_token IN linked_sessions`,
    { session_token: newSessionToken }
  );
  
  if (existingCheck[0]?.length > 0) {
    // Session déjà liée, ne rien faire
    return null;
  }
  
  const result = await db.query<[MultiSession[]]>(
    `UPDATE multi_session 
     SET linked_sessions += $session_token, updated_at = time::now()
     WHERE browser_id = $browser_id
     RETURN AFTER`,
    {
      browser_id: browserId,
      session_token: newSessionToken
    }
  );
  
  return serializeData(result[0]?.[0]) || null;
}

/**
 * Change la session active dans une multi-session
 */
export async function switchActiveSession(
  browserId: string,
  newActiveToken: string
): Promise<MultiSession | null> {
  const db = await getSurrealDB();
  
  // Vérifier que le token fait partie des sessions liées
  const multiSession = await db.query<[MultiSession[]]>(
    `SELECT * FROM multi_session WHERE browser_id = $browser_id LIMIT 1`,
    { browser_id: browserId }
  );
  
  const ms = multiSession[0]?.[0];
  if (!ms || !ms.linked_sessions.includes(newActiveToken)) {
    return null;
  }
  
  const result = await db.query<[MultiSession[]]>(
    `UPDATE multi_session 
     SET active_session_token = $session_token, updated_at = time::now()
     WHERE browser_id = $browser_id
     RETURN AFTER`,
    {
      browser_id: browserId,
      session_token: newActiveToken
    }
  );
  
  return serializeData(result[0]?.[0]) || null;
}

/**
 * Retire une session d'une multi-session
 */
export async function removeSessionFromMultiSession(
  browserId: string,
  sessionToken: string
): Promise<MultiSession | null> {
  const db = await getSurrealDB();
  
  const multiSession = await db.query<[MultiSession[]]>(
    `SELECT * FROM multi_session WHERE browser_id = $browser_id LIMIT 1`,
    { browser_id: browserId }
  );
  
  const ms = multiSession[0]?.[0];
  if (!ms) return null;
  
  const updatedSessions = ms.linked_sessions.filter((t: string) => t !== sessionToken);
  
  // Si c'était la dernière session, supprimer la multi-session
  if (updatedSessions.length === 0) {
    await db.query(`DELETE multi_session WHERE browser_id = $browser_id`, { browser_id: browserId });
    return null;
  }
  
  // Si on retire la session active, basculer sur la première disponible
  const newActiveToken = ms.active_session_token === sessionToken 
    ? updatedSessions[0] 
    : ms.active_session_token;
  
  const result = await db.query<[MultiSession[]]>(
    `UPDATE multi_session 
     SET linked_sessions = $sessions, 
         active_session_token = $active_token,
         updated_at = time::now()
     WHERE browser_id = $browser_id
     RETURN AFTER`,
    {
      browser_id: browserId,
      sessions: updatedSessions,
      active_token: newActiveToken
    }
  );
  
  // Supprimer la session de la base
  await deleteSession(sessionToken);
  
  return serializeData(result[0]?.[0]) || null;
}