import crypto from "crypto"
import dotenv from "dotenv"
import { FastMCP } from "fastmcp"
import { Option } from "functype"
import { z } from "zod"

import { getRedditClient, initializeRedditClient } from "./client/reddit-client"
import type { BotDisclosureConfig, CacheConfig, RedditAuthMode, RedditSafeMode, SafeModeConfig } from "./types"
import { formatPostInfo, formatSubredditInfo, formatUserInfo } from "./utils/formatters"

// Load environment variables
dotenv.config({ quiet: true })

// Version injected at build time by tsdown
declare const __VERSION__: string
const VERSION = (typeof __VERSION__ !== "undefined" ? __VERSION__ : "0.0.0-dev") as `${number}.${number}.${number}`

// User-Agent validation and building
function validateUserAgent(userAgent: string, username?: string): void {
  const recommendedPattern = /^[\w-]+:[\w-]+:[\d.]+ \(by \/u\/\w+\)$/
  if (!recommendedPattern.test(userAgent)) {
    console.error("[Warning] User-Agent does not follow Reddit's recommended format")
    console.error("[Warning] Recommended: 'platform:app_id:version (by /u/username)'")
    console.error("[Warning] Non-standard User-Agents may increase ban risk")
    if (username !== undefined) {
      console.error(`[Warning] Consider using: 'typescript:reddit-mcp-server:${VERSION} (by /u/${username})'`)
    }
  }
}

function buildUserAgent(customAgent?: string, username?: string): string {
  if (customAgent !== undefined) {
    validateUserAgent(customAgent, username)
    return customAgent
  }

  if (username !== undefined) {
    const autoAgent = `typescript:reddit-mcp-server:${VERSION} (by /u/${username})`
    console.error(`[Setup] Auto-generated User-Agent: ${autoAgent}`)
    return autoAgent
  }

  const fallbackAgent = `typescript:reddit-mcp-server:${VERSION} (by /u/anonymous)`
  console.error(
    "[Setup] No REDDIT_USERNAME set — using anonymous User-Agent. Set REDDIT_USERNAME for a personalized agent.",
  )
  return fallbackAgent
}

// Safe mode configuration
function buildSafeModeConfig(safeMode: RedditSafeMode): SafeModeConfig {
  switch (safeMode) {
    case "off":
      return {
        enabled: false,
        mode: "off",
        writeDelayMs: 0,
        duplicateCheck: false,
        maxRecentHashes: 10,
      }
    case "standard":
      return {
        enabled: true,
        mode: "standard",
        writeDelayMs: 2000,
        duplicateCheck: true,
        maxRecentHashes: 10,
      }
    case "strict":
      return {
        enabled: true,
        mode: "strict",
        writeDelayMs: 5000,
        duplicateCheck: true,
        maxRecentHashes: 20,
      }
  }
}

function unwrapClient() {
  return getRedditClient().orThrow(new Error("Reddit client not initialized"))
}

// Initialize Reddit client
async function setupRedditClient() {
  const clientId = process.env.REDDIT_CLIENT_ID
  const clientSecret = process.env.REDDIT_CLIENT_SECRET
  const customUserAgent = process.env.REDDIT_USER_AGENT
  const username = process.env.REDDIT_USERNAME
  const password = process.env.REDDIT_PASSWORD
  const authMode = (process.env.REDDIT_AUTH_MODE ?? "auto") as RedditAuthMode
  const safeMode = (process.env.REDDIT_SAFE_MODE ?? "standard") as RedditSafeMode

  // Validate auth mode
  if (!["auto", "authenticated", "anonymous"].includes(authMode)) {
    console.error(`[Error] Invalid REDDIT_AUTH_MODE: ${authMode}`)
    console.error("[Error] Valid options are: auto, authenticated, anonymous")
    process.exit(1)
  }

  // Validate safe mode
  if (!["off", "standard", "strict"].includes(safeMode)) {
    console.error(`[Error] Invalid REDDIT_SAFE_MODE: ${safeMode}`)
    console.error("[Error] Valid options are: off, standard, strict")
    process.exit(1)
  }

  // In authenticated mode, require credentials
  if (authMode === "authenticated" && (clientId === undefined || clientSecret === undefined)) {
    console.error("[Error] Authenticated mode requires REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET")
    process.exit(1)
  }

  // For auto/anonymous, credentials are optional
  const hasCredentials = Boolean(clientId && clientSecret)

  // Build user-agent (auto-format with username if available)
  const userAgent = buildUserAgent(customUserAgent, username)

  // Build safe mode config
  const safeModeConfig = buildSafeModeConfig(safeMode)

  // Build bot disclosure config
  const botDisclosureMode = process.env.REDDIT_BOT_DISCLOSURE ?? "off"
  const defaultFooter =
    "\n\n---\n^(🤖 I am a bot | Built with) [^reddit-mcp-server](https://github.com/jordanburke/reddit-mcp-server)"
  const botDisclosureConfig: BotDisclosureConfig = {
    enabled: botDisclosureMode === "auto",
    footer: botDisclosureMode === "auto" ? (process.env.REDDIT_BOT_FOOTER ?? defaultFooter) : "",
  }

  // Build cache config (enabled by default to ease Reddit rate limits; opt out with REDDIT_CACHE=off)
  const cacheEnabled = (process.env.REDDIT_CACHE ?? "on") !== "off"
  const cacheMaxMb = Number(process.env.REDDIT_CACHE_MAX_MB ?? "50")
  const cacheConfig: CacheConfig = {
    enabled: cacheEnabled,
    maxBytes: (Number.isFinite(cacheMaxMb) && cacheMaxMb > 0 ? cacheMaxMb : 50) * 1024 * 1024,
  }

  const client = initializeRedditClient({
    clientId: clientId ?? "",
    clientSecret: clientSecret ?? "",
    userAgent,
    username,
    password,
    authMode,
    safeMode: safeModeConfig,
    botDisclosure: botDisclosureConfig,
    cache: cacheConfig,
  })

  console.error("[Setup] Reddit client initialized")
  console.error(`[Setup] Authentication mode: ${authMode}`)

  if (authMode === "anonymous" || !hasCredentials) {
    console.error("[Setup] Using anonymous Reddit API (~10 req/min)")
    console.error("[Setup] No authentication required - ready to use!")
  } else {
    console.error("[Setup] Testing Reddit API connection...")
    const isConnected = await client.checkAuthentication()

    if (!isConnected) {
      console.error("[Error] ✗ Failed to connect to Reddit API")
      console.error("[Error] Please check your REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET")
      process.exit(1)
    }

    console.error("[Setup] ✓ Reddit API connection successful")
    console.error("[Setup] Using OAuth Reddit API (60-100 req/min)")
  }

  if (username !== undefined && password !== undefined) {
    console.error(`[Setup] ✓ User authenticated as: ${username}`)
    console.error("[Setup] Write operations enabled (posting, replying, editing, deleting)")
  } else {
    console.error("[Setup] Read-only mode (no user credentials)")
    console.error("[Setup] For write operations, set REDDIT_USERNAME and REDDIT_PASSWORD")
  }

  // Log safe mode status
  if (safeModeConfig.enabled) {
    console.error(`[Setup] ✓ Safe mode enabled: ${safeModeConfig.mode}`)
    console.error(`[Setup]   - Write delay: ${safeModeConfig.writeDelayMs}ms between operations`)
    console.error(`[Setup]   - Duplicate detection: enabled (tracking last ${safeModeConfig.maxRecentHashes} items)`)
  } else {
    console.error(
      "[Setup] Safe mode: off (explicitly disabled — ensure compliance with Reddit's Responsible Builder Policy)",
    )
  }

  // Log bot disclosure status
  if (botDisclosureConfig.enabled) {
    console.error("[Setup] ✓ Bot disclosure: enabled (automated content will include bot footer)")
  } else {
    console.error("[Setup] Bot disclosure: off")
    console.error("[Setup] For Reddit policy compliance, consider REDDIT_BOT_DISCLOSURE=auto")
  }
}

// OAuth token: generate once at startup, never expose in responses
const oauthToken = process.env.OAUTH_TOKEN ?? crypto.randomBytes(32).toString("hex")
if (process.env.OAUTH_ENABLED === "true" && process.env.OAUTH_TOKEN === undefined) {
  console.error(`[Auth] Generated OAuth token: ${oauthToken}`)
}

// Create FastMCP server
const server = new FastMCP({
  name: "reddit-mcp-server",
  version: VERSION,
  instructions: `A comprehensive Reddit MCP server that provides tools for interacting with Reddit API.

Available capabilities:
- Fetch Reddit posts, comments, and user information
- Get subreddit details and statistics
- Search Reddit content across posts and subreddits
- Create posts and reply to posts/comments (with authentication)
- Edit your own posts and comments (with authentication)
- Delete your own posts and comments (with authentication)
- Analyze engagement metrics and community insights

For write operations (posting, replying, editing, deleting), ensure REDDIT_USERNAME and REDDIT_PASSWORD are configured.

IMPORTANT - Reddit Responsible Builder Policy compliance:
- Data retrieved via these tools must NOT be used for AI model training without Reddit's written approval
- Data must NOT be sold, licensed, or commercially redistributed
- Do NOT attempt to de-anonymize or re-identify Reddit users
- Do NOT post identical or substantially similar content across multiple subreddits
- Do NOT use these tools to manipulate votes, karma, or circumvent Reddit safety mechanisms
- All bot-generated content must clearly disclose its automated nature
- Bots must NOT send private/direct messages without explicit user consent
For details: https://support.reddithelp.com/hc/en-us/articles/42728983564564-Responsible-Builder-Policy`,

  // Optional OAuth configuration for HTTP transport
  ...(process.env.OAUTH_ENABLED === "true" && {
    authenticate: (request: { readonly headers: { readonly authorization?: string } }) => {
      const authHeader = request.headers.authorization
      if (!authHeader?.startsWith("Bearer ")) {
        // eslint-disable-next-line functype/prefer-either
        throw new Response(null, {
          status: 401,
          statusText: "Missing or invalid Authorization header",
        })
      }

      const token = authHeader.slice(7)
      const tokenBuffer = Buffer.from(token)
      const expectedBuffer = Buffer.from(oauthToken)
      const tokenHash = crypto.createHash("sha256").update(tokenBuffer).digest()
      const expectedHash = crypto.createHash("sha256").update(expectedBuffer).digest()
      if (!crypto.timingSafeEqual(tokenHash, expectedHash)) {
        // eslint-disable-next-line functype/prefer-either
        throw new Response(null, {
          status: 403,
          statusText: "Invalid token",
        })
      }

      return Promise.resolve({ authenticated: true })
    },
  }),
})

// Test tool
server.addTool({
  name: "test_reddit_mcp_server",
  description: "Test the Reddit MCP Server connection and configuration",
  parameters: z.object({}),
  execute: () => {
    const client = getRedditClient()
    const hasAuth = client.fold(
      () => "✗",
      () => "✓",
    )
    const hasWriteAccess =
      process.env.REDDIT_USERNAME !== undefined && process.env.REDDIT_PASSWORD !== undefined ? "✓" : "✗"

    return Promise.resolve(`Reddit MCP Server Status:
- Server: ✓ Running
- Reddit Client: ${hasAuth} ${client.fold(
      () => "Not initialized",
      () => "Initialized",
    )}
- Write Access: ${hasWriteAccess} ${hasWriteAccess === "✓" ? "Available" : "Read-only mode"}
- Version: ${VERSION}

Ready to handle Reddit API requests!`)
  },
})

// User tools
server.addTool({
  name: "get_user_info",
  description: "Get detailed information about a Reddit user including karma, account status, and activity analysis",
  parameters: z.object({
    username: z.string().describe("The Reddit username (without u/ prefix)"),
  }),
  execute: async (args) => {
    const client = unwrapClient()

    const result = await client.getUser(args.username)
    return result.fold(
      (err) => {
        // eslint-disable-next-line functype/prefer-either
        throw new Error(`Failed to get user info: ${err.message}`)
      },
      (user) => {
        const formattedUser = formatUserInfo(user)

        return `# User Information: u/${formattedUser.username}

## Profile Overview
- Username: u/${formattedUser.username}
- Karma:
  - Comment Karma: ${formattedUser.karma.commentKarma.toLocaleString()}
  - Post Karma: ${formattedUser.karma.postKarma.toLocaleString()}
  - Total Karma: ${formattedUser.karma.totalKarma.toLocaleString()}
- Account Status: ${formattedUser.accountStatus.join(", ")}
- Account Created: ${formattedUser.accountCreated}
- Profile URL: ${formattedUser.profileUrl}

## Activity Analysis
- ${formattedUser.activityAnalysis.replace(/\n {2}- /g, "\n- ")}

## Recommendations
- ${formattedUser.recommendations.replace(/\n {2}- /g, "\n- ")}`
      },
    )
  },
})

server.addTool({
  name: "get_user_posts",
  description: "Get recent posts by a Reddit user with sorting and filtering options",
  parameters: z.object({
    username: z.string().describe("The Reddit username (without u/ prefix)"),
    sort: z.enum(["new", "hot", "top"]).default("new").describe("Sort order for posts"),
    time_filter: z
      .enum(["hour", "day", "week", "month", "year", "all"])
      .default("all")
      .describe("Time filter for top posts"),
    limit: z.number().min(1).max(100).default(10).describe("Number of posts to retrieve"),
  }),
  execute: async (args) => {
    const client = unwrapClient()

    const result = await client.getUserPosts(args.username, {
      sort: args.sort,
      timeFilter: args.time_filter,
      limit: args.limit,
    })

    return result.fold(
      (err) => {
        // eslint-disable-next-line functype/prefer-either
        throw new Error(`Failed to get user posts: ${err.message}`)
      },
      (posts) => {
        if (posts.length === 0) {
          return `No posts found for u/${args.username} with the specified filters.`
        }

        const postSummaries = posts
          .map((post, index) => {
            const flags = [...(post.over18 ? ["**NSFW**"] : []), ...(post.spoiler === true ? ["**Spoiler**"] : [])]

            return `### ${index + 1}. ${post.title} ${flags.join(" ")}
- Subreddit: r/${post.subreddit}
- Score: ${post.score.toLocaleString()} (${(post.upvoteRatio * 100).toFixed(1)}% upvoted)
- Comments: ${post.numComments.toLocaleString()}
- Posted: ${new Date(post.createdUtc * 1000).toLocaleString()}
- Link: https://reddit.com${post.permalink}`
          })
          .join("\n\n")

        return `# Posts by u/${args.username} (${args.sort} - ${args.time_filter})

${postSummaries}`
      },
    )
  },
})

server.addTool({
  name: "get_user_comments",
  description: "Get recent comments by a Reddit user with sorting and filtering options",
  parameters: z.object({
    username: z.string().describe("The Reddit username (without u/ prefix)"),
    sort: z.enum(["new", "hot", "top"]).default("new").describe("Sort order for comments"),
    time_filter: z
      .enum(["hour", "day", "week", "month", "year", "all"])
      .default("all")
      .describe("Time filter for top comments"),
    limit: z.number().min(1).max(100).default(10).describe("Number of comments to retrieve"),
  }),
  execute: async (args) => {
    const client = unwrapClient()

    const result = await client.getUserComments(args.username, {
      sort: args.sort,
      timeFilter: args.time_filter,
      limit: args.limit,
    })

    return result.fold(
      (err) => {
        // eslint-disable-next-line functype/prefer-either
        throw new Error(`Failed to get user comments: ${err.message}`)
      },
      (comments) => {
        if (comments.length === 0) {
          return `No comments found for u/${args.username} with the specified filters.`
        }

        const commentSummaries = comments
          .map((comment, index) => {
            const truncatedBody = comment.body.length > 300 ? `${comment.body.substring(0, 300)}...` : comment.body

            const flags = [...(comment.edited ? ["*(edited)*"] : []), ...(comment.isSubmitter ? ["**OP**"] : [])]

            return `### ${index + 1}. Comment ${flags.join(" ")}
In r/${comment.subreddit} on "${comment.submissionTitle}"

> ${truncatedBody}

- Score: ${comment.score.toLocaleString()}
- Posted: ${new Date(comment.createdUtc * 1000).toLocaleString()}
- Link: https://reddit.com${comment.permalink}`
          })
          .join("\n\n")

        return `# Comments by u/${args.username} (${args.sort} - ${args.time_filter})

${commentSummaries}`
      },
    )
  },
})

// Post tools
server.addTool({
  name: "get_reddit_post",
  description:
    "Get detailed information about a specific Reddit post including content, stats, and engagement analysis",
  parameters: z.object({
    subreddit: z.string().describe("The subreddit name (without r/ prefix)"),
    post_id: z.string().describe("The Reddit post ID"),
  }),
  execute: async (args) => {
    const client = unwrapClient()

    const result = await client.getPost(args.post_id, args.subreddit)
    return result.fold(
      (err) => {
        // eslint-disable-next-line functype/prefer-either
        throw new Error(`Failed to get post: ${err.message}`)
      },
      (post) => {
        const formattedPost = formatPostInfo(post)

        return `# Post from r/${formattedPost.subreddit}

## Post Details
- Title: ${formattedPost.title}
- Type: ${formattedPost.type}
- Author: u/${formattedPost.author}

## Content
${formattedPost.content}

## Stats
- Score: ${formattedPost.stats.score.toLocaleString()}
- Upvote Ratio: ${(formattedPost.stats.upvoteRatio * 100).toFixed(1)}%
- Comments: ${formattedPost.stats.comments.toLocaleString()}

## Metadata
- Posted: ${formattedPost.metadata.posted}
- Flags: ${formattedPost.metadata.flags.length > 0 ? formattedPost.metadata.flags.join(", ") : "None"}
- Flair: ${formattedPost.metadata.flair}

## Links
- Full Post: ${formattedPost.links.fullPost}
- Short Link: ${formattedPost.links.shortLink}

## Engagement Analysis
- ${formattedPost.engagementAnalysis.replace(/\n {2}- /g, "\n- ")}

## Best Time to Engage
${formattedPost.bestTimeToEngage}`
      },
    )
  },
})

server.addTool({
  name: "get_top_posts",
  description: "Get top posts from a subreddit or from the Reddit home feed",
  parameters: z.object({
    subreddit: z.string().optional().describe("The subreddit name (without r/ prefix). Leave empty for home feed"),
    time_filter: z
      .enum(["hour", "day", "week", "month", "year", "all"])
      .default("week")
      .describe("Time period for top posts"),
    limit: z.number().min(1).max(100).default(10).describe("Number of posts to retrieve"),
  }),
  execute: async (args) => {
    const client = unwrapClient()

    const result = await client.getTopPosts(args.subreddit ?? "", args.time_filter, args.limit)
    return result.fold(
      (err) => {
        // eslint-disable-next-line functype/prefer-either
        throw new Error(`Failed to get top posts: ${err.message}`)
      },
      (posts) => {
        if (posts.length === 0) {
          const location = Option(args.subreddit).fold(
            () => "home feed",
            (sr) => `r/${sr}`,
          )
          return `No posts found in ${location} for the specified time period.`
        }

        const formattedPosts = posts.map(formatPostInfo)
        const postSummaries = formattedPosts
          .map(
            (post, index) => `### ${index + 1}. ${post.title}
- Author: u/${post.author}
- Score: ${post.stats.score.toLocaleString()} (${(post.stats.upvoteRatio * 100).toFixed(1)}% upvoted)
- Comments: ${post.stats.comments.toLocaleString()}
- Posted: ${post.metadata.posted}
- Link: ${post.links.shortLink}`,
          )
          .join("\n\n")

        const location = Option(args.subreddit).fold(
          () => "Home Feed",
          (sr) => `r/${sr}`,
        )
        return `# Top Posts from ${location} (${args.time_filter})

${postSummaries}`
      },
    )
  },
})

server.addTool({
  name: "browse_subreddit",
  description:
    "Browse posts from a subreddit or the Reddit home feed with a sort order (hot, new, top, rising, controversial). " +
    "The time_filter only applies to top and controversial sorts.",
  parameters: z.object({
    subreddit: z.string().optional().describe("The subreddit name (without r/ prefix). Leave empty for home feed"),
    sort: z.enum(["hot", "new", "top", "rising", "controversial"]).default("hot").describe("Sort order for posts"),
    time_filter: z
      .enum(["hour", "day", "week", "month", "year", "all"])
      .default("week")
      .describe("Time period (only applies to top and controversial sorts)"),
    limit: z.number().min(1).max(100).default(10).describe("Number of posts to retrieve"),
  }),
  execute: async (args) => {
    const client = unwrapClient()

    const result = await client.browseSubreddit(args.subreddit ?? "", args.sort, args.time_filter, args.limit)
    return result.fold(
      (err) => {
        // eslint-disable-next-line functype/prefer-either
        throw new Error(`Failed to browse subreddit: ${err.message}`)
      },
      (posts) => {
        const location = Option(args.subreddit).fold(
          () => "home feed",
          (sr) => `r/${sr}`,
        )
        if (posts.length === 0) {
          return `No posts found in ${location}.`
        }

        const formattedPosts = posts.map(formatPostInfo)
        const postSummaries = formattedPosts
          .map(
            (post, index) => `### ${index + 1}. ${post.title}
- Author: u/${post.author}
- Score: ${post.stats.score.toLocaleString()} (${(post.stats.upvoteRatio * 100).toFixed(1)}% upvoted)
- Comments: ${post.stats.comments.toLocaleString()}
- Posted: ${post.metadata.posted}
- Link: ${post.links.shortLink}`,
          )
          .join("\n\n")

        const timeSuffix = args.sort === "top" || args.sort === "controversial" ? `, ${args.time_filter}` : ""
        const heading = location === "home feed" ? "Home Feed" : location
        return `# ${args.sort} posts from ${heading} (${args.sort}${timeSuffix})

${postSummaries}`
      },
    )
  },
})

// Subreddit tools
server.addTool({
  name: "get_subreddit_info",
  description: "Get detailed information about a subreddit including description, stats, and community analysis",
  parameters: z.object({
    subreddit_name: z.string().describe("The subreddit name (without r/ prefix)"),
  }),
  execute: async (args) => {
    const client = unwrapClient()

    const result = await client.getSubredditInfo(args.subreddit_name)
    return result.fold(
      (err) => {
        // eslint-disable-next-line functype/prefer-either
        throw new Error(`Failed to get subreddit info: ${err.message}`)
      },
      (subreddit) => {
        const formattedSubreddit = formatSubredditInfo(subreddit)

        return `# Subreddit Information: r/${formattedSubreddit.name}

## Overview
- Name: r/${formattedSubreddit.name}
- Title: ${formattedSubreddit.title}
- Subscribers: ${formattedSubreddit.stats.subscribers.toLocaleString()}
- Active Users: ${
          typeof formattedSubreddit.stats.activeUsers === "number"
            ? formattedSubreddit.stats.activeUsers.toLocaleString()
            : formattedSubreddit.stats.activeUsers
        }

## Description
${formattedSubreddit.description.short}

## Detailed Description
${formattedSubreddit.description.full}

## Metadata
- Created: ${formattedSubreddit.metadata.created}
- Flags: ${formattedSubreddit.metadata.flags.join(", ")}

## Links
- Subreddit: ${formattedSubreddit.links.subreddit}
- Wiki: ${formattedSubreddit.links.wiki}

## Community Analysis
- ${formattedSubreddit.communityAnalysis.replace(/\n {2}- /g, "\n- ")}

## Engagement Tips
- ${formattedSubreddit.engagementTips.replace(/\n {2}- /g, "\n- ")}`
      },
    )
  },
})

server.addTool({
  name: "get_trending_subreddits",
  description: "Get a list of currently trending subreddits",
  parameters: z.object({}),
  execute: async () => {
    const client = unwrapClient()

    const result = await client.getTrendingSubreddits()
    return result.fold(
      (err) => {
        // eslint-disable-next-line functype/prefer-either
        throw new Error(`Failed to get trending subreddits: ${err.message}`)
      },
      (trendingSubreddits) => `# Trending Subreddits

${trendingSubreddits.map((subreddit, index) => `${index + 1}. r/${subreddit}`).join("\n")}`,
    )
  },
})

// Search tools
server.addTool({
  name: "search_reddit",
  description: "Search Reddit for posts and content across subreddits",
  parameters: z.object({
    query: z.string().describe("Search query"),
    subreddit: z.string().optional().describe("Limit search to specific subreddit (without r/ prefix)"),
    sort: z.enum(["relevance", "hot", "top", "new", "comments"]).default("relevance").describe("Sort order"),
    time_filter: z.enum(["hour", "day", "week", "month", "year", "all"]).default("all").describe("Time filter"),
    limit: z.number().min(1).max(100).default(10).describe("Number of results"),
    type: z.enum(["link", "sr", "user"]).default("link").describe("Type of content to search"),
  }),
  execute: async (args) => {
    const client = unwrapClient()

    if (args.query.trim() === "") {
      // eslint-disable-next-line functype/prefer-either
      throw new Error("Search query cannot be empty")
    }

    const result = await client.searchReddit(args.query, {
      subreddit: args.subreddit,
      sort: args.sort,
      timeFilter: args.time_filter,
      limit: args.limit,
      type: args.type,
    })

    return result.fold(
      (err) => {
        // eslint-disable-next-line functype/prefer-either
        throw new Error(`Failed to search: ${err.message}`)
      },
      (posts) => {
        if (posts.length === 0) {
          const searchLocation = Option(args.subreddit).fold(
            () => "",
            (sr) => ` in r/${sr}`,
          )
          return `No results found for "${args.query}"${searchLocation}.`
        }

        const searchResults = posts
          .map((post, index) => {
            const flags = [...(post.over18 ? ["**NSFW**"] : []), ...(post.spoiler === true ? ["**Spoiler**"] : [])]

            return `### ${index + 1}. ${post.title} ${flags.join(" ")}
- Subreddit: r/${post.subreddit}
- Author: u/${post.author}
- Score: ${post.score.toLocaleString()} (${(post.upvoteRatio * 100).toFixed(1)}% upvoted)
- Comments: ${post.numComments.toLocaleString()}
- Posted: ${new Date(post.createdUtc * 1000).toLocaleString()}
- Link: https://reddit.com${post.permalink}`
          })
          .join("\n\n")

        const searchLocation = Option(args.subreddit).fold(
          () => "",
          (sr) => ` in r/${sr}`,
        )
        return `# Reddit Search Results for: "${args.query}"${searchLocation}

Sorted by: ${args.sort} | Time: ${args.time_filter} | Type: ${args.type}

${searchResults}`
      },
    )
  },
})

// Write tools (require user authentication)
server.addTool({
  name: "create_post",
  description:
    "Create a new post in a subreddit (requires REDDIT_USERNAME and REDDIT_PASSWORD). " +
    "WARNING: Rapid posting or duplicate content may trigger Reddit's spam detection and result in account bans. " +
    "Consider enabling REDDIT_SAFE_MODE=standard for protection.",
  parameters: z.object({
    subreddit: z.string().describe("The subreddit name (without r/ prefix)"),
    title: z.string().describe("The post title"),
    content: z.string().describe("The post content (text for self posts, URL for link posts)"),
    is_self: z.boolean().default(true).describe("Whether this is a self post (text) or link post"),
  }),
  execute: async (args) => {
    const client = unwrapClient()

    if (process.env.REDDIT_USERNAME === undefined || process.env.REDDIT_PASSWORD === undefined) {
      // eslint-disable-next-line functype/prefer-either
      throw new Error(
        "User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.",
      )
    }

    const result = await client.createPost(args.subreddit, args.title, args.content, args.is_self)
    return result.fold(
      (err) => {
        // eslint-disable-next-line functype/prefer-either
        throw new Error(`Failed to create post: ${err.message}`)
      },
      (post) => {
        const formattedPost = formatPostInfo(post)

        return `# Post Created Successfully

## Post Details
- Title: ${formattedPost.title}
- Subreddit: r/${formattedPost.subreddit}
- Type: ${formattedPost.type}
- Link: ${formattedPost.links.fullPost}

Your post has been successfully submitted to r/${formattedPost.subreddit}.`
      },
    )
  },
})

server.addTool({
  name: "reply_to_post",
  description:
    "Post a reply to an existing Reddit post or comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). " +
    "WARNING: Rapid commenting or duplicate content may trigger Reddit's spam detection. " +
    "Enable REDDIT_SAFE_MODE=standard for rate limiting and duplicate detection.",
  parameters: z.object({
    post_id: z.string().describe("The Reddit post ID (thing_id, e.g., t3_xxxxx for posts, t1_xxxxx for comments)"),
    content: z.string().describe("The reply content"),
  }),
  execute: async (args) => {
    const client = unwrapClient()

    if (process.env.REDDIT_USERNAME === undefined || process.env.REDDIT_PASSWORD === undefined) {
      // eslint-disable-next-line functype/prefer-either
      throw new Error(
        "User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.",
      )
    }

    const result = await client.replyToPost(args.post_id, args.content)
    return result.fold(
      (err) => {
        // eslint-disable-next-line functype/prefer-either
        throw new Error(`Failed to reply: ${err.message}`)
      },
      (comment) => `# Reply Posted Successfully

## Comment Details
- Posted to: ${args.post_id}
- Author: u/${process.env.REDDIT_USERNAME}
- Comment ID: ${comment.id}

Your reply has been successfully posted.`,
    )
  },
})

server.addTool({
  name: "delete_post",
  description:
    "Delete your own Reddit post (requires REDDIT_USERNAME and REDDIT_PASSWORD). WARNING: This action is permanent and cannot be undone!",
  parameters: z.object({
    thing_id: z
      .string()
      .describe(
        "The full Reddit thing ID (e.g., 't3_abc123' for posts) or just the post ID (e.g., 'abc123'). The 't3_' prefix will be added automatically if missing.",
      ),
  }),
  execute: async (args) => {
    const client = unwrapClient()

    if (process.env.REDDIT_USERNAME === undefined || process.env.REDDIT_PASSWORD === undefined) {
      // eslint-disable-next-line functype/prefer-either
      throw new Error(
        "User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.",
      )
    }

    const result = await client.deletePost(args.thing_id)
    return result.fold(
      (err) => {
        // eslint-disable-next-line functype/prefer-either
        throw new Error(`Failed to delete post: ${err.message}`)
      },
      () => `# Post Deleted Successfully

The post ${args.thing_id} has been permanently deleted from Reddit.

**Note**: This action cannot be undone. The post content has been removed and cannot be recovered.`,
    )
  },
})

server.addTool({
  name: "delete_comment",
  description:
    "Delete your own Reddit comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). WARNING: This action is permanent and cannot be undone!",
  parameters: z.object({
    thing_id: z
      .string()
      .describe(
        "The full Reddit thing ID (e.g., 't1_abc123' for comments) or just the comment ID (e.g., 'abc123'). The 't1_' prefix will be added automatically if missing.",
      ),
  }),
  execute: async (args) => {
    const client = unwrapClient()

    if (process.env.REDDIT_USERNAME === undefined || process.env.REDDIT_PASSWORD === undefined) {
      // eslint-disable-next-line functype/prefer-either
      throw new Error(
        "User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.",
      )
    }

    const result = await client.deleteComment(args.thing_id)
    return result.fold(
      (err) => {
        // eslint-disable-next-line functype/prefer-either
        throw new Error(`Failed to delete comment: ${err.message}`)
      },
      () => `# Comment Deleted Successfully

The comment ${args.thing_id} has been permanently deleted from Reddit.

**Note**: This action cannot be undone. The comment content has been removed and cannot be recovered.`,
    )
  },
})

server.addTool({
  name: "edit_post",
  description:
    "Edit your own Reddit post (self-text posts only, requires REDDIT_USERNAME and REDDIT_PASSWORD). " +
    "You can only edit the text content of self posts, not titles or link posts. " +
    "WARNING: Rapid edits may trigger spam detection. Enable REDDIT_SAFE_MODE for protection.",
  parameters: z.object({
    thing_id: z
      .string()
      .describe(
        "The full Reddit thing ID (e.g., 't3_abc123' for posts) or just the post ID (e.g., 'abc123'). The 't3_' prefix will be added automatically if missing.",
      ),
    new_text: z.string().describe("The new text content for the post. Supports Reddit markdown formatting."),
  }),
  execute: async (args) => {
    const client = unwrapClient()

    if (process.env.REDDIT_USERNAME === undefined || process.env.REDDIT_PASSWORD === undefined) {
      // eslint-disable-next-line functype/prefer-either
      throw new Error(
        "User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.",
      )
    }

    const result = await client.editPost(args.thing_id, args.new_text)
    return result.fold(
      (err) => {
        // eslint-disable-next-line functype/prefer-either
        throw new Error(`Failed to edit post: ${err.message}`)
      },
      () => `# Post Edited Successfully

The post ${args.thing_id} has been updated with your new content.

**Note**:
- Only self (text) posts can be edited
- Post titles cannot be edited
- Link posts cannot be edited
- An "edited" marker will appear on your post`,
    )
  },
})

server.addTool({
  name: "edit_comment",
  description:
    "Edit your own Reddit comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). " +
    "Update the text content of a comment you previously posted. " +
    "WARNING: Rapid edits may trigger spam detection. Enable REDDIT_SAFE_MODE for protection.",
  parameters: z.object({
    thing_id: z
      .string()
      .describe(
        "The full Reddit thing ID (e.g., 't1_abc123' for comments) or just the comment ID (e.g., 'abc123'). The 't1_' prefix will be added automatically if missing.",
      ),
    new_text: z.string().describe("The new text content for the comment. Supports Reddit markdown formatting."),
  }),
  execute: async (args) => {
    const client = unwrapClient()

    if (process.env.REDDIT_USERNAME === undefined || process.env.REDDIT_PASSWORD === undefined) {
      // eslint-disable-next-line functype/prefer-either
      throw new Error(
        "User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.",
      )
    }

    const result = await client.editComment(args.thing_id, args.new_text)
    return result.fold(
      (err) => {
        // eslint-disable-next-line functype/prefer-either
        throw new Error(`Failed to edit comment: ${err.message}`)
      },
      () => `# Comment Edited Successfully

The comment ${args.thing_id} has been updated with your new content.

**Note**: An "edited" marker will appear on your comment to show it has been modified.`,
    )
  },
})

// Comment tools
server.addTool({
  name: "get_post_comments",
  description: "Get comments from a specific Reddit post",
  parameters: z.object({
    post_id: z.string().describe("The Reddit post ID"),
    subreddit: z.string().describe("The subreddit name (without r/ prefix)"),
    sort: z.enum(["best", "top", "new", "controversial", "old", "qa"]).default("best").describe("Comment sort order"),
    limit: z.number().min(1).max(500).default(100).describe("Maximum number of comments to retrieve"),
  }),
  execute: async (args) => {
    const client = unwrapClient()

    if (args.post_id === "" || args.subreddit === "") {
      // eslint-disable-next-line functype/prefer-either
      throw new Error("post_id and subreddit are required")
    }

    const result = await client.getPostComments(args.post_id, args.subreddit, {
      sort: args.sort,
      limit: args.limit,
    })

    return result.fold(
      (err) => {
        // eslint-disable-next-line functype/prefer-either
        throw new Error(`Failed to get comments: ${err.message}`)
      },
      ({ post, comments }) => {
        const header = `# Comments for: ${post.title}

**Post by u/${post.author} in r/${post.subreddit}**
- Score: ${post.score.toLocaleString()} | Comments: ${post.numComments.toLocaleString()}
- Posted: ${new Date(post.createdUtc * 1000).toLocaleString()}

---

`

        if (comments.length === 0) {
          return `${header}No comments found for this post.`
        }

        const commentSummaries = comments
          .map((comment) => {
            const indent = "└─".repeat(Math.min(comment.depth ?? 0, 3))
            const authorBadge = comment.isSubmitter ? " **[OP]**" : ""
            const editedBadge = comment.edited ? " *(edited)*" : ""

            return `${indent} **u/${comment.author}**${authorBadge}${editedBadge} (${comment.score.toLocaleString()} points)

${comment.body}

---`
          })
          .join("\n\n")

        return header + commentSummaries
      },
    )
  },
})

// Initialize and start server
async function main() {
  await setupRedditClient()

  const useHttp = process.env.TRANSPORT_TYPE === "httpStream" || process.env.TRANSPORT_TYPE === "http"
  const port = parseInt(process.env.PORT ?? "3000")
  const host = process.env.HOST ?? "127.0.0.1"

  if (useHttp) {
    console.error(`[Setup] Starting HTTP server on ${host}:${port}`)
    await server.start({
      transportType: "httpStream",
      httpStream: {
        port,
        host,
        endpoint: "/mcp",
      },
    })
    console.error(`[Setup] HTTP server ready at http://${host}:${port}/mcp`)
    console.error(`[Setup] SSE endpoint available at http://${host}:${port}/sse`)
  } else {
    console.error("[Setup] Starting in stdio mode")
    await server.start({
      transportType: "stdio",
    })
  }
}

// Handle graceful shutdown
process.on("SIGINT", () => {
  console.error("[Shutdown] Shutting down Reddit MCP Server...")
  process.exit(0)
})

process.on("SIGTERM", () => {
  console.error("[Shutdown] Shutting down Reddit MCP Server...")
  process.exit(0)
})

void main().catch(console.error)
