// @ts-nocheck
/**
 * Unit tests for lib/services/social-auth.service.ts
 * Tests OAuth flows for social media platform integrations
 */

// Mock crypto for consistent testing
jest.mock("crypto", () => ({
  ...jest.requireActual("crypto"),
  randomBytes: jest.fn().mockReturnValue(Buffer.from("a".repeat(64), "hex")),
  timingSafeEqual: jest.fn((a, b) => a.toString() === b.toString()),
}));

// Mock encryption module
jest.mock("@/lib/encryption", () => ({
  encrypt: jest.fn((val: string) => `encrypted:${val}`),
  decrypt: jest.fn((val: string) => val.replace("encrypted:", "")),
}));

// Mock Prisma - define inline to avoid hoisting issues
jest.mock("@/lib/db");

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

// Mock fetch
const mockFetch = jest.fn();
global.fetch = mockFetch;

import {
  getAuthorizationUrl,
  handleCallback,
  refreshToken,
  getAccessToken,
  revokeAccess,
  getConnectedAccounts,
  getAccountById,
  updateAccountStatus,
  validateCsrfToken,
} from "@/lib/services/social-auth.service";
import { prisma } from "@/lib/db";

// Get mock reference
const mockPrisma = prisma as jest.Mocked<typeof prisma>;

describe("SocialAuthService", () => {
  const originalEnv = process.env;

  beforeEach(() => {
    jest.clearAllMocks();
    process.env = {
      ...originalEnv,
      META_APP_ID: "test-app-id",
      META_APP_SECRET: "test-app-secret",
    };
  });

  afterEach(() => {
    process.env = originalEnv;
  });

  describe("getAuthorizationUrl", () => {
    it("should generate Facebook OAuth URL with correct parameters", async () => {
      const result = await getAuthorizationUrl(
        "FACEBOOK",
        "user-123",
        "https://example.com/callback",
      );

      expect(result.url).toContain("https://www.facebook.com/");
      expect(result.url).toContain("dialog/oauth");
      expect(result.url).toContain("client_id=test-app-id");
      expect(result.url).toContain("redirect_uri=");
      expect(result.url).toContain("response_type=code");
      expect(result.csrfToken).toBeDefined();
    });

    it("should generate Instagram OAuth URL (same as Facebook)", async () => {
      const result = await getAuthorizationUrl(
        "INSTAGRAM",
        "user-123",
        "https://example.com/callback",
      );

      expect(result.url).toContain("https://www.facebook.com/");
      expect(result.url).toContain("instagram_content_publish");
    });

    it("should include required Meta scopes", async () => {
      const result = await getAuthorizationUrl(
        "FACEBOOK",
        "user-123",
        "https://example.com/callback",
      );

      expect(result.url).toContain("pages_manage_posts");
      expect(result.url).toContain("pages_read_engagement");
      expect(result.url).toContain("instagram_basic");
    });

    it("should throw error for unsupported platforms", async () => {
      await expect(
        getAuthorizationUrl(
          "TWITTER",
          "user-123",
          "https://example.com/callback",
        ),
      ).rejects.toThrow("OAuth not yet implemented");
    });

    it("should throw error when META_APP_ID is not set", async () => {
      delete process.env.META_APP_ID;

      await expect(
        getAuthorizationUrl(
          "FACEBOOK",
          "user-123",
          "https://example.com/callback",
        ),
      ).rejects.toThrow("META_APP_ID and META_APP_SECRET must be configured");
    });

    it("should include state parameter with encoded user info", async () => {
      const result = await getAuthorizationUrl(
        "FACEBOOK",
        "user-123",
        "https://example.com/callback",
      );

      expect(result.url).toContain("state=");
      // State should be base64url encoded
      const stateMatch = result.url.match(/state=([^&]+)/);
      expect(stateMatch).not.toBeNull();
    });
  });

  describe("handleCallback", () => {
    const mockState = () => {
      const state = {
        userId: "user-123",
        platform: "FACEBOOK",
        csrfToken: "a".repeat(64),
        redirectUrl: "https://example.com/callback",
        timestamp: Date.now(),
      };
      return Buffer.from(JSON.stringify(state)).toString("base64url");
    };

    beforeEach(() => {
      // Mock successful token exchange
      mockFetch
        .mockResolvedValueOnce({
          ok: true,
          json: () =>
            Promise.resolve({ access_token: "short-token", expires_in: 3600 }),
        })
        .mockResolvedValueOnce({
          ok: true,
          json: () =>
            Promise.resolve({
              access_token: "long-token",
              expires_in: 5184000,
            }),
        })
        .mockResolvedValueOnce({
          ok: true,
          json: () =>
            Promise.resolve({
              data: [
                {
                  id: "page-123",
                  name: "Test Page",
                  access_token: "page-token",
                  instagram_business_account: { id: "ig-123" },
                },
              ],
            }),
        })
        .mockResolvedValueOnce({
          ok: true,
          json: () =>
            Promise.resolve({ id: "ig-123", username: "testaccount" }),
        });

      mockPrisma.social_accounts.upsert.mockResolvedValue({
        id: "account-123",
        userId: "user-123",
        platform: "FACEBOOK",
        platformAccountId: "page-123",
        accountName: "Test Page",
        status: "CONNECTED",
        tokenExpiry: new Date(Date.now() + 5184000000),
        metadata: {},
        createdAt: new Date(),
        updatedAt: new Date(),
      });
    });

    it("should exchange code for tokens and create accounts", async () => {
      const state = mockState();
      const accounts = await handleCallback("FACEBOOK", "auth-code-123", state);

      expect(accounts).toBeDefined();
      expect(mockFetch).toHaveBeenCalled();
      expect(mockPrisma.social_accounts.upsert).toHaveBeenCalled();
    });

    it("should throw error for invalid state", async () => {
      await expect(
        handleCallback("FACEBOOK", "auth-code", "invalid-state"),
      ).rejects.toThrow("Invalid or expired OAuth state");
    });

    it("should throw error for platform mismatch", async () => {
      const state = mockState();
      await expect(
        handleCallback("INSTAGRAM", "auth-code", state), // State has FACEBOOK
      ).rejects.toThrow("Platform mismatch");
    });

    it("should throw error for expired state", async () => {
      const expiredState = {
        userId: "user-123",
        platform: "FACEBOOK",
        csrfToken: "a".repeat(64),
        redirectUrl: "https://example.com/callback",
        timestamp: Date.now() - 20 * 60 * 1000, // 20 minutes ago
      };
      const state = Buffer.from(JSON.stringify(expiredState)).toString(
        "base64url",
      );

      await expect(
        handleCallback("FACEBOOK", "auth-code", state),
      ).rejects.toThrow("Invalid or expired OAuth state");
    });

    it("should handle Meta API error during token exchange", async () => {
      mockFetch.mockReset();
      mockFetch.mockResolvedValueOnce({
        ok: false,
        json: () => Promise.resolve({ error: { message: "Invalid code" } }),
      });

      const state = mockState();
      await expect(
        handleCallback("FACEBOOK", "invalid-code", state),
      ).rejects.toThrow("Invalid code");
    });

    it("should throw error when no pages are found", async () => {
      mockFetch.mockReset();
      mockFetch
        .mockResolvedValueOnce({
          ok: true,
          json: () =>
            Promise.resolve({ access_token: "short-token", expires_in: 3600 }),
        })
        .mockResolvedValueOnce({
          ok: true,
          json: () =>
            Promise.resolve({
              access_token: "long-token",
              expires_in: 5184000,
            }),
        })
        .mockResolvedValueOnce({
          ok: true,
          json: () => Promise.resolve({ data: [] }), // No pages
        });

      const state = mockState();
      await expect(
        handleCallback("FACEBOOK", "auth-code", state),
      ).rejects.toThrow("No Facebook Pages found");
    });
  });

  describe("refreshToken", () => {
    it("should return existing token if not expired", async () => {
      const futureExpiry = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
      mockPrisma.social_accounts.findUnique.mockResolvedValueOnce({
        id: "account-123",
        platform: "FACEBOOK",
        accessToken: "encrypted:valid-token",
        tokenExpiry: futureExpiry,
      });

      const result = await refreshToken("account-123");

      expect(result.accessToken).toBe("valid-token");
      expect(mockFetch).not.toHaveBeenCalled(); // No API call needed
    });

    it("should refresh token when close to expiry", async () => {
      const nearExpiry = new Date(Date.now() + 2 * 60 * 1000); // 2 minutes (within buffer)
      mockPrisma.social_accounts.findUnique.mockResolvedValueOnce({
        id: "account-123",
        platform: "FACEBOOK",
        accessToken: "encrypted:old-token",
        tokenExpiry: nearExpiry,
      });

      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () =>
          Promise.resolve({
            access_token: "new-long-token",
            expires_in: 5184000,
          }),
      });

      mockPrisma.social_accounts.update.mockResolvedValueOnce({});

      const result = await refreshToken("account-123");

      expect(result.accessToken).toBe("new-long-token");
      expect(mockPrisma.social_accounts.update).toHaveBeenCalled();
    });

    it("should throw error for non-existent account", async () => {
      mockPrisma.social_accounts.findUnique.mockResolvedValueOnce(null);

      await expect(refreshToken("non-existent")).rejects.toThrow(
        "Social account not found",
      );
    });

    it("should throw error for unsupported platform", async () => {
      mockPrisma.social_accounts.findUnique.mockResolvedValueOnce({
        id: "account-123",
        platform: "TWITTER",
        accessToken: "encrypted:token",
        tokenExpiry: new Date(Date.now() - 1000), // Expired
      });

      await expect(refreshToken("account-123")).rejects.toThrow(
        "not implemented",
      );
    });
  });

  describe("getAccessToken", () => {
    it("should return valid access token", async () => {
      const futureExpiry = new Date(Date.now() + 24 * 60 * 60 * 1000);
      mockPrisma.social_accounts.findUnique.mockResolvedValueOnce({
        id: "account-123",
        platform: "FACEBOOK",
        accessToken: "encrypted:my-token",
        tokenExpiry: futureExpiry,
      });

      const token = await getAccessToken("account-123");

      expect(token).toBe("my-token");
    });
  });

  describe("revokeAccess", () => {
    it("should revoke Meta permissions and delete account", async () => {
      mockPrisma.social_accounts.findUnique.mockResolvedValueOnce({
        id: "account-123",
        platform: "FACEBOOK",
        accessToken: "encrypted:token-to-revoke",
      });

      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({}),
      });
      mockPrisma.social_accounts.delete.mockResolvedValueOnce({});

      await revokeAccess("account-123");

      expect(mockFetch).toHaveBeenCalledWith(
        expect.stringContaining("/me/permissions"),
        expect.objectContaining({ method: "DELETE" }),
      );
      expect(mockPrisma.social_accounts.delete).toHaveBeenCalledWith({
        where: { id: "account-123" },
      });
    });

    it("should throw error for non-existent account", async () => {
      mockPrisma.social_accounts.findUnique.mockResolvedValueOnce(null);

      await expect(revokeAccess("non-existent")).rejects.toThrow(
        "Social account not found",
      );
    });

    it("should still delete account even if API revocation fails", async () => {
      mockPrisma.social_accounts.findUnique.mockResolvedValueOnce({
        id: "account-123",
        platform: "FACEBOOK",
        accessToken: "encrypted:token",
      });

      mockFetch.mockRejectedValueOnce(new Error("API Error"));
      mockPrisma.social_accounts.delete.mockResolvedValueOnce({});

      // Should not throw
      await revokeAccess("account-123");

      expect(mockPrisma.social_accounts.delete).toHaveBeenCalled();
    });
  });

  describe("getConnectedAccounts", () => {
    it("should return all connected accounts for user", async () => {
      const mockAccounts = [
        {
          id: "acc-1",
          userId: "user-123",
          platform: "FACEBOOK",
          platformAccountId: "fb-123",
          accountName: "My Page",
          status: "CONNECTED",
          tokenExpiry: new Date(),
          metadata: {},
          createdAt: new Date(),
          updatedAt: new Date(),
        },
        {
          id: "acc-2",
          userId: "user-123",
          platform: "INSTAGRAM",
          platformAccountId: "ig-123",
          accountName: "@myaccount",
          status: "CONNECTED",
          tokenExpiry: new Date(),
          metadata: {},
          createdAt: new Date(),
          updatedAt: new Date(),
        },
      ];

      mockPrisma.social_accounts.findMany.mockResolvedValueOnce(mockAccounts);

      const accounts = await getConnectedAccounts("user-123");

      expect(accounts).toHaveLength(2);
      expect(accounts[0].platform).toBe("FACEBOOK");
      expect(accounts[1].platform).toBe("INSTAGRAM");
    });

    it("should return empty array for user with no accounts", async () => {
      mockPrisma.social_accounts.findMany.mockResolvedValueOnce([]);

      const accounts = await getConnectedAccounts("user-no-accounts");

      expect(accounts).toEqual([]);
    });
  });

  describe("getAccountById", () => {
    it("should return account when found and belongs to user", async () => {
      mockPrisma.social_accounts.findFirst.mockResolvedValueOnce({
        id: "acc-1",
        userId: "user-123",
        platform: "FACEBOOK",
        platformAccountId: "fb-123",
        accountName: "My Page",
        status: "CONNECTED",
        tokenExpiry: new Date(),
        metadata: {},
        createdAt: new Date(),
        updatedAt: new Date(),
      });

      const account = await getAccountById("acc-1", "user-123");

      expect(account).not.toBeNull();
      expect(account?.id).toBe("acc-1");
    });

    it("should return null when account not found", async () => {
      mockPrisma.social_accounts.findFirst.mockResolvedValueOnce(null);

      const account = await getAccountById("non-existent", "user-123");

      expect(account).toBeNull();
    });
  });

  describe("updateAccountStatus", () => {
    it("should update account status", async () => {
      mockPrisma.social_accounts.findUnique.mockResolvedValueOnce({
        metadata: {},
      });
      mockPrisma.social_accounts.update.mockResolvedValueOnce({});

      await updateAccountStatus("acc-1", "TOKEN_EXPIRED");

      expect(mockPrisma.social_accounts.update).toHaveBeenCalledWith({
        where: { id: "acc-1" },
        data: expect.objectContaining({
          status: "TOKEN_EXPIRED",
        }),
      });
    });

    it("should include error message in metadata when provided", async () => {
      mockPrisma.social_accounts.findUnique.mockResolvedValueOnce({
        metadata: { existing: "data" },
      });
      mockPrisma.social_accounts.update.mockResolvedValueOnce({});

      await updateAccountStatus("acc-1", "ERROR", "Token refresh failed");

      expect(mockPrisma.social_accounts.update).toHaveBeenCalledWith(
        expect.objectContaining({
          where: { id: "acc-1" },
          data: expect.objectContaining({
            status: "ERROR",
            metadata: expect.objectContaining({
              lastError: "Token refresh failed",
            }),
          }),
        }),
      );
    });
  });

  describe("validateCsrfToken", () => {
    it("should return true for valid CSRF token", () => {
      const state = {
        userId: "user-123",
        platform: "FACEBOOK",
        csrfToken: "valid-csrf-token-here-32chars-min",
        redirectUrl: "https://example.com/callback",
        timestamp: Date.now(),
      };
      const encodedState = Buffer.from(JSON.stringify(state)).toString(
        "base64url",
      );

      const result = validateCsrfToken(
        encodedState,
        "valid-csrf-token-here-32chars-min",
      );

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

    it("should return false for invalid CSRF token", () => {
      const state = {
        userId: "user-123",
        platform: "FACEBOOK",
        csrfToken: "correct-token-here-32-chars-min1",
        redirectUrl: "https://example.com/callback",
        timestamp: Date.now(),
      };
      const encodedState = Buffer.from(JSON.stringify(state)).toString(
        "base64url",
      );

      const result = validateCsrfToken(encodedState, "wrong-token");

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

    it("should return false for invalid state encoding", () => {
      const result = validateCsrfToken("invalid-base64", "any-token");

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

    it("should return false for expired state", () => {
      const state = {
        userId: "user-123",
        platform: "FACEBOOK",
        csrfToken: "token",
        redirectUrl: "https://example.com/callback",
        timestamp: Date.now() - 20 * 60 * 1000, // 20 minutes ago
      };
      const encodedState = Buffer.from(JSON.stringify(state)).toString(
        "base64url",
      );

      const result = validateCsrfToken(encodedState, "token");

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