import { UserError } from "fastmcp"
import { Left, Option, Right } from "functype"
import { beforeEach, describe, expect, it, vi } from "vitest"

import { getRedditClient } from "../../client/reddit-client"
import { getUserComments, getUserInfo, getUserPosts } from "../user-tools"

vi.mock("../../client/reddit-client")

describe("user-tools", () => {
  const mockRedditClient = {
    getUser: vi.fn(),
    getUserPosts: vi.fn(),
    getUserComments: vi.fn(),
  }

  beforeEach(() => {
    vi.clearAllMocks()
    vi.mocked(getRedditClient).mockReturnValue(Option(mockRedditClient) as any)
  })

  describe("getUserInfo", () => {
    it("should fetch and format user information", async () => {
      const mockUser = {
        name: "testuser",
        id: "user123",
        commentKarma: 5000,
        linkKarma: 1000,
        totalKarma: 6000,
        isMod: true,
        isGold: false,
        isEmployee: false,
        createdUtc: 1600000000,
        profileUrl: "https://reddit.com/user/testuser",
      }

      mockRedditClient.getUser.mockResolvedValue(Right(mockUser))

      const result = await getUserInfo({ username: "testuser" })

      expect(mockRedditClient.getUser).toHaveBeenCalledWith("testuser")
      expect(result.content).toHaveLength(1)
      expect(result.content[0].type).toBe("text")
      expect(result.content[0].text).toContain("User Information: u/testuser")
      expect(result.content[0].text).toContain("Comment Karma: 5,000")
      expect(result.content[0].text).toContain("Moderator")
    })

    it("should throw error if Reddit client is not initialized", async () => {
      vi.mocked(getRedditClient).mockReturnValue(Option.none() as any)

      await expect(getUserInfo({ username: "test" })).rejects.toThrow(new UserError("Reddit client not initialized"))
    })

    it("should handle API errors", async () => {
      mockRedditClient.getUser.mockResolvedValue(Left(new Error("User not found")))

      await expect(getUserInfo({ username: "nonexistent" })).rejects.toThrow(
        new UserError("Failed to fetch user data: User not found"),
      )
    })
  })

  describe("getUserPosts", () => {
    it("should fetch user posts with default parameters", async () => {
      const mockPosts = [
        {
          id: "post1",
          title: "My First Post",
          author: "testuser",
          subreddit: "programming",
          score: 100,
          upvoteRatio: 0.95,
          numComments: 20,
          createdUtc: 1700000000,
          selftext: "This is my post content",
          permalink: "/r/programming/comments/post1",
          over18: false,
          spoiler: false,
        },
      ]

      mockRedditClient.getUserPosts.mockResolvedValue(Right(mockPosts))

      const result = await getUserPosts({ username: "testuser" })

      expect(mockRedditClient.getUserPosts).toHaveBeenCalledWith("testuser", {
        sort: "new",
        timeFilter: "all",
        limit: 10,
      })

      expect(result.content).toHaveLength(1)
      expect(result.content[0].type).toBe("text")
      expect(result.content[0].text).toContain("Posts by u/testuser")
      expect(result.content[0].text).toContain("My First Post")
      expect(result.content[0].text).toContain("Subreddit: r/programming")
    })

    it("should fetch posts with custom parameters", async () => {
      mockRedditClient.getUserPosts.mockResolvedValue(Right([]))

      await getUserPosts({
        username: "testuser",
        sort: "top",
        time_filter: "week",
        limit: 25,
      })

      expect(mockRedditClient.getUserPosts).toHaveBeenCalledWith("testuser", {
        sort: "top",
        timeFilter: "week",
        limit: 25,
      })
    })

    it("should handle NSFW and spoiler posts", async () => {
      const mockPosts = [
        {
          id: "post2",
          title: "NSFW Post",
          author: "testuser",
          subreddit: "test",
          score: 50,
          upvoteRatio: 0.8,
          numComments: 5,
          createdUtc: 1700000000,
          selftext: "",
          permalink: "/r/test/comments/post2",
          over18: true,
          spoiler: true,
        },
      ]

      mockRedditClient.getUserPosts.mockResolvedValue(Right(mockPosts))

      const result = await getUserPosts({ username: "testuser" })

      expect(result.content[0].text).toContain("**NSFW**")
      expect(result.content[0].text).toContain("**Spoiler**")
    })

    it("should throw error if Reddit client is not initialized", async () => {
      vi.mocked(getRedditClient).mockReturnValue(Option.none() as any)

      await expect(getUserPosts({ username: "test" })).rejects.toThrow(new UserError("Reddit client not initialized"))
    })

    it("should handle API errors", async () => {
      mockRedditClient.getUserPosts.mockResolvedValue(Left(new Error("API Error")))

      await expect(getUserPosts({ username: "test" })).rejects.toThrow(
        new UserError("Failed to fetch user posts: API Error"),
      )
    })
  })

  describe("getUserComments", () => {
    it("should fetch user comments with default parameters", async () => {
      const mockComments = [
        {
          id: "comment1",
          author: "testuser",
          body: "This is my insightful comment",
          score: 42,
          subreddit: "programming",
          submissionTitle: "Cool Programming Topic",
          createdUtc: 1700000000,
          edited: false,
          isSubmitter: false,
          permalink: "/r/programming/comments/xyz/cool_programming_topic/comment1",
        },
      ]

      mockRedditClient.getUserComments.mockResolvedValue(Right(mockComments))

      const result = await getUserComments({ username: "testuser" })

      expect(mockRedditClient.getUserComments).toHaveBeenCalledWith("testuser", {
        sort: "new",
        timeFilter: "all",
        limit: 10,
      })

      expect(result.content).toHaveLength(1)
      expect(result.content[0].type).toBe("text")
      expect(result.content[0].text).toContain("Comments by u/testuser")
      expect(result.content[0].text).toContain('In r/programming on "Cool Programming Topic"')
      expect(result.content[0].text).toContain("This is my insightful comment")
    })

    it("should handle edited comments", async () => {
      const mockComments = [
        {
          id: "comment2",
          author: "testuser",
          body: "Edited comment text",
          score: 10,
          subreddit: "test",
          submissionTitle: "Test Post",
          createdUtc: 1700000000,
          edited: true,
          isSubmitter: false,
          permalink: "/r/test/comments/abc/test_post/comment2",
        },
      ]

      mockRedditClient.getUserComments.mockResolvedValue(Right(mockComments))

      const result = await getUserComments({ username: "testuser" })

      expect(result.content[0].text).toContain("*(edited)*")
    })

    it("should truncate long comments", async () => {
      const longComment = "x".repeat(350)
      const mockComments = [
        {
          id: "comment3",
          author: "testuser",
          body: longComment,
          score: 5,
          subreddit: "test",
          submissionTitle: "Test",
          createdUtc: 1700000000,
          edited: false,
          isSubmitter: false,
          permalink: "/r/test/comments/def/test/comment3",
        },
      ]

      mockRedditClient.getUserComments.mockResolvedValue(Right(mockComments))

      const result = await getUserComments({ username: "testuser" })

      expect(result.content[0].text).toContain(`${"x".repeat(300)}...`)
      expect(result.content[0].text).not.toContain("x".repeat(301))
    })

    it("should throw error if Reddit client is not initialized", async () => {
      vi.mocked(getRedditClient).mockReturnValue(Option.none() as any)

      await expect(getUserComments({ username: "test" })).rejects.toThrow(
        new UserError("Reddit client not initialized"),
      )
    })

    it("should handle API errors", async () => {
      mockRedditClient.getUserComments.mockResolvedValue(Left(new Error("API Error")))

      await expect(getUserComments({ username: "test" })).rejects.toThrow(
        new UserError("Failed to fetch user comments: API Error"),
      )
    })
  })
})
