import { Body, Controller, Get, Inject, Param, Post, Query } from "@nestjs/common";
import { UseGuards } from "@nestjs/common";
import type Redis from "ioredis";
import {
  ClientSendMessage,
  CreateTaskFromMessageRequest,
  EditMessageRequest,
  ForwardMessageRequest,
  MarkChannelReadRequest,
  MarkThreadReadRequest,
  ScrollbackQuery,
  SetReactionRequest,
  SetThreadFollowingRequest,
} from "@slackwsh/contracts";
import * as messaging from "@slackwsh/messaging";
import { AuthGuard } from "../auth/auth.guard";
import { CurrentUser } from "../auth/current-user.decorator";
import type { RequestUser } from "../auth/auth.guard";
import { REDIS_PUBSUB } from "../common/redis-pubsub.provider";

@UseGuards(AuthGuard)
@Controller("workspaces/:workspaceId/channels/:channelId/messages")
export class MessagesController {
  constructor(@Inject(REDIS_PUBSUB) private readonly redis: Redis) {}

  /**
   * The outbox-friendly send path (§5.3): a plain idempotent HTTP POST,
   * safe to replay from IndexedDB/SQLite on reconnect because of
   * `UNIQUE(channel_id, client_msg_id)` (I3). apps/gateway also exposes a
   * socket `message:send` event calling the same libs/messaging function —
   * this HTTP route doesn't require a live socket, which matters while a
   * client is still mid-catch-up (I7) or the gateway is briefly unreachable.
   */
  @Post()
  async send(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("channelId") channelId: string,
    @Body() body: unknown,
  ) {
    const input = ClientSendMessage.omit({ channelId: true }).parse(body);
    return messaging.sendMessage(this.redis, workspaceId, channelId, user.userId, input);
  }

  @Get()
  async scrollback(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("channelId") channelId: string,
    @Query() query: unknown,
  ) {
    const opts = ScrollbackQuery.parse(query);
    return { messages: await messaging.getScrollback(workspaceId, channelId, user.userId, opts) };
  }

  @Get(":messageId/thread")
  async thread(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("channelId") channelId: string,
    @Param("messageId") messageId: string,
  ) {
    return { messages: await messaging.getThread(workspaceId, channelId, user.userId, messageId) };
  }

  @Get(":messageId")
  async one(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("channelId") channelId: string,
    @Param("messageId") messageId: string,
  ) {
    return { message: await messaging.getMessage(workspaceId, channelId, user.userId, messageId) };
  }

  @Post(":messageId/edit")
  async edit(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("channelId") channelId: string,
    @Param("messageId") messageId: string,
    @Body() body: unknown,
  ) {
    const input = EditMessageRequest.parse(body);
    return messaging.editMessage(this.redis, workspaceId, channelId, user.userId, messageId, input);
  }

  @Post(":messageId/forward")
  async forward(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("channelId") channelId: string,
    @Param("messageId") messageId: string,
    @Body() body: unknown,
  ) {
    const input = ForwardMessageRequest.parse(body);
    return { messages: await messaging.forwardMessage(this.redis, workspaceId, channelId, user.userId, messageId, input) };
  }

  @Post(":messageId/delete")
  async remove(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("channelId") channelId: string,
    @Param("messageId") messageId: string,
  ) {
    await messaging.deleteMessage(this.redis, workspaceId, channelId, user.userId, messageId);
    return { deleted: true };
  }

  @Post(":messageId/reactions")
  async addReaction(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("channelId") channelId: string,
    @Param("messageId") messageId: string,
    @Body() body: unknown,
  ) {
    const { emoji } = SetReactionRequest.parse(body);
    await messaging.setReaction(this.redis, workspaceId, channelId, user.userId, messageId, emoji, "add");
    return { added: true };
  }

  @Post(":messageId/reactions/remove")
  async removeReaction(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("channelId") channelId: string,
    @Param("messageId") messageId: string,
    @Body() body: unknown,
  ) {
    const { emoji } = SetReactionRequest.parse(body);
    await messaging.setReaction(this.redis, workspaceId, channelId, user.userId, messageId, emoji, "remove");
    return { removed: true };
  }

  @Post(":messageId/pin")
  async pin(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("channelId") channelId: string,
    @Param("messageId") messageId: string,
  ) {
    await messaging.setPin(this.redis, workspaceId, channelId, user.userId, messageId, "add");
    return { pinned: true };
  }

  @Post(":messageId/unpin")
  async unpin(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("channelId") channelId: string,
    @Param("messageId") messageId: string,
  ) {
    await messaging.setPin(this.redis, workspaceId, channelId, user.userId, messageId, "remove");
    return { unpinned: true };
  }

  /** Spins up a workspace-wide Task pre-filled from this message (§ Tasks
   * feature) — lives here rather than TasksController because the input
   * identity (workspace/channel/message) is exactly what this controller
   * already carries via @Param, same as pin/unpin/reactions do. */
  @Post(":messageId/create-task")
  async createTask(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("channelId") channelId: string,
    @Param("messageId") messageId: string,
    @Body() body: unknown,
  ) {
    const input = CreateTaskFromMessageRequest.parse(body);
    return messaging.createTaskFromMessage(this.redis, workspaceId, user.userId, channelId, messageId, input);
  }

  @Post(":messageId/thread-read")
  async markThreadRead(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("channelId") channelId: string,
    @Param("messageId") messageId: string,
    @Body() body: unknown,
  ) {
    const { seq } = MarkThreadReadRequest.parse(body);
    const status = await messaging.markThreadRead(this.redis, workspaceId, channelId, user.userId, messageId, seq);
    return { updated: true, status };
  }

  @Get(":messageId/thread-status")
  async threadStatus(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("channelId") channelId: string,
    @Param("messageId") messageId: string,
  ) {
    return { status: await messaging.getThreadSubscriptionStatus(workspaceId, channelId, user.userId, messageId) };
  }

  @Post(":messageId/thread-following")
  async setThreadFollowing(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("channelId") channelId: string,
    @Param("messageId") messageId: string,
    @Body() body: unknown,
  ) {
    const { following } = SetThreadFollowingRequest.parse(body);
    return { status: await messaging.setThreadFollowing(this.redis, workspaceId, channelId, user.userId, messageId, following) };
  }

  @Post(":messageId/unread")
  async markUnread(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("channelId") channelId: string,
    @Param("messageId") messageId: string,
  ) {
    return messaging.markMessageUnread(this.redis, workspaceId, channelId, user.userId, messageId);
  }
}

@UseGuards(AuthGuard)
@Controller("workspaces/:workspaceId/threads")
export class ThreadsController {
  @Get()
  async list(@CurrentUser() user: RequestUser, @Param("workspaceId") workspaceId: string) {
    return { threads: await messaging.listFollowedThreads(workspaceId, user.userId) };
  }
}

@UseGuards(AuthGuard)
@Controller("workspaces/:workspaceId/channels/:channelId/read")
export class ReadStateController {
  constructor(@Inject(REDIS_PUBSUB) private readonly redis: Redis) {}

  @Post()
  async markRead(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("channelId") channelId: string,
    @Body() body: unknown,
  ) {
    const { seq } = MarkChannelReadRequest.parse(body);
    await messaging.markChannelRead(this.redis, workspaceId, channelId, user.userId, seq);
    return { updated: true };
  }
}
