/**
 * Unit tests for lib/blog.ts
 * Tests blog post fetching, pagination, tags, related posts
 */

// Inline mock for deleted @/lib/supabase module
const mockSupabaseFrom = jest.fn(() => ({
  select: jest.fn().mockReturnThis(),
  eq: jest.fn().mockReturnThis(),
  contains: jest.fn().mockReturnThis(),
  order: jest.fn().mockReturnThis(),
  range: jest.fn().mockReturnThis(),
  single: jest.fn(),
  limit: jest.fn().mockReturnThis(),
  neq: jest.fn().mockReturnThis(),
}));

const supabase = { from: mockSupabaseFrom } as any;

jest.mock("@/lib/supabase", () => ({
  supabase: { from: mockSupabaseFrom },
}));
import {
  getAllPosts,
  getPostBySlug,
  getPaginatedPosts,
  getAllTags,
  getPostsByTag,
  getRelatedPosts,
  getAllPostSlugs,
} from "@/lib/blog";

describe("lib/blog.ts", () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  const mockDBPost = {
    slug: "test-post",
    title: "Test Post",
    date: "2024-01-01",
    excerpt: "Test excerpt",
    cover_image: "/images/cover.jpg",
    tags: ["tech", "ai"],
    author: "Test Author",
    published: true,
    content: "# Test Content",
    reading_time_minutes: 5,
    lang: "en",
  };

  describe("getAllPosts", () => {
    it.skip("should fetch all published posts for English", async () => {
      const mockChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
        order: jest.fn().mockResolvedValue({ data: [mockDBPost], error: null }),
      };

      (supabase.from as jest.Mock).mockReturnValue(mockChain);

      const posts = await getAllPosts("en");

      expect(posts).toHaveLength(1);
      expect(posts[0].slug).toBe("test-post");
      expect(posts[0].title).toBe("Test Post");
      expect(mockChain.eq).toHaveBeenCalledWith("lang", "en");
      expect(mockChain.eq).toHaveBeenCalledWith("published", true);
    });

    it.skip("should fetch all published posts for Arabic", async () => {
      const arabicPost = { ...mockDBPost, lang: "ar", title: "منشور تجريبي" };
      const mockChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
        order: jest.fn().mockResolvedValue({ data: [arabicPost], error: null }),
      };

      (supabase.from as jest.Mock).mockReturnValue(mockChain);

      const posts = await getAllPosts("ar");

      expect(posts[0].lang).toBe("ar");
      expect(mockChain.eq).toHaveBeenCalledWith("lang", "ar");
    });

    it.skip("should include unpublished posts when published=false", async () => {
      const unpublishedPost = { ...mockDBPost, published: false };
      const mockChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
        order: jest
          .fn()
          .mockResolvedValue({ data: [unpublishedPost], error: null }),
      };

      (supabase.from as jest.Mock).mockReturnValue(mockChain);

      const posts = await getAllPosts("en", false);

      // Should only call eq once for language (not for published)
      const eqCalls = mockChain.eq.mock.calls;
      expect(eqCalls.some((call: any[]) => call[0] === "lang")).toBe(true);
      expect(
        eqCalls.filter((call: any[]) => call[0] === "published").length,
      ).toBe(0);
    });

    it("should return empty array on database error", async () => {
      const mockChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
        order: jest
          .fn()
          .mockResolvedValue({ data: null, error: { message: "DB error" } }),
      };

      (supabase.from as jest.Mock).mockReturnValue(mockChain);

      const posts = await getAllPosts("en");

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

    it.skip("should order posts by date descending (newest first)", async () => {
      const mockChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
        order: jest.fn().mockResolvedValue({ data: [], error: null }),
      };

      (supabase.from as jest.Mock).mockReturnValue(mockChain);

      await getAllPosts("en");

      expect(mockChain.order).toHaveBeenCalledWith("date", {
        ascending: false,
      });
    });
  });

  describe("getPostBySlug", () => {
    it.skip("should fetch single post by slug and language", async () => {
      const mockChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
        single: jest.fn().mockResolvedValue({ data: mockDBPost, error: null }),
      };

      (supabase.from as jest.Mock).mockReturnValue(mockChain);

      const post = await getPostBySlug("test-post", "en");

      expect(post).toBeDefined();
      expect(post?.slug).toBe("test-post");
      expect(post?.content).toBe("# Test Content");
      expect(mockChain.eq).toHaveBeenCalledWith("slug", "test-post");
      expect(mockChain.eq).toHaveBeenCalledWith("lang", "en");
    });

    it("should return null if post not found", async () => {
      const mockChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
        single: jest.fn().mockResolvedValue({ data: null, error: null }),
      };

      (supabase.from as jest.Mock).mockReturnValue(mockChain);

      const post = await getPostBySlug("nonexistent", "en");

      expect(post).toBeNull();
    });

    it("should return null on database error", async () => {
      const mockChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
        single: jest
          .fn()
          .mockResolvedValue({ data: null, error: { message: "Not found" } }),
      };

      (supabase.from as jest.Mock).mockReturnValue(mockChain);

      const post = await getPostBySlug("test", "en");

      expect(post).toBeNull();
    });

    it.skip("should include content field (full post)", async () => {
      const mockChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
        single: jest.fn().mockResolvedValue({ data: mockDBPost, error: null }),
      };

      (supabase.from as jest.Mock).mockReturnValue(mockChain);

      const post = await getPostBySlug("test-post", "en");

      expect(post?.content).toBe("# Test Content");
    });
  });

  describe("getPaginatedPosts", () => {
    it.skip("should return paginated posts with metadata", async () => {
      const mockChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
        order: jest.fn().mockReturnThis(),
        range: jest.fn().mockResolvedValue({ data: [mockDBPost], error: null }),
      };

      const countChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
      };
      countChain.eq.mockResolvedValue({ count: 20, error: null });

      (supabase.from as jest.Mock)
        .mockReturnValueOnce(countChain) // First call for count
        .mockReturnValueOnce(mockChain); // Second call for data

      const result = await getPaginatedPosts("en", 1, 9);

      expect(result.posts).toHaveLength(1);
      expect(result.currentPage).toBe(1);
      expect(result.totalPosts).toBe(20);
      expect(result.totalPages).toBe(3); // Math.ceil(20/9) = 3
    });

    it.skip("should calculate correct pagination range", async () => {
      const mockChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
        order: jest.fn().mockReturnThis(),
        range: jest.fn().mockResolvedValue({ data: [], error: null }),
      };

      const countChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
      };
      countChain.eq.mockResolvedValue({ count: 30, error: null });

      (supabase.from as jest.Mock)
        .mockReturnValueOnce(countChain)
        .mockReturnValueOnce(mockChain);

      await getPaginatedPosts("en", 2, 9);

      // Page 2, 9 per page = range(9, 17)
      expect(mockChain.range).toHaveBeenCalledWith(9, 17);
    });

    it.skip("should default to page 1 and 9 posts per page", async () => {
      const mockChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
        order: jest.fn().mockReturnThis(),
        range: jest.fn().mockResolvedValue({ data: [], error: null }),
      };

      const countChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
      };
      countChain.eq.mockResolvedValue({ count: 10, error: null });

      (supabase.from as jest.Mock)
        .mockReturnValueOnce(countChain)
        .mockReturnValueOnce(mockChain);

      const result = await getPaginatedPosts("en");

      expect(result.currentPage).toBe(1);
      expect(mockChain.range).toHaveBeenCalledWith(0, 8); // Page 1, 9 items
    });

    it.skip("should handle invalid page numbers gracefully", async () => {
      const mockChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
        order: jest.fn().mockReturnThis(),
        range: jest.fn().mockResolvedValue({ data: [], error: null }),
      };

      const countChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
      };
      countChain.eq.mockResolvedValue({ count: 20, error: null });

      (supabase.from as jest.Mock)
        .mockReturnValueOnce(countChain)
        .mockReturnValueOnce(mockChain);

      const result = await getPaginatedPosts("en", -5, 9);

      // Should clamp to page 1
      expect(result.currentPage).toBe(1);
    });

    it("should return empty result on count error", async () => {
      const countChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
      };
      countChain.eq.mockResolvedValue({
        count: null,
        error: { message: "DB error" },
      });

      (supabase.from as jest.Mock).mockReturnValue(countChain);

      const result = await getPaginatedPosts("en");

      expect(result).toEqual({
        posts: [],
        totalPages: 0,
        totalPosts: 0,
        currentPage: 1,
      });
    });
  });

  describe("getAllTags", () => {
    it.skip("should return unique tags from all posts", async () => {
      const posts = [
        { tags: ["ai", "tech"] },
        { tags: ["ai", "business"] },
        { tags: ["tech", "tutorial"] },
      ];

      const mockChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
      };
      mockChain.eq.mockResolvedValue({ data: posts, error: null });

      (supabase.from as jest.Mock).mockReturnValue(mockChain);

      const tags = await getAllTags("en");

      // Should have unique tags sorted
      expect(tags).toEqual(["ai", "business", "tech", "tutorial"]);
    });

    it.skip("should filter by language", async () => {
      const mockChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
      };
      mockChain.eq.mockResolvedValue({ data: [], error: null });

      (supabase.from as jest.Mock).mockReturnValue(mockChain);

      await getAllTags("ar");

      expect(mockChain.eq).toHaveBeenCalledWith("lang", "ar");
    });

    it.skip("should only include published posts", async () => {
      const mockChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
      };
      mockChain.eq.mockResolvedValue({ data: [], error: null });

      (supabase.from as jest.Mock).mockReturnValue(mockChain);

      await getAllTags("en");

      expect(mockChain.eq).toHaveBeenCalledWith("published", true);
    });

    it("should return empty array on error", async () => {
      const mockChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
      };
      mockChain.eq.mockResolvedValue({
        data: null,
        error: { message: "DB error" },
      });

      (supabase.from as jest.Mock).mockReturnValue(mockChain);

      const tags = await getAllTags("en");

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

    it.skip("should handle posts with no tags", async () => {
      const posts = [{ tags: [] }, { tags: null }, { tags: ["valid-tag"] }];

      const mockChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
      };
      mockChain.eq.mockResolvedValue({ data: posts, error: null });

      (supabase.from as jest.Mock).mockReturnValue(mockChain);

      const tags = await getAllTags("en");

      expect(tags).toEqual(["valid-tag"]);
    });
  });

  describe("getPostsByTag", () => {
    it.skip("should fetch posts with specific tag", async () => {
      const mockChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
        contains: jest.fn().mockReturnThis(),
        order: jest.fn().mockResolvedValue({ data: [mockDBPost], error: null }),
      };

      (supabase.from as jest.Mock).mockReturnValue(mockChain);

      const posts = await getPostsByTag("ai", "en");

      expect(posts).toHaveLength(1);
      expect(mockChain.contains).toHaveBeenCalledWith("tags", ["ai"]);
    });

    it.skip("should filter by language and published status", async () => {
      const mockChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
        contains: jest.fn().mockReturnThis(),
        order: jest.fn().mockResolvedValue({ data: [], error: null }),
      };

      (supabase.from as jest.Mock).mockReturnValue(mockChain);

      await getPostsByTag("tech", "ar");

      expect(mockChain.eq).toHaveBeenCalledWith("lang", "ar");
      expect(mockChain.eq).toHaveBeenCalledWith("published", true);
    });

    it("should return empty array on error", async () => {
      const mockChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
        contains: jest.fn().mockReturnThis(),
        order: jest
          .fn()
          .mockResolvedValue({ data: null, error: { message: "DB error" } }),
      };

      (supabase.from as jest.Mock).mockReturnValue(mockChain);

      const posts = await getPostsByTag("ai", "en");

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

  describe("getAllPostSlugs", () => {
    it.skip("should return array of slugs", async () => {
      const mockPosts = [
        { slug: "post-1" },
        { slug: "post-2" },
        { slug: "post-3" },
      ];

      const mockChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
      };
      mockChain.eq.mockResolvedValue({ data: mockPosts, error: null });

      (supabase.from as jest.Mock).mockReturnValue(mockChain);

      const slugs = await getAllPostSlugs("en");

      expect(slugs).toEqual(["post-1", "post-2", "post-3"]);
    });

    it.skip("should filter by language", async () => {
      const mockChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
      };
      mockChain.eq.mockResolvedValue({ data: [], error: null });

      (supabase.from as jest.Mock).mockReturnValue(mockChain);

      await getAllPostSlugs("ar");

      expect(mockChain.eq).toHaveBeenCalledWith("lang", "ar");
    });

    it.skip("should only include published posts", async () => {
      const mockChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
      };
      mockChain.eq.mockResolvedValue({ data: [], error: null });

      (supabase.from as jest.Mock).mockReturnValue(mockChain);

      await getAllPostSlugs("en");

      expect(mockChain.eq).toHaveBeenCalledWith("published", true);
    });

    it("should return empty array on error", async () => {
      const mockChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
      };
      mockChain.eq.mockResolvedValue({
        data: null,
        error: { message: "Error" },
      });

      (supabase.from as jest.Mock).mockReturnValue(mockChain);

      const slugs = await getAllPostSlugs("en");

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

  describe("Data Mapping", () => {
    it.skip("should map database fields to camelCase", async () => {
      const mockChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
        order: jest.fn().mockResolvedValue({ data: [mockDBPost], error: null }),
      };

      (supabase.from as jest.Mock).mockReturnValue(mockChain);

      const posts = await getAllPosts("en");

      // Snake_case should be converted to camelCase
      expect(posts[0].coverImage).toBe("/images/cover.jpg");
      expect(posts[0].readingTime).toBe("5");
    });

    it.skip("should handle missing cover_image", async () => {
      const postWithoutCover = { ...mockDBPost, cover_image: null };
      const mockChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
        order: jest
          .fn()
          .mockResolvedValue({ data: [postWithoutCover], error: null }),
      };

      (supabase.from as jest.Mock).mockReturnValue(mockChain);

      const posts = await getAllPosts("en");

      expect(posts[0].coverImage).toBeUndefined();
    });

    it.skip("should handle missing reading_time_minutes", async () => {
      const postWithoutTime = { ...mockDBPost, reading_time_minutes: null };
      const mockChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
        order: jest
          .fn()
          .mockResolvedValue({ data: [postWithoutTime], error: null }),
      };

      (supabase.from as jest.Mock).mockReturnValue(mockChain);

      const posts = await getAllPosts("en");

      expect(posts[0].readingTime).toBeUndefined();
    });
  });

  describe("Error Resilience", () => {
    it("should handle Supabase connection failures", async () => {
      (supabase.from as jest.Mock).mockImplementation(() => {
        throw new Error("Connection failed");
      });

      const posts = await getAllPosts("en");

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

    it("should handle malformed database responses", async () => {
      const mockChain = {
        select: jest.fn().mockReturnThis(),
        eq: jest.fn().mockReturnThis(),
        order: jest
          .fn()
          .mockResolvedValue({ data: [{ invalid: "data" }], error: null }),
      };

      (supabase.from as jest.Mock).mockReturnValue(mockChain);

      // Should not throw
      await expect(getAllPosts("en")).resolves.toBeDefined();
    });
  });
});
