import { describe, expect, it, vi, beforeEach } from "vitest";
import { appRouter } from "./routers";
import { buildDataPrivacyConsent } from "../shared/privacyCompliance";
import type { TrpcContext } from "./_core/context";

// Mock the database functions with all required exports
vi.mock("./db", () => ({
  getDb: vi.fn(() => Promise.resolve({})),
  upsertUser: vi.fn(),
  getUserByEmail: vi.fn(() => Promise.resolve(undefined)),
  getUserByOpenId: vi.fn(),
  getUserById: vi.fn(),
  getAllUsers: vi.fn(() => Promise.resolve([])),
  getAdminUsers: vi.fn(() => Promise.resolve([])),
  updateUser: vi.fn(),
  
  // Association functions
  getAssociationByUserId: vi.fn(() => Promise.resolve(null)),
  getAssociationById: vi.fn(() => Promise.resolve(null)),
  getAssociationBySourceDirectoryEntryId: vi.fn(() => Promise.resolve(null)),
  createAssociation: vi.fn(() => Promise.resolve(1)),
  updateAssociation: vi.fn(() => Promise.resolve()),
  getAllAssociations: vi.fn(() => Promise.resolve([])),
  searchAssociations: vi.fn(() => Promise.resolve({ data: [], total: 0 })),
  getAssociationCommuneCounts: vi.fn(() => Promise.resolve({
    all: 0,
    kourou: 0,
    sinnamary: 0,
    iracoubo: 0,
    saint_elie: 0,
  })),
  toggleAssociationStatus: vi.fn(() => Promise.resolve()),
  getAssociationDirectoryEntryByNormalizedEmail: vi.fn(() => Promise.resolve(null)),
  getAssociationDirectoryEntryById: vi.fn(() => Promise.resolve(null)),
  getAssociationDirectoryEntriesSummary: vi.fn(() => Promise.resolve({ total: 0, withEmail: 0, withoutEmail: 0, lastImportAt: null })),
  listAssociationDirectoryEntriesWithStatus: vi.fn(() => Promise.resolve({ data: [], total: 0 })),
  listAssociationDirectoryMapEntries: vi.fn(() => Promise.resolve({ data: [], total: 0 })),
  getAssociationInvitationSummariesForDirectoryEntryIds: vi.fn(() => Promise.resolve({})),
  listAssociationDirectoryEntries: vi.fn(() => Promise.resolve([])),
  listAssociationDirectoryReviews: vi.fn(() => Promise.resolve([])),
  findAssociationDirectoryMatch: vi.fn(() => Promise.resolve({ status: "none", candidates: [], reason: "no match" })),
  upsertAssociationDirectoryEntry: vi.fn(() => Promise.resolve("created")),
  createAssociationDirectoryEntry: vi.fn(() => Promise.resolve(1)),
  updateAssociationDirectoryEntry: vi.fn(() => Promise.resolve()),
  getAssociationDirectoryEntryDetails: vi.fn(() => Promise.resolve(null)),
  createAssociationDirectoryReview: vi.fn(() => Promise.resolve(1)),
  getAssociationDirectoryReviewById: vi.fn(() => Promise.resolve(null)),
  getPendingAssociationDirectoryReviewByUserId: vi.fn(() => Promise.resolve(null)),
  updateAssociationDirectoryReview: vi.fn(() => Promise.resolve()),
  createAssociationInvitation: vi.fn(() => Promise.resolve(1)),
  getAssociationInvitationByToken: vi.fn(() => Promise.resolve(null)),
  getActiveAssociationInvitationByDirectoryEntryId: vi.fn(() => Promise.resolve(null)),
  getLatestAssociationInvitationByDirectoryEntryId: vi.fn(() => Promise.resolve(null)),
  revokeAssociationInvitationsByDirectoryEntryId: vi.fn(() => Promise.resolve()),
  markAssociationInvitationUsed: vi.fn(() => Promise.resolve()),
  
  // Document functions
  getDocumentsByAssociationId: vi.fn(() => Promise.resolve([])),
  getDocumentById: vi.fn(() => Promise.resolve(null)),
  createDocument: vi.fn(() => Promise.resolve(1)),
  deleteDocument: vi.fn(() => Promise.resolve()),
  
  // Request functions
  getRequestsByAssociationId: vi.fn(() => Promise.resolve([])),
  getRequestById: vi.fn(() => Promise.resolve(null)),
  createRequest: vi.fn(() => Promise.resolve(1)),
  updateRequest: vi.fn(() => Promise.resolve()),
  getAllRequests: vi.fn(() => Promise.resolve([])),
  getRequestsByStatus: vi.fn(() => Promise.resolve([])),
  searchRequests: vi.fn(() => Promise.resolve({ data: [], total: 0 })),
  getPendingRequests: vi.fn(() => Promise.resolve([])),
  getOverdueRequests: vi.fn(() => Promise.resolve([])),
  assignRequest: vi.fn(() => Promise.resolve()),
  
  // Request history
  createRequestHistory: vi.fn(() => Promise.resolve(1)),
  getRequestHistoryByRequestId: vi.fn(() => Promise.resolve([])),
  
  // Templates
  getAllRequestTemplates: vi.fn(() => Promise.resolve([])),
  getRequestTemplateById: vi.fn(() => Promise.resolve(null)),
  getRequestTemplateByType: vi.fn(() => Promise.resolve(null)),
  createRequestTemplate: vi.fn(() => Promise.resolve(1)),
  updateRequestTemplate: vi.fn(() => Promise.resolve()),
  
  getAllResponseTemplates: vi.fn(() => Promise.resolve([])),
  getResponseTemplateById: vi.fn(() => Promise.resolve(null)),
  createResponseTemplate: vi.fn(() => Promise.resolve(1)),
  updateResponseTemplate: vi.fn(() => Promise.resolve()),
  deleteResponseTemplate: vi.fn(() => Promise.resolve()),
  
  // Audit & Notifications
  createAuditLog: vi.fn(() => Promise.resolve(1)),
  getAuditLogs: vi.fn(() => Promise.resolve({ data: [], total: 0 })),
  createAdminNotification: vi.fn(() => Promise.resolve(1)),
  getAdminNotifications: vi.fn(() => Promise.resolve([])),
  markNotificationAsRead: vi.fn(() => Promise.resolve()),
  markAllNotificationsAsRead: vi.fn(() => Promise.resolve()),
  getUnreadNotificationCount: vi.fn(() => Promise.resolve(0)),
  
  // Settings
  getPortalSetting: vi.fn(() => Promise.resolve(null)),
  setPortalSetting: vi.fn(() => Promise.resolve()),
  getAllPortalSettings: vi.fn(() => Promise.resolve([])),
  
  // Stats
  getDashboardStats: vi.fn(() => Promise.resolve({
    totalAssociations: 0,
    activeAssociations: 0,
    totalRequests: 0,
    pendingRequests: 0,
    validatedRequests: 0,
    rejectedRequests: 0,
    newAssociationsThisMonth: 0,
    newRequestsThisMonth: 0,
    requestsThisWeek: 0,
    overdueRequests: 0,
    acceptanceRate: 0,
  })),
  getRequestsPerMonth: vi.fn(() => Promise.resolve([])),
  getAssociationsPerMonth: vi.fn(() => Promise.resolve([])),
  getRequestsByTypeStats: vi.fn(() => Promise.resolve([])),
  getAverageProcessingTime: vi.fn(() => Promise.resolve(null)),
}));

// Mock storage
vi.mock("./storage", () => ({
  storagePut: vi.fn(() => Promise.resolve({ url: "https://example.com/file.pdf", key: "test-key" })),
  storageGet: vi.fn(() => Promise.resolve({ url: "https://example.com/file.pdf", key: "test-key" })),
}));

// Mock notification
vi.mock("./_core/notification", () => ({
  notifyOwner: vi.fn(() => Promise.resolve(true)),
}));

vi.mock("./mailer", () => ({
  canSendOperationalEmails: vi.fn(() => true),
  sendOperationalEmail: vi.fn(() => Promise.resolve({ sent: true })),
}));

vi.mock("./_core/auth", () => ({
  clearSessionCookie: vi.fn(),
  createSessionToken: vi.fn(() => Promise.resolve("session-token")),
  loginLocalUser: vi.fn(),
  registerLocalUser: vi.fn((input) => Promise.resolve({
    id: 99,
    openId: input.email,
    email: input.email,
    name: input.name,
    loginMethod: "local_jwt",
    role: "user",
    isActive: true,
    createdAt: new Date(),
    updatedAt: new Date(),
    lastSignedIn: new Date(),
  })),
  setSessionCookie: vi.fn(),
}));

type AuthenticatedUser = NonNullable<TrpcContext["user"]>;

function createAuthContext(role: "user" | "admin" | "directrice" | "super_admin" = "user"): TrpcContext {
  const user: AuthenticatedUser = {
    id: 1,
    openId: "test-user-123",
    email: "test@example.com",
    name: "Test User",
    loginMethod: "manus",
    role,
    canManageLogistics: false,
    canSignSalle: role === "directrice" || role === "super_admin",
    delegatedSalleSignerUserId: null,
    salleSignatureDelegatedByUserIds: [],
    isActive: true,
    createdAt: new Date(),
    updatedAt: new Date(),
    lastSignedIn: new Date(),
  };

  return {
    user,
    req: {
      protocol: "https",
      headers: {},
    } as TrpcContext["req"],
    res: {
      clearCookie: vi.fn(),
    } as unknown as TrpcContext["res"],
  };
}

function createUnauthContext(): TrpcContext {
  return {
    user: null,
    req: {
      protocol: "https",
      headers: {},
    } as TrpcContext["req"],
    res: {
      clearCookie: vi.fn(),
    } as unknown as TrpcContext["res"],
  };
}

describe("association router", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("getMyProfile returns null when no association exists", async () => {
    const ctx = createAuthContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.association.getMyProfile();
    expect(result).toBeNull();
  });

  it("exposes a protected portal directory listing", async () => {
    const db = await import("./db");
    const ctx = createAuthContext();
    const caller = appRouter.createCaller(ctx);

    (db.listAssociationDirectoryEntriesWithStatus as any).mockResolvedValue({
      total: 1,
      data: [
        {
          id: 12,
          nomAssociation: "Association des Savanes",
          emailOfficiel: "contact@example.com",
          ville: "Kourou",
          importedAt: new Date(),
          registered: false,
          registeredAt: null,
          invitationStatus: undefined,
        },
      ],
    });

    const result = await caller.associationDirectory.listPortal({
      commune: "kourou",
      registrationStatus: "unregistered",
      limit: 20,
    });

    expect(result.total).toBe(1);
    expect(result.data[0]?.nomAssociation).toBe("Association des Savanes");
  });

  it("returns a public secure invitation when the token is still valid", async () => {
    const db = await import("./db");
    const caller = appRouter.createCaller(createUnauthContext());

    (db.getAssociationInvitationByToken as any).mockResolvedValue({
      token: "invite-token",
      directoryEntryId: 42,
      emailOfficiel: "contact@example.com",
      emailOfficielNormalise: "contact@example.com",
      sentByUserId: 1,
      deliveryMode: "email",
      emailSent: true,
      sentAt: new Date(),
      expiresAt: new Date(Date.now() + 60_000),
      usedAt: null,
      revokedAt: null,
      acceptedByUserId: null,
      createdAt: new Date(),
      updatedAt: new Date(),
    });
    (db.getAssociationDirectoryEntryById as any).mockResolvedValue({
      id: 42,
      nomAssociation: "Association des Savanes",
      emailOfficiel: "contact@example.com",
      nomRepresentant: "Mme Test",
      ville: "Kourou",
    });

    const result = await caller.associationInvitation.getPublic({ token: "invite-token" });

    expect(result.valid).toBe(true);
    expect(result.association.nomAssociation).toBe("Association des Savanes");
  });

  it("registers a user from a valid invitation and marks it as used", async () => {
    const db = await import("./db");
    const ctx = createUnauthContext();
    const caller = appRouter.createCaller(ctx);

    (db.getAssociationInvitationByToken as any).mockResolvedValue({
      token: "invite-token",
      directoryEntryId: 42,
      emailOfficiel: "contact@example.com",
      emailOfficielNormalise: "contact@example.com",
      sentByUserId: 1,
      deliveryMode: "email",
      emailSent: true,
      sentAt: new Date(),
      expiresAt: new Date(Date.now() + 60_000),
      usedAt: null,
      revokedAt: null,
      acceptedByUserId: null,
      createdAt: new Date(),
      updatedAt: new Date(),
    });
    (db.getAssociationDirectoryEntryById as any).mockResolvedValue({
      id: 42,
      nomAssociation: "Association Importée",
      emailOfficiel: "contact@example.com",
      emailOfficielNormalise: "contact@example.com",
      siret: "12345678901234",
      rna: "W123456789",
      adresse: "1 rue des Savanes",
      codePostal: "97310",
      ville: "Kourou",
      telephone: "0594000000",
      siteWeb: null,
      dateCreation: null,
      objetAssociation: "Culture",
      statutJuridique: "association_loi_1901",
      nomRepresentant: "Mme Test",
      fonctionRepresentant: "Présidente",
      sourceFileName: "bordereau.xlsx",
      sourceRowNumber: 2,
      sourceFingerprint: "abc",
      isActive: true,
      importedAt: new Date(),
      updatedAt: new Date(),
    });
    (db.getAssociationBySourceDirectoryEntryId as any).mockResolvedValue(null);
    (db.getAssociationByUserId as any).mockResolvedValue(null);

    const result = await caller.auth.register({
      name: "Mme Test",
      email: "contact@example.com",
      password: "motdepasse",
      thematique: "culture_loisirs",
      invitationToken: "invite-token",
      privacyConsent: buildDataPrivacyConsent("register_account"),
    });

    expect(result.email).toBe("contact@example.com");
    expect(db.createAssociation).toHaveBeenCalled();
    expect(db.markAssociationInvitationUsed).toHaveBeenCalledWith("invite-token", 99);
  });

  it("getMyProfile auto-creates profile from imported directory when email matches", async () => {
    const db = await import("./db");
    const ctx = createAuthContext();
    const caller = appRouter.createCaller(ctx);

    (db.getAssociationDirectoryEntryByNormalizedEmail as any).mockResolvedValue({
      id: 42,
      nomAssociation: "Association Importée",
      emailOfficiel: "test@example.com",
      emailOfficielNormalise: "test@example.com",
      siret: "12345678901234",
      rna: "W123456789",
      adresse: "1 rue des Savanes",
      codePostal: "97310",
      ville: "Kourou",
      telephone: "0594000000",
      siteWeb: null,
      dateCreation: null,
      objetAssociation: "Culture",
      statutJuridique: "association_loi_1901",
      nomRepresentant: "Mme Test",
      fonctionRepresentant: "Présidente",
      sourceFileName: "bordereau.xlsx",
      sourceRowNumber: 2,
      sourceFingerprint: "abc",
      isActive: true,
      importedAt: new Date(),
      updatedAt: new Date(),
    });

    (db.getAssociationById as any).mockResolvedValue({
      id: 1,
      userId: 1,
      sourceDirectoryEntryId: 42,
      nomAssociation: "Association Importée",
      siret: "12345678901234",
      rna: "W123456789",
      adresse: "1 rue des Savanes",
      codePostal: "97310",
      ville: "Kourou",
      telephone: "0594000000",
      emailContact: "test@example.com",
      siteWeb: null,
      dateCreation: null,
      objetAssociation: "Culture",
      statutJuridique: "association_loi_1901",
      nomRepresentant: "Mme Test",
      fonctionRepresentant: "Présidente",
      profileComplete: true,
      isActive: true,
      createdAt: new Date(),
      updatedAt: new Date(),
    });

    const result = await caller.association.getMyProfile();

    expect(db.createAssociation).toHaveBeenCalled();
    expect(result?.sourceDirectoryEntryId).toBe(42);
    expect(result?.nomAssociation).toBe("Association Importée");
  });

  it("upsertProfile requires authentication", async () => {
    const ctx = createUnauthContext();
    const caller = appRouter.createCaller(ctx);

    await expect(
      caller.association.upsertProfile({
        nomAssociation: "Test Association",
      })
    ).rejects.toThrow();
  });

  it("upsertProfile creates association for authenticated user", async () => {
    const ctx = createAuthContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.association.upsertProfile({
      nomAssociation: "Test Association",
      siret: "12345678901234",
      adresse: "123 rue Test",
      codePostal: "75001",
      ville: "Paris",
      gouvernance: {
        representantLegal: {
          prenom: "Jean",
          nom: "Dupont",
          fonction: "president",
          email: "",
          telephone: "",
        },
        membres: [],
      },
      privacyConsent: buildDataPrivacyConsent("save_profile"),
    });

    expect(result).toBeDefined();
    expect(result).toHaveProperty('id');
    expect(result.updated).toBe(false);
  });
});

describe("document router", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("getMyDocuments requires authentication", async () => {
    const ctx = createUnauthContext();
    const caller = appRouter.createCaller(ctx);

    await expect(caller.document.getMyDocuments()).rejects.toThrow();
  });

  it("getMyDocuments returns empty array when no documents exist", async () => {
    const ctx = createAuthContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.document.getMyDocuments();
    expect(result).toEqual([]);
  });
});

describe("request router", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("getMyRequests requires authentication", async () => {
    const ctx = createUnauthContext();
    const caller = appRouter.createCaller(ctx);

    await expect(caller.request.getMyRequests()).rejects.toThrow();
  });

  it("getMyRequests returns empty array when no requests exist", async () => {
    const ctx = createAuthContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.request.getMyRequests();
    expect(result).toEqual([]);
  });
});

describe("stats router", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("getDashboard requires admin role", async () => {
    const ctx = createAuthContext("user");
    const caller = appRouter.createCaller(ctx);

    await expect(caller.stats.getDashboard()).rejects.toThrow();
  });

  it("getDashboard returns stats for admin", async () => {
    const ctx = createAuthContext("admin");
    const caller = appRouter.createCaller(ctx);

    const result = await caller.stats.getDashboard();
    expect(result).toBeDefined();
    expect(result.totalAssociations).toBe(0);
    expect(result.totalRequests).toBe(0);
  });
});
