import { describe, expect, it, vi, beforeEach } from "vitest";
import { appRouter } from "./routers";
import type { TrpcContext } from "./_core/context";
import * as db from "./db";

// Mock the database module
vi.mock("./db", () => ({
  getDashboardStats: vi.fn().mockResolvedValue({
    totalAssociations: 10,
    activeAssociations: 8,
    totalRequests: 25,
    pendingRequests: 5,
    validatedRequests: 15,
    rejectedRequests: 3,
    newAssociationsThisMonth: 2,
    newRequestsThisMonth: 8,
    requestsThisWeek: 3,
    overdueRequests: 1,
    acceptanceRate: 83,
  }),
  getRequestsPerMonth: vi.fn().mockResolvedValue([
    { month: '2026-01', total: 10, validated: 5, rejected: 2, pending: 3 },
  ]),
  getAssociationsPerMonth: vi.fn().mockResolvedValue([
    { month: '2026-01', total: 3 },
  ]),
  getRequestsByTypeStats: vi.fn().mockResolvedValue([
    { type: 'subvention_fonctionnement', total: 10, validated: 5, totalMontantAccorde: 50000 },
  ]),
  getAverageProcessingTime: vi.fn().mockResolvedValue(7.5),
  getPendingRequests: vi.fn().mockResolvedValue([]),
  getOverdueRequests: vi.fn().mockResolvedValue([]),
  getAdminUsers: vi.fn().mockResolvedValue([
    { id: 1, name: 'Admin', email: 'admin@test.com', role: 'admin', lastSignedIn: new Date() },
  ]),
  getAuditLogs: vi.fn().mockResolvedValue({ data: [], total: 0 }),
  getAdminNotifications: vi.fn().mockResolvedValue([]),
  getUnreadNotificationCount: vi.fn().mockResolvedValue(0),
  markNotificationAsRead: vi.fn().mockResolvedValue(undefined),
  markAllNotificationsAsRead: vi.fn().mockResolvedValue(undefined),
  searchAssociations: vi.fn().mockResolvedValue({ data: [], total: 0 }),
  getAssociationCommuneCounts: vi.fn().mockResolvedValue({
    all: 5,
    kourou: 2,
    sinnamary: 1,
    iracoubo: 1,
    saint_elie: 1,
  }),
  searchRequests: vi.fn().mockResolvedValue({ data: [], total: 0 }),
  getAllPortalSettings: vi.fn().mockResolvedValue([]),
  getPortalSetting: vi.fn().mockResolvedValue(null),
  getPortalSettingRecord: vi.fn().mockResolvedValue(null),
  setPortalSetting: vi.fn().mockResolvedValue(undefined),
  getAssociationDirectoryEntriesSummary: vi.fn().mockResolvedValue({ total: 5, withEmail: 4, withoutEmail: 1, registered: 2, unregistered: 3, lastImportAt: null }),
  listAssociationDirectoryEntriesWithStatus: vi.fn().mockResolvedValue({ data: [], total: 0 }),
  listAssociationDirectoryMapEntries: vi.fn().mockResolvedValue({ data: [], total: 0 }),
  getAssociationInvitationSummariesForDirectoryEntryIds: vi.fn().mockResolvedValue({}),
  listAssociationDirectoryEntries: vi.fn().mockResolvedValue([]),
  listAssociationDirectoryReviews: vi.fn().mockResolvedValue([]),
  findAssociationDirectoryMatch: vi.fn().mockResolvedValue({ status: "none", candidates: [], reason: "no match" }),
  upsertAssociationDirectoryEntry: vi.fn().mockResolvedValue("created"),
  createAssociationDirectoryEntry: vi.fn().mockResolvedValue(1),
  updateAssociationDirectoryEntry: vi.fn().mockResolvedValue(undefined),
  getAssociationDirectoryEntryById: vi.fn().mockResolvedValue(null),
  getAssociationDirectoryEntryDetails: vi.fn().mockResolvedValue(null),
  createAssociationDirectoryReview: vi.fn().mockResolvedValue(1),
  getAssociationDirectoryReviewById: vi.fn().mockResolvedValue(null),
  getPendingAssociationDirectoryReviewByUserId: vi.fn().mockResolvedValue(null),
  updateAssociationDirectoryReview: vi.fn().mockResolvedValue(undefined),
  getAssociationBySourceDirectoryEntryId: vi.fn().mockResolvedValue(null),
  createAssociationInvitation: vi.fn().mockResolvedValue(1),
  getAssociationInvitationByToken: vi.fn().mockResolvedValue(null),
  getActiveAssociationInvitationByDirectoryEntryId: vi.fn().mockResolvedValue(null),
  getLatestAssociationInvitationByDirectoryEntryId: vi.fn().mockResolvedValue(null),
  revokeAssociationInvitationsByDirectoryEntryId: vi.fn().mockResolvedValue(undefined),
  markAssociationInvitationUsed: vi.fn().mockResolvedValue(undefined),
  createAuditLog: vi.fn().mockResolvedValue(1),
  getMaterialReturnFollowupByRequestId: vi.fn().mockResolvedValue(null),
}));

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

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

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

  return { ctx };
}

function createSuperAdminContext(): { ctx: TrpcContext } {
  const user: AuthenticatedUser = {
    id: 3,
    openId: "super-admin-user",
    email: "superadmin@example.com",
    name: "Super Admin User",
    loginMethod: "manus",
    role: "super_admin",
    canManageLogistics: true,
    canSignSalle: true,
    delegatedSalleSignerUserId: null,
    salleSignatureDelegatedByUserIds: [],
    isActive: true,
    createdAt: new Date(),
    updatedAt: new Date(),
    lastSignedIn: new Date(),
  };

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

  return { ctx };
}

function createUserContext(): { ctx: TrpcContext } {
  const user: AuthenticatedUser = {
    id: 2,
    openId: "regular-user",
    email: "user@example.com",
    name: "Regular User",
    loginMethod: "manus",
    role: "user",
    isActive: true,
    createdAt: new Date(),
    updatedAt: new Date(),
    lastSignedIn: new Date(),
  };

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

  return { ctx };
}

beforeEach(() => {
  vi.clearAllMocks();
});

describe("Admin Dashboard Stats", () => {
  it("returns dashboard statistics for admin users", async () => {
    const { ctx } = createAdminContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.stats.getDashboard();

    expect(result).toBeDefined();
    expect(result.totalAssociations).toBe(10);
    expect(result.activeAssociations).toBe(8);
    expect(result.totalRequests).toBe(25);
    expect(result.pendingRequests).toBe(5);
    expect(result.acceptanceRate).toBe(83);
  });

  it("denies access to non-admin users", async () => {
    const { ctx } = createUserContext();
    const caller = appRouter.createCaller(ctx);

    await expect(caller.stats.getDashboard()).rejects.toThrow(
      "Accès réservé à l’accueil, aux administrateurs et aux super administrateurs",
    );
  });
});

describe("Admin Request Statistics", () => {
  it("returns requests per month for admin users", async () => {
    const { ctx } = createAdminContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.stats.getRequestsPerMonth({ months: 12 });

    expect(Array.isArray(result)).toBe(true);
  });

  it("returns requests by type for admin users", async () => {
    const { ctx } = createAdminContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.stats.getRequestsByType();

    expect(Array.isArray(result)).toBe(true);
  });

  it("returns average processing time for admin users", async () => {
    const { ctx } = createAdminContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.stats.getAverageProcessingTime();

    expect(result).toBe(7.5);
  });
});

describe("Admin Notifications", () => {
  it("returns notifications for admin users", async () => {
    const { ctx } = createAdminContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.notifications.getAll({ unreadOnly: false });

    expect(Array.isArray(result)).toBe(true);
  });

  it("returns unread count for admin users", async () => {
    const { ctx } = createAdminContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.notifications.getUnreadCount();

    expect(typeof result).toBe("number");
  });

  it("marks notification as read", async () => {
    const { ctx } = createAdminContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.notifications.markAsRead({ id: 1 });

    expect(result.success).toBe(true);
  });

  it("marks all notifications as read", async () => {
    const { ctx } = createAdminContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.notifications.markAllAsRead();

    expect(result.success).toBe(true);
  });
});

describe("Admin User Management", () => {
  it("lists admin users for super admin", async () => {
    const { ctx } = createSuperAdminContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.adminUsers.listAll();

    expect(Array.isArray(result)).toBe(true);
    expect(result.length).toBeGreaterThan(0);
    expect(result[0].role).toBe("admin");
  });

  it("denies admin user listing to regular admins", async () => {
    const { ctx } = createAdminContext();
    const caller = appRouter.createCaller(ctx);

    await expect(caller.adminUsers.listAll()).rejects.toThrow("Accès réservé aux super administrateurs");
  });
});

describe("Audit Log", () => {
  it("returns audit logs for admin users", async () => {
    const { ctx } = createAdminContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.auditLog.list({ limit: 10 });

    expect(result).toBeDefined();
    expect(result.data).toBeDefined();
    expect(Array.isArray(result.data)).toBe(true);
  });
});

describe("Association Directory Admin", () => {
  it("returns directory summary for admin", async () => {
    const { ctx } = createAdminContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.associationDirectory.getSummary();

    expect(result.total).toBe(5);
    expect(result.withEmail).toBe(4);
    expect(result.unregistered).toBe(3);
  });

  it("returns directory entries with registration filters for admin", async () => {
    const { ctx } = createAdminContext();
    const caller = appRouter.createCaller(ctx);

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

    expect(result).toEqual({ data: [], total: 0 });
  });

  it("denies directory summary to regular users", async () => {
    const { ctx } = createUserContext();
    const caller = appRouter.createCaller(ctx);

    await expect(caller.associationDirectory.getSummary()).rejects.toThrow("Accès réservé aux administrateurs");
  });
});

describe("Association Search", () => {
  it("searches associations for admin users", async () => {
    const { ctx } = createAdminContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.association.search({
      search: "test",
      limit: 10,
    });

    expect(result).toBeDefined();
    expect(result.data).toBeDefined();
    expect(typeof result.total).toBe("number");
  });

  it("returns commune counts for admin users", async () => {
    const { ctx } = createAdminContext();
    const caller = appRouter.createCaller(ctx);

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

    expect(result.kourou).toBe(2);
    expect(result.all).toBe(5);
  });
});

describe("Request Search", () => {
  it("searches requests for admin users", async () => {
    const { ctx } = createAdminContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.request.search({
      search: "test",
      status: "soumise",
      limit: 10,
    });

    expect(result).toBeDefined();
    expect(result.data).toBeDefined();
    expect(typeof result.total).toBe("number");
  });

  it("returns pending requests for admin users", async () => {
    const { ctx } = createAdminContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.request.getPending();

    expect(Array.isArray(result)).toBe(true);
  });

  it("returns overdue requests for admin users", async () => {
    const { ctx } = createAdminContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.request.getOverdue();

    expect(Array.isArray(result)).toBe(true);
  });
});

describe("Material Availability", () => {
  it("counts submitted and in-progress material requests before validation", async () => {
    vi.mocked(db.searchRequests).mockResolvedValue({
      data: [
        {
          id: 101,
          type: "demande_materiel_evenementiel",
          status: "soumise",
          formData: JSON.stringify({
            dateDebutManifestation: "2026-06-10",
            dateRestitution: "2026-06-12",
            materielsDemandes: { tente3x3: true },
            quantitesDemandees: { tente3x3: "4" },
          }),
        },
        {
          id: 102,
          type: "demande_materiel_evenementiel",
          status: "en_cours_traitement",
          formData: JSON.stringify({
            dateDebutManifestation: "2026-06-10",
            dateRestitution: "2026-06-12",
            materielsDemandes: { tente3x3: true },
            quantitesDemandees: { tente3x3: "3" },
          }),
        },
        {
          id: 103,
          type: "demande_materiel_evenementiel",
          status: "brouillon",
          formData: JSON.stringify({
            dateDebutManifestation: "2026-06-10",
            dateRestitution: "2026-06-12",
            materielsDemandes: { tente3x3: true },
            quantitesDemandees: { tente3x3: "2" },
          }),
        },
      ] as any,
      total: 3,
    });
    vi.mocked(db.getMaterialReturnFollowupByRequestId).mockResolvedValue(null as any);

    const { ctx } = createUserContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.request.getMaterialAvailability({
      dateDebut: "2026-06-10",
      dateFin: "2026-06-12",
    });

    const tente = result.items.find((item) => item.key === "tente3x3");
    expect(tente?.reserved).toBe(7);
    expect(tente?.available).toBe(5);
  });
});
