/**
 * Session User Name Tests
 *
 * Tests that user's name is properly propagated through the authentication flow:
 * 1. JWT callback stores name from user and database
 * 2. Session callback exposes name to client components
 * 3. Dashboard displays personalized greeting with user's name
 *
 * This prevents regression of "Welcome back, Partner" instead of actual user name.
 */

import { describe, test, expect, beforeEach, jest } from "@jest/globals";

// Mock NextAuth before importing auth
jest.mock("next-auth", () => {
  const mockNextAuth = jest.fn(() => ({
    handlers: { GET: jest.fn(), POST: jest.fn() },
    auth: jest.fn(),
    signIn: jest.fn(),
    signOut: jest.fn(),
  }));
  return {
    __esModule: true,
    default: mockNextAuth,
    NextAuthConfig: jest.fn(),
  };
});

jest.mock("next-auth/providers/google", () => ({
  __esModule: true,
  default: jest.fn(() => ({ id: "google", name: "Google" })),
}));

jest.mock("next-auth/providers/credentials", () => ({
  __esModule: true,
  default: jest.fn(() => ({ id: "credentials", name: "Credentials" })),
}));

jest.mock("next/headers", () => ({
  headers: jest.fn(() => ({
    get: jest.fn(() => null),
  })),
}));

jest.mock("@/lib/db");

// eslint-disable-next-line @typescript-eslint/no-require-imports
const { prisma: mockPrisma } = require("@/lib/db");

jest.mock("@/lib/logger", () => ({
  logger: {
    info: jest.fn(),
    warn: jest.fn(),
    error: jest.fn(),
  },
}));

jest.mock("@/lib/dual-database", () => ({
  saveToBothDatabases: jest.fn(),
  saveUserToBoth: jest.fn(),
  saveAuditLogToBoth: jest.fn(),
}));

jest.mock("@/lib/services/session-manager", () => ({
  createSession: jest.fn().mockResolvedValue({ id: "session-123" }),
}));

jest.mock("bcryptjs", () => ({
  compare: jest.fn(),
  hash: jest.fn(),
}));

describe("Session User Name", () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe("JWT Callback - Name Propagation", () => {
    test("should include name in token when user signs in", async () => {
      // Simulate JWT callback behavior
      const user = {
        id: "user-123",
        email: "john@example.com",
        name: "John Doe",
        role: "CUSTOMER",
      };

      const token: Record<string, unknown> = {};

      // Simulate JWT callback logic
      if (user) {
        token.id = user.id;
        token.name = user.name;
        token.role = user.role || "CUSTOMER";
      }

      expect(token.name).toBe("John Doe");
      expect(token.id).toBe("user-123");
    });

    test("should fetch name from database when enriching token", async () => {
      const mockUser = {
        id: "user-123",
        name: "Jane Smith",
        stripeCustomerId: "cus_123",
        role: "CUSTOMER",
      };

      mockPrisma.users.findUnique.mockResolvedValue(mockUser);

      // Simulate database lookup for token enrichment
      const token: { id: string; name: string | null } = {
        id: "user-123",
        name: null,
      };

      const dbUser = await mockPrisma.users.findUnique({
        where: { id: token.id as string },
        select: { stripeCustomerId: true, role: true, name: true },
      });

      if (dbUser) {
        token.name = dbUser.name || token.name;
      }

      expect(token.name).toBe("Jane Smith");
      expect(mockPrisma.users.findUnique).toHaveBeenCalledWith({
        where: { id: "user-123" },
        select: { stripeCustomerId: true, role: true, name: true },
      });
    });

    test("should preserve existing token name if database name is null", async () => {
      const mockUser = {
        id: "user-123",
        name: null, // No name in database
        stripeCustomerId: "cus_123",
        role: "CUSTOMER",
      };

      mockPrisma.users.findUnique.mockResolvedValue(mockUser);

      // Simulate database lookup with existing token name
      const token: { id: string; name: string | null } = {
        id: "user-123",
        name: "OAuth Name",
      };

      const dbUser = await mockPrisma.users.findUnique({
        where: { id: token.id as string },
        select: { stripeCustomerId: true, role: true, name: true },
      });

      if (dbUser) {
        token.name = dbUser.name || token.name;
      }

      // Should keep OAuth name when DB name is null
      expect(token.name).toBe("OAuth Name");
    });
  });

  describe("Session Callback - Name Exposure", () => {
    test("should set session user name from token", () => {
      const token = {
        id: "user-123",
        name: "John Doe",
        role: "CUSTOMER",
        stripeCustomerId: "cus_123",
      };

      const session = {
        user: {
          id: "",
          email: "john@example.com",
          name: null as string | null,
        },
        expires: new Date(Date.now() + 7200000).toISOString(),
      };

      // Simulate session callback logic
      if (session.user && token.id) {
        session.user.id = token.id as string;
        session.user.name = (token.name as string) || session.user.name || null;
      }

      expect(session.user.name).toBe("John Doe");
    });

    test("should fallback to existing session name when token name is missing", () => {
      const token = {
        id: "user-123",
        name: null,
        role: "CUSTOMER",
      };

      const session = {
        user: {
          id: "",
          email: "john@example.com",
          name: "Session Name", // Existing name
        },
        expires: new Date(Date.now() + 7200000).toISOString(),
      };

      // Simulate session callback logic
      if (session.user && token.id) {
        session.user.id = token.id as string;
        session.user.name = (token.name as string) || session.user.name || null;
      }

      expect(session.user.name).toBe("Session Name");
    });

    test("should return null when no name is available", () => {
      const token = {
        id: "user-123",
        name: null,
        role: "CUSTOMER",
      };

      const session = {
        user: {
          id: "",
          email: "john@example.com",
          name: null as string | null,
        },
        expires: new Date(Date.now() + 7200000).toISOString(),
      };

      // Simulate session callback logic
      if (session.user && token.id) {
        session.user.id = token.id as string;
        session.user.name = (token.name as string) || session.user.name || null;
      }

      expect(session.user.name).toBeNull();
    });
  });

  describe("Dashboard Greeting Logic", () => {
    test("should display user name in English greeting when available", () => {
      const session = {
        user: {
          id: "user-123",
          email: "john@example.com",
          name: "John Doe",
        },
      };

      const isAr = false;
      const greeting = isAr
        ? `مرحباً، ${(session.user && session.user.name) || "فريق ماويدي"}`
        : `Welcome back, ${(session.user && session.user.name) || "Mawidi Partner"}`;

      expect(greeting).toBe("Welcome back, John Doe");
      expect(greeting).not.toContain("Partner");
    });

    test("should display user name in Arabic greeting when available", () => {
      const session = {
        user: {
          id: "user-123",
          email: "ahmad@example.com",
          name: "أحمد محمد",
        },
      };

      const isAr = true;
      const greeting = isAr
        ? `مرحباً، ${(session.user && session.user.name) || "فريق ماويدي"}`
        : `Welcome back, ${(session.user && session.user.name) || "Mawidi Partner"}`;

      expect(greeting).toBe("مرحباً، أحمد محمد");
      expect(greeting).not.toContain("فريق ماويدي");
    });

    test('should fallback to "Mawidi Partner" when name is not available (English)', () => {
      const session = {
        user: {
          id: "user-123",
          email: "john@example.com",
          name: null,
        },
      };

      const isAr = false;
      const greeting = isAr
        ? `مرحباً، ${(session.user && session.user.name) || "فريق ماويدي"}`
        : `Welcome back, ${(session.user && session.user.name) || "Mawidi Partner"}`;

      expect(greeting).toBe("Welcome back, Mawidi Partner");
    });

    test('should fallback to "فريق ماويدي" when name is not available (Arabic)', () => {
      const session = {
        user: {
          id: "user-123",
          email: "ahmad@example.com",
          name: null,
        },
      };

      const isAr = true;
      const greeting = isAr
        ? `مرحباً، ${(session.user && session.user.name) || "فريق ماويدي"}`
        : `Welcome back, ${(session.user && session.user.name) || "Mawidi Partner"}`;

      expect(greeting).toBe("مرحباً، فريق ماويدي");
    });

    test("should handle empty string name as falsy", () => {
      const session = {
        user: {
          id: "user-123",
          email: "john@example.com",
          name: "", // Empty string
        },
      };

      const isAr = false;
      const greeting = isAr
        ? `مرحباً، ${(session.user && session.user.name) || "فريق ماويدي"}`
        : `Welcome back, ${(session.user && session.user.name) || "Mawidi Partner"}`;

      // Empty string is falsy, should fallback
      expect(greeting).toBe("Welcome back, Mawidi Partner");
    });

    test("should handle undefined session user gracefully", () => {
      const session = {
        user: null,
      };

      const isAr = false;
      const greeting = isAr
        ? `مرحباً، ${(session.user && (session.user as any).name) || "فريق ماويدي"}`
        : `Welcome back, ${(session.user && (session.user as any).name) || "Mawidi Partner"}`;

      expect(greeting).toBe("Welcome back, Mawidi Partner");
    });
  });

  describe("End-to-End Name Flow", () => {
    test("should maintain name through complete auth flow", async () => {
      // Step 1: User signs in (simulated)
      const signInUser = {
        id: "user-123",
        email: "john@example.com",
        name: "John Doe",
      };

      // Step 2: JWT callback adds name to token
      const token: Record<string, unknown> = {};
      token.id = signInUser.id;
      token.name = signInUser.name;

      // Step 3: Database lookup enriches token
      mockPrisma.users.findUnique.mockResolvedValue({
        name: "John Doe Updated", // Name might be updated in DB
        stripeCustomerId: "cus_123",
        role: "CUSTOMER",
      });

      const dbUser = await mockPrisma.users.findUnique({
        where: { id: token.id as string },
        select: { stripeCustomerId: true, role: true, name: true },
      });

      if (dbUser) {
        token.name = dbUser.name || token.name;
      }

      // Step 4: Session callback creates session with name
      const session = {
        user: {
          id: "",
          email: signInUser.email,
          name: null as string | null,
        },
        expires: new Date(Date.now() + 7200000).toISOString(),
      };

      session.user.id = token.id as string;
      session.user.name = (token.name as string) || null;

      // Step 5: Dashboard displays greeting
      const greeting = `Welcome back, ${session.user.name || "Mawidi Partner"}`;

      expect(greeting).toBe("Welcome back, John Doe Updated");
      expect(greeting).not.toContain("Partner");
    });

    test("should handle Google OAuth user with name", async () => {
      // Google OAuth provides name from Google profile
      const googleUser = {
        id: "google-user-123",
        email: "google@example.com",
        name: "Google User Name",
      };

      const token: Record<string, unknown> = {
        id: googleUser.id,
        name: googleUser.name,
      };

      // DB might not have name yet for new user
      mockPrisma.users.findUnique.mockResolvedValue({
        name: null, // New user, no name in DB yet
        stripeCustomerId: null,
        role: "CUSTOMER",
      });

      const dbUser = await mockPrisma.users.findUnique({
        where: { id: token.id as string },
        select: { stripeCustomerId: true, role: true, name: true },
      });

      if (dbUser) {
        // Should keep OAuth name when DB name is null
        token.name = dbUser.name || token.name;
      }

      const session = {
        user: {
          id: token.id as string,
          email: googleUser.email,
          name: token.name as string,
        },
      };

      const greeting = `Welcome back, ${session.user.name || "Mawidi Partner"}`;

      expect(greeting).toBe("Welcome back, Google User Name");
    });
  });
});

describe("User Name Database Query", () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  test("should query name field when enriching JWT", async () => {
    mockPrisma.users.findUnique.mockResolvedValue({
      name: "Database Name",
      stripeCustomerId: "cus_123",
      role: "CUSTOMER",
    });

    await mockPrisma.users.findUnique({
      where: { id: "user-123" },
      select: { stripeCustomerId: true, role: true, name: true },
    });

    expect(mockPrisma.users.findUnique).toHaveBeenCalledWith(
      expect.objectContaining({
        select: expect.objectContaining({
          name: true,
        }),
      }),
    );
  });

  test("should handle database errors gracefully", async () => {
    mockPrisma.users.findUnique.mockRejectedValue(
      new Error("Database connection failed"),
    );

    const token = { id: "user-123", name: "Fallback Name" };

    try {
      await mockPrisma.users.findUnique({
        where: { id: token.id },
        select: { stripeCustomerId: true, role: true, name: true },
      });
    } catch {
      // On error, token should retain its original name
    }

    // Token name should still be the fallback
    expect(token.name).toBe("Fallback Name");
  });
});
