import { eq, and, ne, sql } from "drizzle-orm";
import { drizzle } from "drizzle-orm/mysql2";
import { 
  InsertUser, 
  users, 
  formations, 
  apprenants, 
  sequences, 
  datesFormation,
  inscriptions,
  passwordResetTokens,
  InsertPasswordResetToken,
  emailTemplates,
  emailConfig,
  InsertEmailConfig,
  InsertSequence,
  InsertDateFormation,
  InsertInscription,
  InsertFormation,
  InsertApprenant,
  Sequence,
  DateFormation,
  Apprenant,
  Formation,
  EmailTemplate,
  InsertEmailTemplate,
  formateurs,
  InsertFormateur,
  Formateur,
  rappels,
  InsertRappel,
  Rappel,
  rappelDates,
  logsRappels
} from "../drizzle/schema";
import { ENV } from './_core/env';

let _db: ReturnType<typeof drizzle> | null = null;

// Lazily create the drizzle instance so local tooling can run without a DB.
export async function getDb() {
  if (!_db && process.env.DATABASE_URL) {
    try {
      _db = drizzle(process.env.DATABASE_URL);
    } catch (error) {
      console.warn("[Database] Failed to connect:", error);
      _db = null;
    }
  }
  return _db;
}

export async function upsertUser(user: InsertUser): Promise<void> {
  if (!user.openId) {
    throw new Error("User openId is required for upsert");
  }

  const db = await getDb();
  if (!db) {
    console.warn("[Database] Cannot upsert user: database not available");
    return;
  }

  try {
    const values: InsertUser = {
      openId: user.openId,
    };
    const updateSet: Record<string, unknown> = {};

    const textFields = ["name", "email", "loginMethod"] as const;
    type TextField = (typeof textFields)[number];

    const assignNullable = (field: TextField) => {
      const value = user[field];
      if (value === undefined) return;
      const normalized = value ?? null;
      values[field] = normalized;
      updateSet[field] = normalized;
    };

    textFields.forEach(assignNullable);

    if (user.lastSignedIn !== undefined) {
      values.lastSignedIn = user.lastSignedIn;
      updateSet.lastSignedIn = user.lastSignedIn;
    }
    if (user.role !== undefined) {
      values.role = user.role;
      updateSet.role = user.role;
    } else if (user.openId === ENV.ownerOpenId) {
      values.role = 'admin';
      updateSet.role = 'admin';
    }

    if (!values.lastSignedIn) {
      values.lastSignedIn = new Date();
    }

    if (Object.keys(updateSet).length === 0) {
      updateSet.lastSignedIn = new Date();
    }

    await db.insert(users).values(values).onDuplicateKeyUpdate({
      set: updateSet,
    });
  } catch (error) {
    console.error("[Database] Failed to upsert user:", error);
    throw error;
  }
}

export async function getUserByOpenId(openId: string) {
  const db = await getDb();
  if (!db) {
    console.warn("[Database] Cannot get user: database not available");
    return undefined;
  }

  const result = await db.select().from(users).where(eq(users.openId, openId)).limit(1);

  return result.length > 0 ? result[0] : undefined;
}

// ==================== FORMATIONS ====================

export async function createFormation(data: InsertFormation) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  const result = await db.insert(formations).values(data);
  return result;
}

export async function getFormations() {
  const db = await getDb();
  if (!db) return [];
  
  return await db.select().from(formations);
}

export async function getFormationsByFormateur(formateurId: number) {
  const db = await getDb();
  if (!db) return [];
  
  // Récupérer les IDs de formations uniques pour lesquelles le formateur a des séquences
  const seqs = await db.select({ formationId: sequences.formationId })
    .from(sequences)
    .where(eq(sequences.formateurId, formateurId));
  
  const formationIds = Array.from(new Set(seqs.map(s => s.formationId)));
  
  if (formationIds.length === 0) return [];
  
  // Récupérer les formations correspondantes
  const result = await db.select().from(formations).where(
    sql`${formations.id} IN (${sql.join(formationIds.map(id => sql`${id}`), sql`, `)})`
  );
  
  return result;
}

export async function getFormationById(id: number) {
  const db = await getDb();
  if (!db) return undefined;
  
  const result = await db.select().from(formations).where(eq(formations.id, id)).limit(1);
  return result.length > 0 ? result[0] : undefined;
}

export async function getFormationByLien(lien: string) {
  const db = await getDb();
  if (!db) return undefined;
  
  const result = await db.select().from(formations).where(eq(formations.lienUnique, lien)).limit(1);
  return result.length > 0 ? result[0] : undefined;
}

export async function updateFormation(id: number, data: Partial<InsertFormation>) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  await db.update(formations).set(data).where(eq(formations.id, id));
}

export async function deleteFormation(id: number) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  await db.delete(formations).where(eq(formations.id, id));
}

// ==================== SÉQUENCES ====================

export async function createSequence(data: InsertSequence) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  const result = await db.insert(sequences).values(data);
  return result;
}

export async function getSequences() {
  const db = await getDb();
  if (!db) return [];
  
  return await db.select().from(sequences);
}

export async function getSequencesWithFormation() {
  const db = await getDb();
  if (!db) return [];
  
  const seqs = await db.select().from(sequences);
  
  // Récupérer les informations de formation et de formateur pour chaque séquence
  return await Promise.all(
    seqs.map(async (seq) => {
      const formation = await db.select().from(formations).where(eq(formations.id, seq.formationId)).limit(1);
      
      let formateur = null;
      if (seq.formateurId) {
        const formateurResult = await db.select().from(formateurs).where(eq(formateurs.id, seq.formateurId)).limit(1);
        if (formateurResult.length > 0) {
          formateur = { id: formateurResult[0].id, nom: formateurResult[0].nom };
        }
      }
      
      return {
        ...seq,
        formation: formation.length > 0 ? { id: formation[0].id, nom: formation[0].nom } : null,
        formateur,
      };
    })
  );
}

export async function getSequencesByFormateur(formateurId: number) {
  const db = await getDb();
  if (!db) return [];
  
  // Récupérer uniquement les séquences de ce formateur
  const seqs = await db.select().from(sequences).where(eq(sequences.formateurId, formateurId));
  
  // Récupérer les informations de formation et de formateur pour chaque séquence
  return await Promise.all(
    seqs.map(async (seq) => {
      const formation = await db.select().from(formations).where(eq(formations.id, seq.formationId)).limit(1);
      
      let formateur = null;
      if (seq.formateurId) {
        const formateurResult = await db.select().from(formateurs).where(eq(formateurs.id, seq.formateurId)).limit(1);
        if (formateurResult.length > 0) {
          formateur = { id: formateurResult[0].id, nom: formateurResult[0].nom };
        }
      }
      
      return {
        ...seq,
        formation: formation.length > 0 ? { id: formation[0].id, nom: formation[0].nom } : null,
        formateur,
      };
    })
  );
}

export async function getSequenceById(id: number): Promise<Sequence | undefined> {
  const db = await getDb();
  if (!db) return undefined;
  
  const result = await db.select().from(sequences).where(eq(sequences.id, id)).limit(1);
  return result.length > 0 ? result[0] : undefined;
}

export async function getSequencesByFormation(formationId: number) {
  const db = await getDb();
  if (!db) return [];
  
  return await db.select().from(sequences).where(eq(sequences.formationId, formationId));
}

export async function updateSequence(id: number, data: Partial<InsertSequence>) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  await db.update(sequences).set(data).where(eq(sequences.id, id));
}

export async function deleteSequence(id: number) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  await db.delete(sequences).where(eq(sequences.id, id));
}

// ==================== DATES DE FORMATION ====================

export async function createDateFormation(data: InsertDateFormation) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  const result = await db.insert(datesFormation).values(data);
  return result;
}

export async function getDatesBySequence(sequenceId: number): Promise<DateFormation[]> {
  const db = await getDb();
  if (!db) return [];
  
  const results = await db.select()
    .from(datesFormation)
    .where(eq(datesFormation.sequenceId, sequenceId))
    .orderBy(datesFormation.ordre);
  
  return results;
}

export async function deleteDatesBySequence(sequenceId: number) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  await db.delete(datesFormation).where(eq(datesFormation.sequenceId, sequenceId));
}

// ==================== APPRENANTS ====================

export async function createApprenant(data: InsertApprenant) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  const result = await db.insert(apprenants).values(data);
  return result;
}

export async function getApprenants() {
  const db = await getDb();
  if (!db) return [];
  
  const apprenantsData = await db.select().from(apprenants);
  
  // Pour chaque apprenant, récupérer ses inscriptions
  const apprenantsWithInscriptions = await Promise.all(
    apprenantsData.map(async (apprenant) => {
      const inscriptionsData = await db
        .select()
        .from(inscriptions)
        .where(eq(inscriptions.apprenantId, apprenant.id));
      
      return {
        ...apprenant,
        inscriptions: inscriptionsData,
      };
    })
  );
  
  return apprenantsWithInscriptions;
}

export async function getApprenantsByFormateur(formateurId: number) {
  const db = await getDb();
  if (!db) return [];
  
  // Récupérer les IDs de séquences du formateur
  const seqs = await db.select({ id: sequences.id })
    .from(sequences)
    .where(eq(sequences.formateurId, formateurId));
  
  const sequenceIds = seqs.map(s => s.id);
  
  if (sequenceIds.length === 0) return [];
  
  // Récupérer les IDs d'apprenants inscrits à ces séquences
  const inscriptionsData = await db.select({ apprenantId: inscriptions.apprenantId })
    .from(inscriptions)
    .where(
      sql`${inscriptions.sequenceId} IN (${sql.join(sequenceIds.map(id => sql`${id}`), sql`, `)})`
    );
  
  const apprenantIds = Array.from(new Set(inscriptionsData.map(i => i.apprenantId)));
  
  if (apprenantIds.length === 0) return [];
  
  // Récupérer les apprenants correspondants
  const apprenantsData = await db.select().from(apprenants).where(
    sql`${apprenants.id} IN (${sql.join(apprenantIds.map(id => sql`${id}`), sql`, `)})`
  );
  
  // Pour chaque apprenant, récupérer ses inscriptions (filtrées par les séquences du formateur)
  const apprenantsWithInscriptions = await Promise.all(
    apprenantsData.map(async (apprenant) => {
      const inscriptionsFiltered = await db
        .select()
        .from(inscriptions)
        .where(
          and(
            eq(inscriptions.apprenantId, apprenant.id),
            sql`${inscriptions.sequenceId} IN (${sql.join(sequenceIds.map(id => sql`${id}`), sql`, `)})`
          )
        );
      
      return {
        ...apprenant,
        inscriptions: inscriptionsFiltered,
      };
    })
  );
  
  return apprenantsWithInscriptions;
}

export async function getApprenantById(id: number): Promise<Apprenant | undefined> {
  const db = await getDb();
  if (!db) return undefined;
  
  const result = await db.select().from(apprenants).where(eq(apprenants.id, id)).limit(1);
  return result.length > 0 ? result[0] : undefined;
}

export async function getApprenantByEmail(email: string) {
  const db = await getDb();
  if (!db) return undefined;
  
  const result = await db.select().from(apprenants).where(eq(apprenants.email, email)).limit(1);
  return result.length > 0 ? result[0] : undefined;
}

export async function updateApprenant(id: number, data: Partial<InsertApprenant>) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  await db.update(apprenants).set(data).where(eq(apprenants.id, id));
}

export async function deleteApprenant(id: number) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  await db.delete(apprenants).where(eq(apprenants.id, id));
}

// ==================== INSCRIPTIONS ====================

export async function createInscription(data: InsertInscription) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  const result = await db.insert(inscriptions).values(data);
  return result;
}

export async function getAllInscriptions() {
  const db = await getDb();
  if (!db) return [];
  
  const results = await db.select({
    inscription: inscriptions,
    apprenant: apprenants,
  })
    .from(inscriptions)
    .leftJoin(apprenants, eq(inscriptions.apprenantId, apprenants.id));
  
  return results;
}

export async function getInscriptionsBySequence(sequenceId: number) {
  const db = await getDb();
  if (!db) return [];
  
  const results = await db.select({
    inscription: inscriptions,
    apprenant: apprenants,
  })
  .from(inscriptions)
  .leftJoin(apprenants, eq(inscriptions.apprenantId, apprenants.id))
  .where(and(
    eq(inscriptions.sequenceId, sequenceId),
    ne(inscriptions.statut, 'annulee')
  ));
  
  return results;
}

export async function getInscriptionsByApprenant(apprenantId: number) {
  const db = await getDb();
  if (!db) return [];
  
  const results = await db.select({
    inscription: inscriptions,
    sequence: sequences,
  })
  .from(inscriptions)
  .leftJoin(sequences, eq(inscriptions.sequenceId, sequences.id))
  .where(and(
    eq(inscriptions.apprenantId, apprenantId),
    ne(inscriptions.statut, 'annulee')
  ));
  
  return results;
}

export async function checkExistingInscription(apprenantId: number, sequenceId: number) {
  const db = await getDb();
  if (!db) return undefined;
  
  const result = await db.select()
    .from(inscriptions)
    .where(and(
      eq(inscriptions.apprenantId, apprenantId),
      eq(inscriptions.sequenceId, sequenceId),
      ne(inscriptions.statut, 'annulee')
    ))
    .limit(1);
  
  return result.length > 0 ? result[0] : undefined;
}

export async function countInscriptionsBySequence(sequenceId: number, statut?: string) {
  const db = await getDb();
  if (!db) return 0;
  
  const conditions = [eq(inscriptions.sequenceId, sequenceId)];
  if (statut) {
    conditions.push(eq(inscriptions.statut, statut as any));
  }
  
  const result = await db.select({ count: sql<number>`count(*)` })
    .from(inscriptions)
    .where(and(...conditions));
  
  return result[0]?.count || 0;
}

export async function getInscriptionById(id: number) {
  const db = await getDb();
  if (!db) return undefined;
  
  const result = await db.select().from(inscriptions).where(eq(inscriptions.id, id)).limit(1);
  return result.length > 0 ? result[0] : undefined;
}

export async function updateInscription(id: number, data: Partial<InsertInscription>) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  await db.update(inscriptions).set(data).where(eq(inscriptions.id, id));
}

export async function deleteInscription(id: number) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  await db.delete(inscriptions).where(eq(inscriptions.id, id));
}

// ==================== GESTION DES UTILISATEURS ====================

export async function createUser(data: InsertUser) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  // Hasher le mot de passe si fourni
  if (data.password) {
    const bcrypt = await import('bcryptjs');
    data.password = await bcrypt.hash(data.password, 10);
  }
  
  // Si le rôle est formateur et qu'il n'y a pas de formateurId, créer une entrée dans la table formateurs
  if (data.role === 'formateur' && !data.formateurId && data.name && data.email) {
    const formateurResult = await db.insert(formateurs).values({
      nom: data.name,
      email: data.email,
    });
    // Récupérer l'ID du formateur créé
    data.formateurId = Number(formateurResult[0].insertId);
  }
  
  const result = await db.insert(users).values(data);
  return result;
}

export async function getAllUsers() {
  const db = await getDb();
  if (!db) return [];
  
  return await db.select().from(users);
}

export async function getUserById(id: number) {
  const db = await getDb();
  if (!db) return undefined;
  
  const result = await db.select().from(users).where(eq(users.id, id)).limit(1);
  return result.length > 0 ? result[0] : undefined;
}

export async function updateUser(id: number, data: Partial<InsertUser>) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  // Hasher le mot de passe si fourni
  if (data.password) {
    const bcrypt = await import('bcryptjs');
    data.password = await bcrypt.hash(data.password, 10);
  }
  
  // Récupérer l'utilisateur actuel
  const currentUser = await getUserById(id);
  if (!currentUser) throw new Error("User not found");
  
  // Si le rôle change vers formateur et qu'il n'y a pas de formateurId
  if (data.role === 'formateur' && !currentUser.formateurId && !data.formateurId) {
    const name = data.name || currentUser.name;
    const email = data.email || currentUser.email;
    if (name && email) {
      const formateurResult = await db.insert(formateurs).values({
        nom: name,
        email: email,
      });
      data.formateurId = Number(formateurResult[0].insertId);
    }
  }
  
  // Si l'utilisateur est formateur et a un formateurId, synchroniser les modifications
  if (currentUser.formateurId && (data.name || data.email)) {
    const formateurUpdate: any = {};
    if (data.name) formateurUpdate.nom = data.name;
    if (data.email) formateurUpdate.email = data.email;
    
    if (Object.keys(formateurUpdate).length > 0) {
      await db.update(formateurs)
        .set(formateurUpdate)
        .where(eq(formateurs.id, currentUser.formateurId));
    }
  }
  
  await db.update(users).set(data).where(eq(users.id, id));
}

export async function toggleUserStatus(id: number, isActive: boolean) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  await db.update(users).set({ isActive }).where(eq(users.id, id));
}

export async function deleteUser(id: number) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  await db.delete(users).where(eq(users.id, id));
}


// ==================== GESTION DES TOKENS DE RÉINITIALISATION ====================

export async function createPasswordResetToken(email: string, token: string) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  const expiresAt = new Date();
  expiresAt.setHours(expiresAt.getHours() + 24); // Token valide 24h
  
  // Convertir la date au format MySQL DATETIME (YYYY-MM-DD HH:MM:SS)
  const mysqlDate = expiresAt.toISOString().slice(0, 19).replace('T', ' ');
  
  // Utiliser une requête SQL brute pour éviter les problèmes avec Drizzle
  const result = await db.execute(sql`
    INSERT INTO passwordResetTokens (email, token, expiresAt)
    VALUES (${email}, ${token}, ${mysqlDate})
  `);
  return result;
}

export async function getPasswordResetToken(token: string) {
  const db = await getDb();
  if (!db) return undefined;
  
  const result = await db.select()
    .from(passwordResetTokens)
    .where(eq(passwordResetTokens.token, token))
    .limit(1);
  
  return result.length > 0 ? result[0] : undefined;
}

export async function markTokenAsUsed(tokenId: number) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  await db.update(passwordResetTokens)
    .set({ used: true })
    .where(eq(passwordResetTokens.id, tokenId));
}

export async function deleteExpiredTokens() {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  const now = new Date();
  await db.delete(passwordResetTokens)
    .where(sql`${passwordResetTokens.expiresAt} < ${now}`);
}

// ============================================
// Email Templates Management
// ============================================

/**
 * Récupère tous les templates d'emails
 */
export async function getAllEmailTemplates() {
  const db = await getDb();
  if (!db) return [];
  
  return await db.select().from(emailTemplates);
}

/**
 * Récupère un template par son type
 */
export async function getEmailTemplateByType(type: string) {
  const db = await getDb();
  if (!db) return null;
  
  const results = await db.select().from(emailTemplates).where(eq(emailTemplates.type, type)).limit(1);
  return results.length > 0 ? results[0] : null;
}

/**
 * Crée ou met à jour un template d'email
 */
export async function upsertEmailTemplate(template: InsertEmailTemplate) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  const existing = await getEmailTemplateByType(template.type);
  
  if (existing) {
    // Mise à jour
    await db.update(emailTemplates)
      .set({
        titre: template.titre,
        bodyContent: template.bodyContent,
        couleurPrincipale: template.couleurPrincipale,
        couleurSecondaire: template.couleurSecondaire,
        piedDePage: template.piedDePage,
        updatedAt: new Date(),
      })
      .where(eq(emailTemplates.type, template.type));
    
    return await getEmailTemplateByType(template.type);
  } else {
    // Création
    await db.insert(emailTemplates).values(template);
    return await getEmailTemplateByType(template.type);
  }
}

/**
 * Supprime un template d'email
 */
export async function deleteEmailTemplate(type: string) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  await db.delete(emailTemplates).where(eq(emailTemplates.type, type));
}

/**
 * Initialise les templates par défaut si la table est vide
 */
export async function initializeDefaultEmailTemplates() {
  const db = await getDb();
  if (!db) return;
  
  const existing = await getAllEmailTemplates();
  if (existing.length > 0) return; // Déjà initialisé
  
  const defaultTemplates: InsertEmailTemplate[] = [
    {
      type: "inscription",
      titre: "Confirmation d'inscription",
      bodyContent: "<p>Contenu par défaut</p>",
      couleurPrincipale: "#283581",
      couleurSecondaire: "#0578BE",
      piedDePage: "Cet email a été envoyé automatiquement par le système de gestion des formations Itinova. Pour toute question, veuillez contacter le service RH.",
    },
    {
      type: "teaser",
      titre: "Email teaser",
      bodyContent: "<p>Contenu par défaut</p>",
      couleurPrincipale: "#283581",
      couleurSecondaire: "#0578BE",
      piedDePage: "Cet email a été envoyé automatiquement par le système de gestion des formations Itinova. Pour toute question, veuillez contacter le service RH.",
    },
    {
      type: "rappel1",
      titre: "Rappel 1",
      couleurPrincipale: "#283581",
      couleurSecondaire: "#0578BE",
      bodyContent: `<h2>Rappel : Votre formation commence bientôt !</h2>
<p>Bonjour {{prenomApprenant}} {{nomApprenant}},</p>

<p>Nous vous rappelons que votre formation <strong>{{nomFormation}}</strong> commence dans une semaine.</p>

<div class="info-box">
  <p><strong>Séquence :</strong> {{nomSequence}}</p>
  <h4>Dates :</h4>
  {{datesHTML}}
  <p><strong>Lieu :</strong> {{lieu}}</p>
  <p><strong>Formateur :</strong> {{formateur}}</p>
</div>

<p>N'oubliez pas d'apporter le matériel nécessaire et de vous présenter à l'heure indiquée.</p>

<p>À très bientôt !</p>`,
      piedDePage: "Cet email a été envoyé automatiquement par le système de gestion des formations Itinova. Pour toute question, veuillez contacter le service RH.",
    },
    {
      type: "rappel2",
      titre: "Rappel 2",
      couleurPrincipale: "#283581",
      couleurSecondaire: "#0578BE",
      bodyContent: `<h2>Rappel : Votre formation commence demain !</h2>
<p>Bonjour {{prenomApprenant}} {{nomApprenant}},</p>

<p>Nous vous rappelons que votre formation <strong>{{nomFormation}}</strong> commence <strong>demain</strong>.</p>

<div class="info-box">
  <p><strong>Séquence :</strong> {{nomSequence}}</p>
  <h4>Dates :</h4>
  {{datesHTML}}
  <p><strong>Lieu :</strong> {{lieu}}</p>
  <p><strong>Formateur :</strong> {{formateur}}</p>
</div>

<p><strong>Merci de vous présenter à l'heure indiquée.</strong></p>
<p>N'oubliez pas d'apporter le matériel nécessaire.</p>

<p>À demain !</p>`,
      piedDePage: "Cet email a été envoyé automatiquement par le système de gestion des formations Itinova. Pour toute question, veuillez contacter le service RH.",
    },
    {
      type: "rappel3",
      titre: "Rappel 3",
      couleurPrincipale: "#283581",
      couleurSecondaire: "#0578BE",
      bodyContent: `<h2>Rappel : Votre formation commence bientôt !</h2>
<p>Bonjour {{prenomApprenant}} {{nomApprenant}},</p>

<p>Nous vous rappelons que votre formation <strong>{{nomFormation}}</strong> commence dans une semaine.</p>

<div class="info-box">
  <p><strong>Séquence :</strong> {{nomSequence}}</p>
  <h4>Dates :</h4>
  {{datesHTML}}
  <p><strong>Lieu :</strong> {{lieu}}</p>
  <p><strong>Formateur :</strong> {{formateur}}</p>
</div>

<p>N'oubliez pas d'apporter le matériel nécessaire et de vous présenter à l'heure indiquée.</p>

<p>À très bientôt !</p>`,
      piedDePage: "Cet email a été envoyé automatiquement par le système de gestion des formations Itinova. Pour toute question, veuillez contacter le service RH.",
    },
    {
      type: "rappel4",
      titre: "Rappel 4",
      couleurPrincipale: "#283581",
      couleurSecondaire: "#0578BE",
      bodyContent: `<h2>Rappel : Votre formation commence bientôt !</h2>
<p>Bonjour {{prenomApprenant}} {{nomApprenant}},</p>

<p>Nous vous rappelons que votre formation <strong>{{nomFormation}}</strong> commence dans une semaine.</p>

<div class="info-box">
  <p><strong>Séquence :</strong> {{nomSequence}}</p>
  <h4>Dates :</h4>
  {{datesHTML}}
  <p><strong>Lieu :</strong> {{lieu}}</p>
  <p><strong>Formateur :</strong> {{formateur}}</p>
</div>

<p>N'oubliez pas d'apporter le matériel nécessaire et de vous présenter à l'heure indiquée.</p>

<p>À très bientôt !</p>`,
      piedDePage: "Cet email a été envoyé automatiquement par le système de gestion des formations Itinova. Pour toute question, veuillez contacter le service RH.",
    },
    {
      type: "rappel5",
      titre: "Rappel 5",
      couleurPrincipale: "#283581",
      couleurSecondaire: "#0578BE",
      bodyContent: `<h2>Rappel : Votre formation commence bientôt !</h2>
<p>Bonjour {{prenomApprenant}} {{nomApprenant}},</p>

<p>Nous vous rappelons que votre formation <strong>{{nomFormation}}</strong> commence dans une semaine.</p>

<div class="info-box">
  <p><strong>Séquence :</strong> {{nomSequence}}</p>
  <h4>Dates :</h4>
  {{datesHTML}}
  <p><strong>Lieu :</strong> {{lieu}}</p>
  <p><strong>Formateur :</strong> {{formateur}}</p>
</div>

<p>N'oubliez pas d'apporter le matériel nécessaire et de vous présenter à l'heure indiquée.</p>

<p>À très bientôt !</p>`,
      piedDePage: "Cet email a été envoyé automatiquement par le système de gestion des formations Itinova. Pour toute question, veuillez contacter le service RH.",
    },
    {
      type: "rappel6",
      titre: "Rappel 6",
      couleurPrincipale: "#283581",
      couleurSecondaire: "#0578BE",
      bodyContent: `<h2>Rappel : Votre formation commence bientôt !</h2>
<p>Bonjour {{prenomApprenant}} {{nomApprenant}},</p>

<p>Nous vous rappelons que votre formation <strong>{{nomFormation}}</strong> commence dans une semaine.</p>

<div class="info-box">
  <p><strong>Séquence :</strong> {{nomSequence}}</p>
  <h4>Dates :</h4>
  {{datesHTML}}
  <p><strong>Lieu :</strong> {{lieu}}</p>
  <p><strong>Formateur :</strong> {{formateur}}</p>
</div>

<p>N'oubliez pas d'apporter le matériel nécessaire et de vous présenter à l'heure indiquée.</p>

<p>À très bientôt !</p>`,
      piedDePage: "Cet email a été envoyé automatiquement par le système de gestion des formations Itinova. Pour toute question, veuillez contacter le service RH.",
    },
    {
      type: "reset_password",
      titre: "Réinitialisation de mot de passe",
      bodyContent: "<p>Contenu par défaut</p>",
      couleurPrincipale: "#283581",
      couleurSecondaire: "#0578BE",
      piedDePage: "Cet email a été envoyé automatiquement par le système de gestion des formations Itinova. Pour toute question, veuillez contacter le service RH.",
    },
  ];
  
  for (const template of defaultTemplates) {
    await db.insert(emailTemplates).values(template);
  }
  
  console.log("[DB] Templates d'emails par défaut initialisés");
}


// ==================== Email Config ====================

/**
 * Récupère la configuration email active
 */
export async function getActiveEmailConfig() {
  const db = await getDb();
  if (!db) return undefined;
  
  const result = await db
    .select()
    .from(emailConfig)
    .where(eq(emailConfig.active, true))
    .limit(1);
  
  return result.length > 0 ? result[0] : undefined;
}

/**
 * Crée ou met à jour la configuration email
 */
export async function upsertEmailConfig(data: InsertEmailConfig) {
  const db = await getDb();
  if (!db) return;
  
  // Vérifier s'il existe déjà une configuration active
  const existing = await db
    .select()
    .from(emailConfig)
    .where(eq(emailConfig.active, true))
    .limit(1);
  
  if (existing.length > 0) {
    // Mettre à jour la configuration existante
    await db
      .update(emailConfig)
      .set({
        ...data,
        updatedAt: new Date(),
      })
      .where(eq(emailConfig.id, existing[0].id));
  } else {
    // Créer une nouvelle configuration
    await db.insert(emailConfig).values({
      ...data,
      active: true,
    });
  }
}

/**
 * Met à jour la configuration email
 */
export async function updateEmailConfig(id: number, data: Partial<InsertEmailConfig>) {
  const db = await getDb();
  if (!db) return;
  
  await db
    .update(emailConfig)
    .set({
      ...data,
      updatedAt: new Date(),
    })
    .where(eq(emailConfig.id, id));
}


// ==================== FORMATEURS ====================

export async function createFormateur(data: InsertFormateur) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  const result = await db.insert(formateurs).values(data);
  return result;
}

export async function getFormateurs() {
  const db = await getDb();
  if (!db) return [];
  
  return await db.select().from(formateurs);
}

export async function getFormateurById(id: number): Promise<Formateur | undefined> {
  const db = await getDb();
  if (!db) return undefined;
  
  const result = await db.select().from(formateurs).where(eq(formateurs.id, id)).limit(1);
  return result.length > 0 ? result[0] : undefined;
}

export async function updateFormateur(id: number, data: Partial<InsertFormateur>) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  await db.update(formateurs).set(data).where(eq(formateurs.id, id));
}

export async function deleteFormateur(id: number) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  await db.delete(formateurs).where(eq(formateurs.id, id));
}


// ==================== RAPPELS ====================

export async function createRappel(data: InsertRappel & { fichier?: { nomFichier: string; urlFichier: string; s3Key: string; typeFichier: string; tailleFichier: number; } | null; fichier2?: { nomFichier: string; urlFichier: string; s3Key: string; typeFichier: string; tailleFichier: number; } | null }) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  // Construire un objet d'insertion explicite
  const insertData: Partial<InsertRappel> = {
    nom: data.nom,
    templateType: data.templateType,
    timing: data.timing,
    joursAvant: data.joursAvant,
    heureEnvoi: data.heureEnvoi,
    actif: data.actif,
  };
  
  // Ajouter les champs de fichier 1 si présents
  if (data.fichier) {
    insertData.nomFichier = data.fichier.nomFichier;
    insertData.urlFichier = data.fichier.urlFichier;
    insertData.s3Key = data.fichier.s3Key;
    insertData.typeFichier = data.fichier.typeFichier;
    insertData.tailleFichier = data.fichier.tailleFichier;
  }
  
  // Ajouter les champs de fichier 2 si présents
  if (data.fichier2) {
    insertData.nomFichier2 = data.fichier2.nomFichier;
    insertData.urlFichier2 = data.fichier2.urlFichier;
    insertData.s3Key2 = data.fichier2.s3Key;
    insertData.typeFichier2 = data.fichier2.typeFichier;
    insertData.tailleFichier2 = data.fichier2.tailleFichier;
  }
  
  const result = await db.insert(rappels).values(insertData as InsertRappel);
  return result;
}

export async function getRappels() {
  const db = await getDb();
  if (!db) return [];
  
  const allRappels = await db.select().from(rappels).orderBy(rappels.joursAvant);
  
  // Pour chaque rappel, récupérer les dates de formation associées avec leurs infos
  const rappelsWithDates = await Promise.all(
    allRappels.map(async (rappel) => {
      const dateFormationIds = await getRappelDates(rappel.id);
      
      // Récupérer les détails des dates de formation
      const datesDetails = await Promise.all(
        dateFormationIds.map(async (dateId) => {
          const dateFormationResult = await db.select().from(datesFormation).where(eq(datesFormation.id, dateId)).limit(1);
          if (dateFormationResult.length === 0) return null;
          
          const dateFormation = dateFormationResult[0];
          const sequenceResult = await db.select().from(sequences).where(eq(sequences.id, dateFormation.sequenceId)).limit(1);
          if (sequenceResult.length === 0) return null;
          
          const sequence = sequenceResult[0];
          const formationResult = await db.select().from(formations).where(eq(formations.id, sequence.formationId)).limit(1);
          if (formationResult.length === 0) return null;
          
          const formation = formationResult[0];
          
          return {
            dateFormationId: dateFormation.id,
            dateDebut: dateFormation.dateDebut,
            dateFin: dateFormation.dateFin,
            ordre: dateFormation.ordre,
            sequenceId: sequence.id,
            sequenceNom: sequence.nom,
            formationId: formation.id,
            formationNom: formation.nom,
          };
        })
      );
      
      return {
        ...rappel,
        dateFormationIds,
        datesDetails: datesDetails.filter(d => d !== null),
      };
    })
  );
  
  return rappelsWithDates;
}

export async function getRappelById(id: number): Promise<Rappel | undefined> {
  const db = await getDb();
  if (!db) return undefined;
  
  const result = await db.select().from(rappels).where(eq(rappels.id, id)).limit(1);
  return result.length > 0 ? result[0] : undefined;
}

export async function getActiveRappels(): Promise<Rappel[]> {
  const db = await getDb();
  if (!db) return [];
  
  return await db.select().from(rappels).where(eq(rappels.actif, true));
}

export async function updateRappel(id: number, data: Partial<InsertRappel> & { fichier?: { nomFichier: string; urlFichier: string; s3Key: string; typeFichier: string; tailleFichier: number; } | null; fichier2?: { nomFichier: string; urlFichier: string; s3Key: string; typeFichier: string; tailleFichier: number; } | null }) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  const updateData: any = { ...data };
  
  // Gérer les champs de fichier 1
  if ('fichier' in data) {
    delete updateData.fichier;
    if (data.fichier) {
      updateData.nomFichier = data.fichier.nomFichier;
      updateData.urlFichier = data.fichier.urlFichier;
      updateData.s3Key = data.fichier.s3Key;
      updateData.typeFichier = data.fichier.typeFichier;
      updateData.tailleFichier = data.fichier.tailleFichier;
    } else {
      // Supprimer le fichier 1
      updateData.nomFichier = null;
      updateData.urlFichier = null;
      updateData.s3Key = null;
      updateData.typeFichier = null;
      updateData.tailleFichier = null;
    }
  }
  
  // Gérer les champs de fichier 2
  if ('fichier2' in data) {
    delete updateData.fichier2;
    if (data.fichier2) {
      updateData.nomFichier2 = data.fichier2.nomFichier;
      updateData.urlFichier2 = data.fichier2.urlFichier;
      updateData.s3Key2 = data.fichier2.s3Key;
      updateData.typeFichier2 = data.fichier2.typeFichier;
      updateData.tailleFichier2 = data.fichier2.tailleFichier;
    } else {
      // Supprimer le fichier 2
      updateData.nomFichier2 = null;
      updateData.urlFichier2 = null;
      updateData.s3Key2 = null;
      updateData.typeFichier2 = null;
      updateData.tailleFichier2 = null;
    }
  }
  
  await db.update(rappels).set({
    ...updateData,
    updatedAt: new Date(),
  }).where(eq(rappels.id, id));
}

export async function deleteRappel(id: number) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  // Supprimer d'abord les associations
  await db.delete(rappelDates).where(eq(rappelDates.rappelId, id));
  // Puis le rappel
  await db.delete(rappels).where(eq(rappels.id, id));
}

// ==================== RAPPEL DATES ====================

export async function createRappelDates(rappelId: number, dateFormationIds: number[]) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  if (dateFormationIds.length === 0) return;
  
  const values = dateFormationIds.map(dateFormationId => ({
    rappelId,
    dateFormationId,
  }));
  
  await db.insert(rappelDates).values(values);
}

export async function deleteRappelDates(rappelId: number) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  await db.delete(rappelDates).where(eq(rappelDates.rappelId, rappelId));
}

export async function getRappelDates(rappelId: number): Promise<number[]> {
  const db = await getDb();
  if (!db) return [];
  
  const results = await db.select().from(rappelDates).where(eq(rappelDates.rappelId, rappelId));
  return results.map(r => r.dateFormationId);
}

export async function updateRappelExecution(id: number) {
  const db = await getDb();
  if (!db) return;
  
  await db.update(rappels).set({
    derniereExecution: new Date(),
  }).where(eq(rappels.id, id));
}

export async function getRappelsByDateFormation(dateFormationId: number) {
  const db = await getDb();
  if (!db) return [];
  
  // Récupérer les IDs des rappels associés à cette date
  const rappelDatesResults = await db.select().from(rappelDates).where(eq(rappelDates.dateFormationId, dateFormationId));
  const rappelIds = rappelDatesResults.map(rd => rd.rappelId);
  
  if (rappelIds.length === 0) {
    // Retourner tous les rappels actifs qui n'ont pas de dates spécifiques (rappels globaux)
    const allRappels = await db.select().from(rappels).where(eq(rappels.actif, true));
    const globalRappels = [];
    
    for (const rappel of allRappels) {
      const dates = await getRappelDates(rappel.id);
      if (dates.length === 0) {
        globalRappels.push(rappel);
      }
    }
    
    return globalRappels;
  }
  
  // Récupérer les détails des rappels
  const rappelsResults = await Promise.all(
    rappelIds.map(async (id) => {
      const rappelResult = await db.select().from(rappels).where(eq(rappels.id, id)).limit(1);
      return rappelResult.length > 0 ? rappelResult[0] : null;
    })
  );
  
  // Filtrer les null et retourner
  return rappelsResults.filter((r): r is Rappel => r !== null);
}


// ==================== UTILISATEURS ADMIN ====================

/**
 * Récupère tous les utilisateurs avec le rôle admin
 */
export async function getAdminUsers() {
  const db = await getDb();
  if (!db) return [];
  
  return await db.select().from(users).where(eq(users.role, 'admin'));
}



export async function getUtilisateurByEmail(email: string) {
  const db = await getDb();
  if (!db) return undefined;
  
  const result = await db.select()
    .from(users)
    .where(eq(users.email, email))
    .limit(1);
  
  return result.length > 0 ? result[0] : undefined;
}

export async function updateUtilisateurPassword(userId: number, hashedPassword: string): Promise<void> {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  await db.update(users)
    .set({ password: hashedPassword })
    .where(eq(users.id, userId));
}

// ==================== LOGS RAPPELS ====================

/**
 * Crée un log de rappel dans l'historique
 */
export async function createLogRappel(logData: {
  rappelId: number;
  sequenceId: number;
  apprenantId: number;
  email: string;
  type: string;
  typeEnvoi: 'automatique' | 'test';
  statut: 'succes' | 'echec';
  messageErreur?: string;
  dateEnvoi: Date;
}) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  
  await db.insert(logsRappels).values({
    rappelId: logData.rappelId,
    sequenceId: logData.sequenceId,
    apprenantId: logData.apprenantId,
    email: logData.email,
    type: logData.type as any,
    typeEnvoi: logData.typeEnvoi,
    statut: logData.statut,
    messageErreur: logData.messageErreur || null,
    dateEnvoi: logData.dateEnvoi,
  });
}
