import { eq, desc, and, sql, count, gte, lte, isNull, isNotNull, or, like, inArray } from "drizzle-orm";
import { drizzle } from "drizzle-orm/mysql2";
import { getAssociationCommuneVariants, normalizeAssociationCommune, type AssociationCommuneFilter } from "@shared/associationCommunes";
import {
  matchAssociationDirectoryEntry,
  normalizeDirectoryEmail,
  normalizeDirectoryRna,
  normalizeDirectorySiret,
  type AssociationDirectoryMatchInput,
} from "./associationDirectoryMatcher";
import { 
  InsertUser, users, 
  associations, InsertAssociation, Association,
  associationDirectoryEntries, InsertAssociationDirectoryEntry, AssociationDirectoryEntry,
  associationDirectoryReviews, InsertAssociationDirectoryReview, AssociationDirectoryReview,
  associationDirectoryUpdateProposals, InsertAssociationDirectoryUpdateProposal, AssociationDirectoryUpdateProposal,
  associationInvitations, InsertAssociationInvitation, AssociationInvitation,
  documents, InsertDocument, Document,
  requests, InsertRequest, Request,
  requestTemplates, InsertRequestTemplate, RequestTemplate,
  requestHistory, InsertRequestHistory, RequestHistory,
  responseTemplates, InsertResponseTemplate, ResponseTemplate,
  auditLog, InsertAuditLog, AuditLog,
  adminNotifications, InsertAdminNotification, AdminNotification,
  portalSettings, InsertPortalSetting, PortalSetting,
  operationalRecapServices, InsertOperationalRecapService, OperationalRecapService,
  materialReturnFollowups, InsertMaterialReturnFollowup, MaterialReturnFollowup,
  emailActionTokens, InsertEmailActionToken, EmailActionToken
} from "../drizzle/schema";
import { ENV } from './_core/env';

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

export function isDatabaseConfigured() {
  return Boolean(process.env.DATABASE_URL);
}

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;
}

// ============== USER FUNCTIONS ==============

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", "passwordHash", "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;
}

export async function getUserByEmail(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 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 getUserByMfaChallengeToken(token: string) {
  const db = await getDb();
  if (!db) return undefined;
  const result = await db.select().from(users).where(eq(users.mfaChallengeToken, token)).limit(1);
  return result.length > 0 ? result[0] : undefined;
}

export async function getUsersScheduledForPurge(referenceDate: Date = new Date()) {
  const db = await getDb();
  if (!db) return [];
  return db.select().from(users).where(
    and(
      isNotNull(users.purgeScheduledAt),
      lte(users.purgeScheduledAt, referenceDate),
      isNull(users.purgedAt),
      eq(users.legalHold, false)
    )
  );
}

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

export async function getAdminUsers() {
  const db = await getDb();
  if (!db) return [];
  return db.select().from(users).where(
    or(
      eq(users.role, 'accueil'),
      eq(users.role, 'service_terrain'),
      eq(users.role, 'logistique_controle'),
      eq(users.role, 'admin'),
      eq(users.role, 'directrice'),
      eq(users.role, 'super_admin')
    )
  ).orderBy(desc(users.createdAt));
}

export async function updateUser(id: number, data: Partial<InsertUser>): Promise<void> {
  const db = await getDb();
  if (!db) return;
  await db.update(users).set(data).where(eq(users.id, id));
}

export async function deleteUser(id: number): Promise<void> {
  const db = await getDb();
  if (!db) return;
  await db.delete(users).where(eq(users.id, id));
}

// ============== ASSOCIATION FUNCTIONS ==============

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

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

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

export async function createAssociation(data: InsertAssociation): Promise<number> {
  const db = await getDb();
  if (!db) throw new Error("Mode demo actif : la base de donnees n'est pas configuree.");
  const result = await db.insert(associations).values(data);
  return result[0].insertId;
}

export async function updateAssociation(id: number, data: Partial<InsertAssociation>): Promise<void> {
  const db = await getDb();
  if (!db) throw new Error("Mode demo actif : la base de donnees n'est pas configuree.");
  await db.update(associations).set(data).where(eq(associations.id, id));
}

export async function getAllAssociations(): Promise<Association[]> {
  const db = await getDb();
  if (!db) return [];
  return db.select().from(associations).orderBy(desc(associations.createdAt));
}

export async function searchAssociations(params: {
  search?: string;
  ville?: string;
  profileComplete?: boolean;
  isActive?: boolean;
  limit?: number;
  offset?: number;
}): Promise<{ data: Association[]; total: number }> {
  const db = await getDb();
  if (!db) return { data: [], total: 0 };

  const conditions = [];
  
  if (params.search) {
    conditions.push(
      or(
        like(associations.nomAssociation, `%${params.search}%`),
        like(associations.siret, `%${params.search}%`),
        like(associations.ville, `%${params.search}%`)
      )
    );
  }
  if (params.ville) {
    const communeVariants = getAssociationCommuneVariants(params.ville);
    if (communeVariants.length > 0) {
      conditions.push(or(...communeVariants.map(variant => eq(associations.ville, variant))));
    } else {
      conditions.push(eq(associations.ville, params.ville));
    }
  }
  if (params.profileComplete !== undefined) {
    conditions.push(eq(associations.profileComplete, params.profileComplete));
  }
  if (params.isActive !== undefined) {
    conditions.push(eq(associations.isActive, params.isActive));
  }

  const whereClause = conditions.length > 0 ? and(...conditions) : undefined;

  const [data, totalResult] = await Promise.all([
    db.select()
      .from(associations)
      .where(whereClause)
      .orderBy(desc(associations.createdAt))
      .limit(params.limit || 20)
      .offset(params.offset || 0),
    db.select({ count: count() })
      .from(associations)
      .where(whereClause)
  ]);

  return { data, total: totalResult[0]?.count || 0 };
}

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

export async function getAssociationCommuneCounts(): Promise<Record<AssociationCommuneFilter, number>> {
  const db = await getDb();
  const counts: Record<AssociationCommuneFilter, number> = {
    all: 0,
    kourou: 0,
    sinnamary: 0,
    iracoubo: 0,
    saint_elie: 0,
  };

  if (!db) {
    return counts;
  }

  const rows = await db.select({ ville: associations.ville }).from(associations);
  counts.all = rows.length;

  rows.forEach((row) => {
    const commune = normalizeAssociationCommune(row.ville);
    if (commune !== "all") {
      counts[commune] += 1;
    }
  });

  return counts;
}

// ============== ASSOCIATION DIRECTORY FUNCTIONS ==============

export async function getAssociationDirectoryEntryByNormalizedEmail(email: string): Promise<AssociationDirectoryEntry | undefined> {
  const db = await getDb();
  if (!db) return undefined;
  const result = await db.select()
    .from(associationDirectoryEntries)
    .where(eq(associationDirectoryEntries.emailOfficielNormalise, email))
    .limit(1);
  return result.length > 0 ? result[0] : undefined;
}

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

export async function getAssociationDirectoryEntryByFingerprint(sourceFingerprint: string): Promise<AssociationDirectoryEntry | undefined> {
  const db = await getDb();
  if (!db) return undefined;
  const result = await db.select()
    .from(associationDirectoryEntries)
    .where(eq(associationDirectoryEntries.sourceFingerprint, sourceFingerprint))
    .limit(1);
  return result.length > 0 ? result[0] : undefined;
}

export async function getAssociationDirectoryEntriesSummary() {
  const db = await getDb();
  if (!db) {
    return {
      total: 0,
      withEmail: 0,
      withoutEmail: 0,
      registered: 0,
      unregistered: 0,
      lastImportAt: null as Date | null,
    };
  }

  const [totalResult, withEmailResult, withoutEmailResult, registeredResult, latest] = await Promise.all([
    db.select({ count: count() }).from(associationDirectoryEntries),
    db.select({ count: count() }).from(associationDirectoryEntries).where(sql`${associationDirectoryEntries.emailOfficielNormalise} is not null`),
    db.select({ count: count() }).from(associationDirectoryEntries).where(sql`${associationDirectoryEntries.emailOfficielNormalise} is null`),
    db.select({ count: count() })
      .from(associationDirectoryEntries)
      .innerJoin(associations, eq(associations.sourceDirectoryEntryId, associationDirectoryEntries.id)),
    db.select().from(associationDirectoryEntries).orderBy(desc(associationDirectoryEntries.importedAt)).limit(1),
  ]);

  const total = totalResult[0]?.count || 0;
  const registered = registeredResult[0]?.count || 0;

  return {
    total,
    withEmail: withEmailResult[0]?.count || 0,
    withoutEmail: withoutEmailResult[0]?.count || 0,
    registered,
    unregistered: Math.max(total - registered, 0),
    lastImportAt: latest[0]?.importedAt || null,
  };
}

export async function listAssociationDirectoryEntries(limit = 50): Promise<AssociationDirectoryEntry[]> {
  const db = await getDb();
  if (!db) return [];
  return db.select()
    .from(associationDirectoryEntries)
    .orderBy(desc(associationDirectoryEntries.updatedAt))
    .limit(limit);
}

export async function listAssociationDirectoryEntriesWithStatus(params?: {
  search?: string;
  registrationStatus?: "all" | "registered" | "unregistered";
  commune?: AssociationCommuneFilter;
  thematique?: "all" | (typeof import("@shared/associationThematics").associationThematicValues)[number];
  limit?: number;
}) {
  const db = await getDb();
  if (!db) return { data: [], total: 0 };

  const conditions = [];

  if (params?.search) {
    conditions.push(
      or(
        like(associationDirectoryEntries.nomAssociation, `%${params.search}%`),
        like(associationDirectoryEntries.emailOfficiel, `%${params.search}%`),
        like(associationDirectoryEntries.siret, `%${params.search}%`),
        like(associationDirectoryEntries.rna, `%${params.search}%`),
        like(associationDirectoryEntries.ville, `%${params.search}%`)
      )
    );
  }

  if (params?.commune && params.commune !== "all") {
    const communeVariants = getAssociationCommuneVariants(params.commune);
    if (communeVariants.length > 0) {
      conditions.push(or(...communeVariants.map((variant) => eq(associationDirectoryEntries.ville, variant))));
    }
  }

  if (params?.thematique && params.thematique !== "all") {
    conditions.push(like(associationDirectoryEntries.thematique, `%\"${params.thematique}\"%`));
  }

  if (params?.registrationStatus === "registered") {
    conditions.push(isNotNull(associations.id));
  } else if (params?.registrationStatus === "unregistered") {
    conditions.push(isNull(associations.id));
  }

  const whereClause = conditions.length > 0 ? and(...conditions) : undefined;

  const [rows, totalResult] = await Promise.all([
    db
      .select({
        id: associationDirectoryEntries.id,
        nomAssociation: associationDirectoryEntries.nomAssociation,
        emailOfficiel: associationDirectoryEntries.emailOfficiel,
        siret: associationDirectoryEntries.siret,
        rna: associationDirectoryEntries.rna,
        thematique: associationDirectoryEntries.thematique,
        adresse: associationDirectoryEntries.adresse,
        codePostal: associationDirectoryEntries.codePostal,
        ville: associationDirectoryEntries.ville,
        latitude: associationDirectoryEntries.latitude,
        longitude: associationDirectoryEntries.longitude,
        geoSource: associationDirectoryEntries.geoSource,
        geoPrecision: associationDirectoryEntries.geoPrecision,
        siteWeb: associationDirectoryEntries.siteWeb,
        facebookUrl: associationDirectoryEntries.facebookUrl,
        instagramUrl: associationDirectoryEntries.instagramUrl,
        importedAt: associationDirectoryEntries.importedAt,
        updatedAt: associationDirectoryEntries.updatedAt,
        sourceFileName: associationDirectoryEntries.sourceFileName,
        externalSourceStatus: associationDirectoryEntries.externalSourceStatus,
        externalSourceLabel: associationDirectoryEntries.externalSourceLabel,
        registeredAssociationId: associations.id,
        registeredAssociationName: associations.nomAssociation,
        registeredAt: associations.createdAt,
      })
      .from(associationDirectoryEntries)
      .leftJoin(associations, eq(associations.sourceDirectoryEntryId, associationDirectoryEntries.id))
      .where(whereClause)
      .orderBy(desc(associationDirectoryEntries.updatedAt))
      .limit(params?.limit || 50),
    db
      .select({ count: count() })
      .from(associationDirectoryEntries)
      .leftJoin(associations, eq(associations.sourceDirectoryEntryId, associationDirectoryEntries.id))
      .where(whereClause),
  ]);

  return {
    data: rows.map((row) => ({
      ...row,
      registered: Boolean(row.registeredAssociationId),
    })),
    total: totalResult[0]?.count || 0,
  };
}

export async function upsertAssociationDirectoryEntry(data: InsertAssociationDirectoryEntry): Promise<"created" | "updated"> {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const existing = data.emailOfficielNormalise
    ? await getAssociationDirectoryEntryByNormalizedEmail(data.emailOfficielNormalise)
    : data.sourceFingerprint
      ? await getAssociationDirectoryEntryByFingerprint(data.sourceFingerprint)
      : undefined;

  if (existing) {
    await db.update(associationDirectoryEntries)
      .set({ ...data, importedAt: new Date() })
      .where(eq(associationDirectoryEntries.id, existing.id));
    return "updated";
  }

  await db.insert(associationDirectoryEntries).values(data);
  return "created";
}

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

export async function createAssociationDirectoryEntry(data: InsertAssociationDirectoryEntry): Promise<number> {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  const result = await db.insert(associationDirectoryEntries).values(data);
  return result[0].insertId;
}

export async function listAssociationDirectoryEntriesForMatching() {
  const db = await getDb();
  if (!db) return [];
  return db
    .select({
      id: associationDirectoryEntries.id,
      nomAssociation: associationDirectoryEntries.nomAssociation,
      emailOfficiel: associationDirectoryEntries.emailOfficiel,
      siret: associationDirectoryEntries.siret,
      rna: associationDirectoryEntries.rna,
      ville: associationDirectoryEntries.ville,
      telephone: associationDirectoryEntries.telephone,
      nomRepresentant: associationDirectoryEntries.nomRepresentant,
    })
    .from(associationDirectoryEntries)
    .where(eq(associationDirectoryEntries.isActive, true));
}

export async function findAssociationDirectoryMatch(input: AssociationDirectoryMatchInput) {
  const entries = await listAssociationDirectoryEntriesForMatching();
  return matchAssociationDirectoryEntry(entries, input);
}

export async function createAssociationDirectoryReview(data: InsertAssociationDirectoryReview): Promise<number> {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  const result = await db.insert(associationDirectoryReviews).values(data);
  return result[0].insertId;
}

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

export async function getPendingAssociationDirectoryReviewByUserId(userId: number): Promise<AssociationDirectoryReview | undefined> {
  const db = await getDb();
  if (!db) return undefined;
  const result = await db.select().from(associationDirectoryReviews)
    .where(and(eq(associationDirectoryReviews.userId, userId), eq(associationDirectoryReviews.status, "pending")))
    .orderBy(desc(associationDirectoryReviews.createdAt))
    .limit(1);
  return result[0];
}

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

export async function listAssociationDirectoryReviews(params?: {
  status?: "pending" | "linked" | "created" | "ignored";
  sourceType?: "portal_signup" | "helloasso";
  limit?: number;
}) {
  const db = await getDb();
  if (!db) return [];

  const conditions = [];
  if (params?.status) {
    conditions.push(eq(associationDirectoryReviews.status, params.status));
  }
  if (params?.sourceType) {
    conditions.push(eq(associationDirectoryReviews.sourceType, params.sourceType));
  }

  const whereClause = conditions.length > 0 ? and(...conditions) : undefined;

  return db.select().from(associationDirectoryReviews)
    .where(whereClause)
    .orderBy(desc(associationDirectoryReviews.createdAt))
    .limit(params?.limit || 50);
}

export async function createAssociationDirectoryUpdateProposal(data: InsertAssociationDirectoryUpdateProposal): Promise<number> {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  const result = await db.insert(associationDirectoryUpdateProposals).values(data);
  return result[0].insertId;
}

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

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

export async function getLatestPendingAssociationDirectoryUpdateProposalByEntryId(
  directoryEntryId: number,
  sourceType?: "official_registry" | "helloasso"
): Promise<AssociationDirectoryUpdateProposal | undefined> {
  const db = await getDb();
  if (!db) return undefined;

  const conditions = [
    eq(associationDirectoryUpdateProposals.directoryEntryId, directoryEntryId),
    eq(associationDirectoryUpdateProposals.status, "pending"),
  ];

  if (sourceType) {
    conditions.push(eq(associationDirectoryUpdateProposals.sourceType, sourceType));
  }

  const result = await db.select().from(associationDirectoryUpdateProposals)
    .where(and(...conditions))
    .orderBy(desc(associationDirectoryUpdateProposals.createdAt))
    .limit(1);

  return result[0];
}

export async function listAssociationDirectoryUpdateProposals(params?: {
  directoryEntryId?: number;
  status?: "pending" | "applied" | "dismissed";
  sourceType?: "official_registry" | "helloasso";
  limit?: number;
}) {
  const db = await getDb();
  if (!db) return [];

  const conditions = [];
  if (params?.directoryEntryId) {
    conditions.push(eq(associationDirectoryUpdateProposals.directoryEntryId, params.directoryEntryId));
  }
  if (params?.status) {
    conditions.push(eq(associationDirectoryUpdateProposals.status, params.status));
  }
  if (params?.sourceType) {
    conditions.push(eq(associationDirectoryUpdateProposals.sourceType, params.sourceType));
  }

  const whereClause = conditions.length > 0 ? and(...conditions) : undefined;

  return db.select().from(associationDirectoryUpdateProposals)
    .where(whereClause)
    .orderBy(desc(associationDirectoryUpdateProposals.createdAt))
    .limit(params?.limit || 50);
}

export async function getAssociationDirectoryEntryDetails(id: number) {
  const entry = await getAssociationDirectoryEntryById(id);
  if (!entry) return null;

  const linkedAssociation = await getAssociationBySourceDirectoryEntryId(id);
  if (!linkedAssociation) {
    return {
      entry,
      association: null,
      documents: [],
      requests: [],
    };
  }

  const [documents, requests] = await Promise.all([
    getDocumentsByAssociationId(linkedAssociation.id),
    getRequestsByAssociationId(linkedAssociation.id),
  ]);

  return {
    entry,
    association: linkedAssociation,
    documents,
    requests,
  };
}

export async function listAssociationDirectoryMapEntries(params?: {
  search?: string;
  commune?: AssociationCommuneFilter;
  registrationStatus?: "all" | "registered" | "unregistered";
  thematique?: "all" | (typeof import("@shared/associationThematics").associationThematicValues)[number];
  limit?: number;
}) {
  const db = await getDb();
  if (!db) return { data: [], total: 0 };

  const conditions: Array<any> = [
    isNotNull(associationDirectoryEntries.latitude),
    isNotNull(associationDirectoryEntries.longitude),
  ];

  if (params?.search) {
    conditions.push(
      or(
        like(associationDirectoryEntries.nomAssociation, `%${params.search}%`),
        like(associationDirectoryEntries.siret, `%${params.search}%`),
        like(associationDirectoryEntries.rna, `%${params.search}%`),
        like(associationDirectoryEntries.ville, `%${params.search}%`),
        like(associationDirectoryEntries.objetAssociation, `%${params.search}%`)
      )
    );
  }

  if (params?.commune && params.commune !== "all") {
    const communeVariants = getAssociationCommuneVariants(params.commune);
    if (communeVariants.length > 0) {
      conditions.push(or(...communeVariants.map((variant) => eq(associationDirectoryEntries.ville, variant))));
    }
  }

  if (params?.thematique && params.thematique !== "all") {
    conditions.push(like(associationDirectoryEntries.thematique, `%\"${params.thematique}\"%`));
  }

  if (params?.registrationStatus === "registered") {
    conditions.push(isNotNull(associations.id));
  } else if (params?.registrationStatus === "unregistered") {
    conditions.push(isNull(associations.id));
  }

  const whereClause = and(...conditions);

  const [rows, totalResult] = await Promise.all([
    db
      .select({
        id: associationDirectoryEntries.id,
        nomAssociation: associationDirectoryEntries.nomAssociation,
        ville: associationDirectoryEntries.ville,
        thematique: associationDirectoryEntries.thematique,
        objetAssociation: associationDirectoryEntries.objetAssociation,
        siteWeb: associationDirectoryEntries.siteWeb,
        latitude: associationDirectoryEntries.latitude,
        longitude: associationDirectoryEntries.longitude,
        geoSource: associationDirectoryEntries.geoSource,
        geoPrecision: associationDirectoryEntries.geoPrecision,
        externalSourceLabel: associationDirectoryEntries.externalSourceLabel,
        registeredAssociationId: associations.id,
        registeredAt: associations.createdAt,
      })
      .from(associationDirectoryEntries)
      .leftJoin(associations, eq(associations.sourceDirectoryEntryId, associationDirectoryEntries.id))
      .where(whereClause)
      .orderBy(desc(associationDirectoryEntries.updatedAt))
      .limit(params?.limit || 500),
    db
      .select({ count: count() })
      .from(associationDirectoryEntries)
      .leftJoin(associations, eq(associations.sourceDirectoryEntryId, associationDirectoryEntries.id))
      .where(whereClause),
  ]);

  return {
    data: rows.map((row) => ({
      ...row,
      latitude: row.latitude ? Number(row.latitude) : null,
      longitude: row.longitude ? Number(row.longitude) : null,
      registered: Boolean(row.registeredAssociationId),
      publicUrl: `/associations/${row.id}`,
    })),
    total: totalResult[0]?.count || 0,
  };
}

// ============== DOCUMENT FUNCTIONS ==============

export async function getDocumentsByAssociationId(associationId: number): Promise<Document[]> {
  const db = await getDb();
  if (!db) return [];
  return db.select().from(documents).where(eq(documents.associationId, associationId)).orderBy(desc(documents.uploadedAt));
}

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

export async function createDocument(data: InsertDocument): Promise<number> {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  const result = await db.insert(documents).values(data);
  return result[0].insertId;
}

export async function deleteDocument(id: number): Promise<void> {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  await db.delete(documents).where(eq(documents.id, id));
}

// ============== REQUEST FUNCTIONS ==============

export async function getRequestsByAssociationId(associationId: number): Promise<Request[]> {
  const db = await getDb();
  if (!db) return [];
  return db.select().from(requests).where(eq(requests.associationId, associationId)).orderBy(desc(requests.createdAt));
}

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

export async function createRequest(data: InsertRequest): Promise<number> {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  const result = await db.insert(requests).values(data);
  return result[0].insertId;
}

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

export async function getAllRequests(): Promise<Request[]> {
  const db = await getDb();
  if (!db) return [];
  return db.select().from(requests).orderBy(desc(requests.createdAt));
}

export async function getRequestsByStatus(status: string): Promise<Request[]> {
  const db = await getDb();
  if (!db) return [];
  return db.select().from(requests).where(eq(requests.status, status as any)).orderBy(desc(requests.createdAt));
}

export async function searchRequests(params: {
  search?: string;
  type?: string;
  status?: string;
  priority?: string;
  assigneA?: number;
  associationId?: number;
  dateFrom?: Date;
  dateTo?: Date;
  limit?: number;
  offset?: number;
}): Promise<{ data: Request[]; total: number }> {
  const db = await getDb();
  if (!db) return { data: [], total: 0 };

  const conditions = [];
  
  if (params.search) {
    conditions.push(like(requests.titre, `%${params.search}%`));
  }
  if (params.type) {
    conditions.push(eq(requests.type, params.type as any));
  }
  if (params.status) {
    conditions.push(eq(requests.status, params.status as any));
  }
  if (params.priority) {
    conditions.push(eq(requests.priority, params.priority as any));
  }
  if (params.assigneA) {
    conditions.push(eq(requests.assigneA, params.assigneA));
  }
  if (params.associationId) {
    conditions.push(eq(requests.associationId, params.associationId));
  }
  if (params.dateFrom) {
    conditions.push(gte(requests.createdAt, params.dateFrom));
  }
  if (params.dateTo) {
    conditions.push(lte(requests.createdAt, params.dateTo));
  }

  const whereClause = conditions.length > 0 ? and(...conditions) : undefined;

  const [data, totalResult] = await Promise.all([
    db.select()
      .from(requests)
      .where(whereClause)
      .orderBy(desc(requests.createdAt))
      .limit(params.limit || 20)
      .offset(params.offset || 0),
    db.select({ count: count() })
      .from(requests)
      .where(whereClause)
  ]);

  return { data, total: totalResult[0]?.count || 0 };
}

export async function getPendingRequests(): Promise<Request[]> {
  const db = await getDb();
  if (!db) return [];
  return db.select()
    .from(requests)
    .where(eq(requests.status, 'soumise'))
    .orderBy(requests.dateSubmission);
}

export async function getOverdueRequests(): Promise<Request[]> {
  const db = await getDb();
  if (!db) return [];
  const now = new Date();
  return db.select()
    .from(requests)
    .where(
      and(
        or(eq(requests.status, 'soumise'), eq(requests.status, 'en_cours_traitement')),
        lte(requests.dateLimiteTraitement, now)
      )
    )
    .orderBy(requests.dateLimiteTraitement);
}

export async function assignRequest(id: number, adminId: number): Promise<void> {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  await db.update(requests).set({ 
    assigneA: adminId,
    status: 'en_cours_traitement'
  }).where(eq(requests.id, id));
}

// ============== REQUEST HISTORY FUNCTIONS ==============

export async function createRequestHistory(data: InsertRequestHistory): Promise<number> {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  const result = await db.insert(requestHistory).values(data);
  return result[0].insertId;
}

export async function getRequestHistoryByRequestId(requestId: number): Promise<RequestHistory[]> {
  const db = await getDb();
  if (!db) return [];
  return db.select().from(requestHistory).where(eq(requestHistory.requestId, requestId)).orderBy(desc(requestHistory.createdAt));
}

// ============== RESPONSE TEMPLATE FUNCTIONS ==============

export async function getAllResponseTemplates(): Promise<ResponseTemplate[]> {
  const db = await getDb();
  if (!db) return [];
  return db.select().from(responseTemplates).where(eq(responseTemplates.actif, true)).orderBy(responseTemplates.nom);
}

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

export async function createResponseTemplate(data: InsertResponseTemplate): Promise<number> {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  const result = await db.insert(responseTemplates).values(data);
  return result[0].insertId;
}

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

export async function deleteResponseTemplate(id: number): Promise<void> {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  await db.update(responseTemplates).set({ actif: false }).where(eq(responseTemplates.id, id));
}

// ============== REQUEST TEMPLATE FUNCTIONS ==============

export async function getAllRequestTemplates(): Promise<RequestTemplate[]> {
  const db = await getDb();
  if (!db) return [];
  return db.select().from(requestTemplates).where(eq(requestTemplates.actif, true)).orderBy(requestTemplates.nom);
}

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

export async function getRequestTemplateByType(type: string): Promise<RequestTemplate | undefined> {
  const db = await getDb();
  if (!db) return undefined;
  const result = await db.select().from(requestTemplates).where(eq(requestTemplates.type, type as any)).limit(1);
  return result.length > 0 ? result[0] : undefined;
}

export async function createRequestTemplate(data: InsertRequestTemplate): Promise<number> {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  const result = await db.insert(requestTemplates).values(data);
  return result[0].insertId;
}

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

// ============== AUDIT LOG FUNCTIONS ==============

export async function createAuditLog(data: InsertAuditLog): Promise<number> {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  const result = await db.insert(auditLog).values(data);
  return result[0].insertId;
}

export async function getAuditLogs(params: {
  userId?: number;
  entityType?: string;
  entityId?: number;
  limit?: number;
  offset?: number;
}): Promise<{ data: AuditLog[]; total: number }> {
  const db = await getDb();
  if (!db) return { data: [], total: 0 };

  const conditions = [];
  if (params.userId) conditions.push(eq(auditLog.userId, params.userId));
  if (params.entityType) conditions.push(eq(auditLog.entityType, params.entityType));
  if (params.entityId) conditions.push(eq(auditLog.entityId, params.entityId));

  const whereClause = conditions.length > 0 ? and(...conditions) : undefined;

  const [data, totalResult] = await Promise.all([
    db.select()
      .from(auditLog)
      .where(whereClause)
      .orderBy(desc(auditLog.createdAt))
      .limit(params.limit || 50)
      .offset(params.offset || 0),
    db.select({ count: count() })
      .from(auditLog)
      .where(whereClause)
  ]);

  return { data, total: totalResult[0]?.count || 0 };
}

// ============== ADMIN NOTIFICATIONS FUNCTIONS ==============

export async function createAdminNotification(data: InsertAdminNotification): Promise<number> {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  const result = await db.insert(adminNotifications).values(data);
  return result[0].insertId;
}

export async function getAdminNotifications(userId: number, unreadOnly: boolean = false): Promise<AdminNotification[]> {
  const db = await getDb();
  if (!db) return [];
  
  const conditions = [
    or(eq(adminNotifications.userId, userId), isNull(adminNotifications.userId))
  ];
  
  if (unreadOnly) {
    conditions.push(eq(adminNotifications.lu, false));
  }

  return db.select()
    .from(adminNotifications)
    .where(and(...conditions))
    .orderBy(desc(adminNotifications.createdAt))
    .limit(50);
}

export async function markNotificationAsRead(id: number): Promise<void> {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  await db.update(adminNotifications).set({ lu: true }).where(eq(adminNotifications.id, id));
}

export async function markAllNotificationsAsRead(userId: number): Promise<void> {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  await db.update(adminNotifications)
    .set({ lu: true })
    .where(
      and(
        or(eq(adminNotifications.userId, userId), isNull(adminNotifications.userId)),
        eq(adminNotifications.lu, false)
      )
    );
}

export async function getUnreadNotificationCount(userId: number): Promise<number> {
  const db = await getDb();
  if (!db) return 0;
  const result = await db.select({ count: count() })
    .from(adminNotifications)
    .where(
      and(
        or(eq(adminNotifications.userId, userId), isNull(adminNotifications.userId)),
        eq(adminNotifications.lu, false)
      )
    );
  return result[0]?.count || 0;
}

// ============== PORTAL SETTINGS FUNCTIONS ==============

export async function getPortalSetting(key: string): Promise<string | null> {
  const db = await getDb();
  if (!db) return null;
  const result = await db.select().from(portalSettings).where(eq(portalSettings.cle, key)).limit(1);
  return result.length > 0 ? result[0].valeur : null;
}

export async function getPortalSettingRecord(key: string): Promise<PortalSetting | null> {
  const db = await getDb();
  if (!db) return null;
  const result = await db.select().from(portalSettings).where(eq(portalSettings.cle, key)).limit(1);
  return result[0] ?? null;
}

export async function setPortalSetting(key: string, value: string, description?: string): Promise<void> {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  await db.insert(portalSettings)
    .values({ cle: key, valeur: value, description })
    .onDuplicateKeyUpdate({ set: { valeur: value, description } });
}

export async function getAllPortalSettings(): Promise<PortalSetting[]> {
  const db = await getDb();
  if (!db) return [];
  return db.select().from(portalSettings).orderBy(portalSettings.cle);
}

// ============== OPERATIONAL RECAP SERVICES FUNCTIONS ==============

export async function getAllOperationalRecapServices(): Promise<OperationalRecapService[]> {
  const db = await getDb();
  if (!db) return [];
  return db.select().from(operationalRecapServices).orderBy(operationalRecapServices.label);
}

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

export async function createOperationalRecapService(data: InsertOperationalRecapService): Promise<number> {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  const result = await db.insert(operationalRecapServices).values(data);
  return result[0].insertId;
}

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

export async function deleteOperationalRecapService(id: number): Promise<void> {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  await db.delete(operationalRecapServices).where(eq(operationalRecapServices.id, id));
}

// ============== STATISTICS FUNCTIONS ==============

export async function getDashboardStats() {
  const db = await getDb();
  if (!db) return null;

  const now = new Date();
  const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
  const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);

  const [
    totalAssociations,
    activeAssociations,
    totalRequests,
    pendingRequests,
    validatedRequests,
    rejectedRequests,
    newAssociationsThisMonth,
    newRequestsThisMonth,
    requestsThisWeek,
    overdueRequests
  ] = await Promise.all([
    db.select({ count: count() }).from(associations),
    db.select({ count: count() }).from(associations).where(eq(associations.isActive, true)),
    db.select({ count: count() }).from(requests),
    db.select({ count: count() }).from(requests).where(eq(requests.status, 'soumise')),
    db.select({ count: count() }).from(requests).where(eq(requests.status, 'validee')),
    db.select({ count: count() }).from(requests).where(eq(requests.status, 'refusee')),
    db.select({ count: count() }).from(associations).where(gte(associations.createdAt, thirtyDaysAgo)),
    db.select({ count: count() }).from(requests).where(gte(requests.createdAt, thirtyDaysAgo)),
    db.select({ count: count() }).from(requests).where(gte(requests.createdAt, sevenDaysAgo)),
    db.select({ count: count() }).from(requests).where(
      and(
        or(eq(requests.status, 'soumise'), eq(requests.status, 'en_cours_traitement')),
        lte(requests.dateLimiteTraitement, now)
      )
    )
  ]);

  const totalValidatedAndRejected = (validatedRequests[0]?.count || 0) + (rejectedRequests[0]?.count || 0);
  const acceptanceRate = totalValidatedAndRejected > 0 
    ? Math.round((validatedRequests[0]?.count || 0) / totalValidatedAndRejected * 100) 
    : 0;

  return {
    totalAssociations: totalAssociations[0]?.count || 0,
    activeAssociations: activeAssociations[0]?.count || 0,
    totalRequests: totalRequests[0]?.count || 0,
    pendingRequests: pendingRequests[0]?.count || 0,
    validatedRequests: validatedRequests[0]?.count || 0,
    rejectedRequests: rejectedRequests[0]?.count || 0,
    newAssociationsThisMonth: newAssociationsThisMonth[0]?.count || 0,
    newRequestsThisMonth: newRequestsThisMonth[0]?.count || 0,
    requestsThisWeek: requestsThisWeek[0]?.count || 0,
    overdueRequests: overdueRequests[0]?.count || 0,
    acceptanceRate
  };
}

export async function getRequestsPerMonth(months: number = 12) {
  const db = await getDb();
  if (!db) return [];

  const result = await db.execute(sql`
    SELECT 
      DATE_FORMAT(createdAt, '%Y-%m') as month,
      COUNT(*) as total,
      SUM(CASE WHEN status = 'validee' THEN 1 ELSE 0 END) as validated,
      SUM(CASE WHEN status = 'refusee' THEN 1 ELSE 0 END) as rejected,
      SUM(CASE WHEN status IN ('soumise', 'en_cours_traitement') THEN 1 ELSE 0 END) as pending
    FROM requests
    WHERE createdAt >= DATE_SUB(NOW(), INTERVAL ${months} MONTH)
    GROUP BY DATE_FORMAT(createdAt, '%Y-%m')
    ORDER BY month ASC
  `);

  return (result as any)[0] as any[];
}

export async function getAssociationsPerMonth(months: number = 12) {
  const db = await getDb();
  if (!db) return [];

  const result = await db.execute(sql`
    SELECT 
      DATE_FORMAT(createdAt, '%Y-%m') as month,
      COUNT(*) as total
    FROM associations
    WHERE createdAt >= DATE_SUB(NOW(), INTERVAL ${months} MONTH)
    GROUP BY DATE_FORMAT(createdAt, '%Y-%m')
    ORDER BY month ASC
  `);

  return (result as any)[0] as any[];
}

export async function getRequestsByTypeStats() {
  const db = await getDb();
  if (!db) return [];

  const result = await db.execute(sql`
    SELECT 
      type,
      COUNT(*) as total,
      SUM(CASE WHEN status = 'validee' THEN 1 ELSE 0 END) as validated,
      SUM(CASE WHEN montantAccorde IS NOT NULL THEN montantAccorde ELSE 0 END) as totalMontantAccorde
    FROM requests
    GROUP BY type
    ORDER BY total DESC
  `);

  return (result as any)[0] as any[];
}

export async function getAverageProcessingTime() {
  const db = await getDb();
  if (!db) return null;

  const result = await db.execute(sql`
    SELECT 
      AVG(TIMESTAMPDIFF(DAY, dateSubmission, dateTraitement)) as avgDays
    FROM requests
    WHERE dateSubmission IS NOT NULL 
      AND dateTraitement IS NOT NULL
      AND status IN ('validee', 'refusee')
  `);

  const rows = (result as any)[0] as any[];
  return rows[0]?.avgDays || null;
}

export type AssociationInvitationStatusSummary = {
  directoryEntryId: number;
  status: "never_sent" | "sent" | "expired" | "accepted";
  sentAt: Date | null;
  expiresAt: Date | null;
  usedAt: Date | null;
  revokedAt: Date | null;
  emailSent: boolean;
  deliveryMode: "email" | "manual_link" | null;
};

// ============== ASSOCIATION INVITATION FUNCTIONS ==============

export async function createAssociationInvitation(data: InsertAssociationInvitation): Promise<number> {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  const result = await db.insert(associationInvitations).values(data);
  return result[0].insertId;
}

export async function getAssociationInvitationByToken(token: string): Promise<AssociationInvitation | null> {
  const db = await getDb();
  if (!db) return null;
  const result = await db.select()
    .from(associationInvitations)
    .where(eq(associationInvitations.token, token))
    .limit(1);
  return result[0] ?? null;
}

export async function getActiveAssociationInvitationByDirectoryEntryId(directoryEntryId: number): Promise<AssociationInvitation | null> {
  const db = await getDb();
  if (!db) return null;
  const result = await db.select()
    .from(associationInvitations)
    .where(and(
      eq(associationInvitations.directoryEntryId, directoryEntryId),
      isNull(associationInvitations.usedAt),
      isNull(associationInvitations.revokedAt),
      gte(associationInvitations.expiresAt, new Date())
    ))
    .orderBy(desc(associationInvitations.createdAt))
    .limit(1);
  return result[0] ?? null;
}

export async function getLatestAssociationInvitationByDirectoryEntryId(directoryEntryId: number): Promise<AssociationInvitation | null> {
  const db = await getDb();
  if (!db) return null;
  const result = await db.select()
    .from(associationInvitations)
    .where(eq(associationInvitations.directoryEntryId, directoryEntryId))
    .orderBy(desc(associationInvitations.createdAt))
    .limit(1);
  return result[0] ?? null;
}

export async function revokeAssociationInvitationsByDirectoryEntryId(directoryEntryId: number): Promise<void> {
  const db = await getDb();
  if (!db) return;
  await db.update(associationInvitations)
    .set({ revokedAt: new Date() })
    .where(and(
      eq(associationInvitations.directoryEntryId, directoryEntryId),
      isNull(associationInvitations.usedAt),
      isNull(associationInvitations.revokedAt)
    ));
}

export async function markAssociationInvitationUsed(token: string, acceptedByUserId: number): Promise<void> {
  const db = await getDb();
  if (!db) return;
  await db.update(associationInvitations)
    .set({
      usedAt: new Date(),
      acceptedByUserId,
    })
    .where(eq(associationInvitations.token, token));
}

export async function getAssociationInvitationSummariesForDirectoryEntryIds(directoryEntryIds: number[]): Promise<Record<number, AssociationInvitationStatusSummary>> {
  if (directoryEntryIds.length === 0) return {};

  const db = await getDb();
  if (!db) return {};

  const rows = await db.select()
    .from(associationInvitations)
    .where(inArray(associationInvitations.directoryEntryId, directoryEntryIds))
    .orderBy(desc(associationInvitations.createdAt));

  const now = new Date();
  const summaries: Record<number, AssociationInvitationStatusSummary> = {};

  rows.forEach((invitation) => {
    if (summaries[invitation.directoryEntryId]) return;

    let status: AssociationInvitationStatusSummary["status"] = "expired";
    if (invitation.usedAt) {
      status = "accepted";
    } else if (!invitation.revokedAt && invitation.expiresAt >= now) {
      status = "sent";
    }

    summaries[invitation.directoryEntryId] = {
      directoryEntryId: invitation.directoryEntryId,
      status,
      sentAt: invitation.sentAt,
      expiresAt: invitation.expiresAt,
      usedAt: invitation.usedAt,
      revokedAt: invitation.revokedAt,
      emailSent: invitation.emailSent,
      deliveryMode: invitation.deliveryMode,
    };
  });

  directoryEntryIds.forEach((directoryEntryId) => {
    if (!summaries[directoryEntryId]) {
      summaries[directoryEntryId] = {
        directoryEntryId,
        status: "never_sent",
        sentAt: null,
        expiresAt: null,
        usedAt: null,
        revokedAt: null,
        emailSent: false,
        deliveryMode: null,
      };
    }
  });

  return summaries;
}

// ============== EMAIL ACTION TOKEN FUNCTIONS ==============

export async function createEmailActionToken(data: {
  token: string;
  requestId: number;
  action: 'validee' | 'refusee' | 'acceptation_devis_salle' | 'refus_devis_salle';
  expiresAt: Date;
}) {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  await db.insert(emailActionTokens).values({
    token: data.token,
    requestId: data.requestId,
    action: data.action,
    expiresAt: data.expiresAt,
  });

  return data.token;
}

export async function getEmailActionToken(token: string) {
  const db = await getDb();
  if (!db) return null;

  const result = await db.select().from(emailActionTokens).where(eq(emailActionTokens.token, token)).limit(1);
  return result.length > 0 ? result[0] : null;
}

export async function markTokenAsUsed(token: string, userId: number) {
  const db = await getDb();
  if (!db) return;

  await db.update(emailActionTokens)
    .set({ used: true, usedAt: new Date(), usedBy: userId })
    .where(eq(emailActionTokens.token, token));
}


// ============== RESERVATION CALENDAR FUNCTIONS ==============

export async function getApprovedReservations(): Promise<(Request & { associationName: string; associationEmail: string | null })[]> {
  const db = await getDb();
  if (!db) return [];

  const result = await db
    .select({
      id: requests.id,
      associationId: requests.associationId,
      type: requests.type,
      titre: requests.titre,
      description: requests.description,
      formData: requests.formData,
      status: requests.status,
      priority: requests.priority,
      montantDemande: requests.montantDemande,
      montantAccorde: requests.montantAccorde,
      dateSubmission: requests.dateSubmission,
      dateTraitement: requests.dateTraitement,
      dateLimiteTraitement: requests.dateLimiteTraitement,
      assigneA: requests.assigneA,
      traitePar: requests.traitePar,
      commentaireAdmin: requests.commentaireAdmin,
      documentsJoints: requests.documentsJoints,
      createdAt: requests.createdAt,
      updatedAt: requests.updatedAt,
      associationName: associations.nomAssociation,
      associationEmail: associations.emailContact,
    })
    .from(requests)
    .innerJoin(associations, eq(requests.associationId, associations.id))
    .where(
      and(
        eq(requests.type, 'demande_salle'),
        eq(requests.status, 'validee')
      )
    )
    .orderBy(desc(requests.dateTraitement));

  return result as any;
}

export async function updateRequestSalles(id: number, newFormData: string): Promise<void> {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  await db.update(requests)
    .set({ formData: newFormData, updatedAt: new Date() })
    .where(eq(requests.id, id));
}

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


export async function deleteRequest(id: number): Promise<void> {
  const db = await getDb();
  if (!db) throw new Error("Database not available");
  // Delete associated history first
  await db.delete(requestHistory).where(eq(requestHistory.requestId, id));
  // Delete the request
  await db.delete(requests).where(eq(requests.id, id));
}

export async function getAssociationByRequestId(requestId: number): Promise<Association | undefined> {
  const db = await getDb();
  if (!db) return undefined;
  const request = await getRequestById(requestId);
  if (!request) return undefined;
  return getAssociationById(request.associationId);
}

// ============== MATERIAL RETURN FOLLOWUPS ==============

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

export async function getMaterialReturnFollowupByToken(token: string): Promise<MaterialReturnFollowup | undefined> {
  const db = await getDb();
  if (!db) return undefined;
  const result = await db.select().from(materialReturnFollowups).where(eq(materialReturnFollowups.uploadToken, token)).limit(1);
  return result.length > 0 ? result[0] : undefined;
}

export async function upsertMaterialReturnFollowup(
  requestId: number,
  data: Omit<InsertMaterialReturnFollowup, "requestId">
): Promise<number> {
  const db = await getDb();
  if (!db) throw new Error("Database not available");

  const existing = await getMaterialReturnFollowupByRequestId(requestId);
  if (existing) {
    await db.update(materialReturnFollowups)
      .set(data)
      .where(eq(materialReturnFollowups.requestId, requestId));
    return existing.id;
  }

  const result = await db.insert(materialReturnFollowups).values({
    requestId,
    ...data,
  });
  return result[0].insertId;
}

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

export async function listPendingMaterialReturnFollowups(now: Date): Promise<MaterialReturnFollowup[]> {
  const db = await getDb();
  if (!db) return [];
  return db.select()
    .from(materialReturnFollowups)
    .where(
      and(
        eq(materialReturnFollowups.status, "planifie"),
        lte(materialReturnFollowups.plannedSendAt, now)
      )
    )
    .orderBy(materialReturnFollowups.plannedSendAt);
}

export async function listMaterialReturnFollowupsToEscalate(): Promise<MaterialReturnFollowup[]> {
  const db = await getDb();
  if (!db) return [];
  return db.select()
    .from(materialReturnFollowups)
    .where(eq(materialReturnFollowups.status, "en_attente"));
}
