import { Body, Controller, Delete, Get, Inject, Param, Post, UseGuards } from "@nestjs/common";
import type Redis from "ioredis";
import {
  AddChannelIntegrationRequest,
  AddChannelMembersRequest,
  ArchiveChannelRequest,
  CreateChannelRequest,
  CreateDirectMessageRequest,
  CreateGroupDirectMessageRequest,
  ConvertGroupDirectMessageRequest,
  SetDirectMessageClosedRequest,
  UpdateChannelPrefsRequest,
  UpdateChannelRequest,
} 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";
import { ObjectStorageService } from "../messages/object-storage.service";

@UseGuards(AuthGuard)
@Controller("workspaces/:workspaceId/channels")
export class ChannelsController {
  constructor(
    @Inject(REDIS_PUBSUB) private readonly redis: Redis,
    private readonly storage: ObjectStorageService,
  ) {}

  @Post()
  async create(@CurrentUser() user: RequestUser, @Param("workspaceId") workspaceId: string, @Body() body: unknown) {
    const input = CreateChannelRequest.parse(body);
    return messaging.createChannel(workspaceId, user.userId, input);
  }

  @Get()
  async list(@CurrentUser() user: RequestUser, @Param("workspaceId") workspaceId: string) {
    return { channels: await messaging.listMyChannels(workspaceId, user.userId) };
  }

  @Get(":channelId/members")
  async listMembers(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("channelId") channelId: string,
  ) {
    return { members: await messaging.listChannelMembers(workspaceId, user.userId, channelId) };
  }

  @Post(":channelId/members")
  async addMembers(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("channelId") channelId: string,
    @Body() body: unknown,
  ) {
    const { userIds } = AddChannelMembersRequest.parse(body);
    return messaging.addChannelMembers(workspaceId, user.userId, channelId, userIds);
  }

  @Post(":channelId/members/:memberUserId/remove")
  async removeMember(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("channelId") channelId: string,
    @Param("memberUserId") memberUserId: string,
  ) {
    return messaging.removeChannelMember(workspaceId, user.userId, channelId, memberUserId);
  }

  @Post(":channelId/join")
  async join(@CurrentUser() user: RequestUser, @Param("workspaceId") workspaceId: string, @Param("channelId") channelId: string) {
    return messaging.joinChannel(workspaceId, user.userId, channelId);
  }

  @Post(":channelId/leave")
  async leave(@CurrentUser() user: RequestUser, @Param("workspaceId") workspaceId: string, @Param("channelId") channelId: string) {
    await messaging.leaveChannel(workspaceId, user.userId, channelId);
    return { left: true };
  }

  @Post(":channelId/archive")
  async archive(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("channelId") channelId: string,
    @Body() body: unknown,
  ) {
    const { archived } = ArchiveChannelRequest.parse(body);
    await messaging.archiveChannel(workspaceId, user.userId, channelId, archived);
    return { archived };
  }

  /** Everything the About-this-room panel needs, in one request. */
  @Get(":channelId/about")
  async about(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("channelId") channelId: string,
  ) {
    return messaging.getChannelAbout(workspaceId, user.userId, channelId);
  }

  /** The caller's own notification settings for this room — never anyone else's. */
  @Post(":channelId/prefs")
  async updatePrefs(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("channelId") channelId: string,
    @Body() body: unknown,
  ) {
    const input = UpdateChannelPrefsRequest.parse(body);
    const membership = await messaging.updateChannelPrefs(workspaceId, user.userId, channelId, input);
    return {
      membership: {
        role: membership.role,
        notifPref: membership.notifPref,
        isMuted: membership.isMuted,
        isStarred: membership.isStarred,
      },
    };
  }

  @Post(":channelId/integrations")
  async addIntegration(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("channelId") channelId: string,
    @Body() body: unknown,
  ) {
    const input = AddChannelIntegrationRequest.parse(body);
    const row = await messaging.addChannelIntegration(workspaceId, user.userId, channelId, input);
    return {
      integration: {
        id: row.id,
        provider: row.provider,
        label: row.label,
        externalUrl: row.externalUrl,
        addedBy: row.addedBy,
        addedAt: row.addedAt.toISOString(),
      },
    };
  }

  @Post(":channelId/integrations/:integrationId/remove")
  async removeIntegration(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("channelId") channelId: string,
    @Param("integrationId") integrationId: string,
  ) {
    await messaging.removeChannelIntegration(workspaceId, user.userId, channelId, integrationId);
    return { removed: true };
  }

  @Post(":channelId")
  async update(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("channelId") channelId: string,
    @Body() body: unknown,
  ) {
    const input = UpdateChannelRequest.parse(body);
    const channel = await messaging.updateChannel(this.redis, workspaceId, user.userId, channelId, input);
    return { updated: true, channel };
  }

  @Delete(":channelId/permanent")
  async permanentlyDelete(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("channelId") channelId: string,
  ) {
    const result = await messaging.permanentlyDeleteChannel(workspaceId, user.userId, channelId);
    await Promise.allSettled(result.objectKeys.map((key) => this.storage.delete(key)));
    return { deleted: true };
  }
}

@UseGuards(AuthGuard)
@Controller("workspaces/:workspaceId/dms")
export class DirectMessagesController {
  constructor(@Inject(REDIS_PUBSUB) private readonly redis: Redis) {}

  @Post()
  async createDm(@CurrentUser() user: RequestUser, @Param("workspaceId") workspaceId: string, @Body() body: unknown) {
    const { otherUserId } = CreateDirectMessageRequest.parse(body);
    return messaging.getOrCreateDirectMessage(workspaceId, user.userId, otherUserId);
  }

  @Post("group")
  async createGroupDm(@CurrentUser() user: RequestUser, @Param("workspaceId") workspaceId: string, @Body() body: unknown) {
    const { memberUserIds } = CreateGroupDirectMessageRequest.parse(body);
    return messaging.createGroupDirectMessage(workspaceId, user.userId, memberUserIds);
  }

  @Post(":channelId/closed")
  async setClosed(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("channelId") channelId: string,
    @Body() body: unknown,
  ) {
    const { closed } = SetDirectMessageClosedRequest.parse(body);
    return messaging.setDirectMessageClosed(workspaceId, user.userId, channelId, closed);
  }

  @Post(":channelId/convert")
  async convert(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("channelId") channelId: string,
    @Body() body: unknown,
  ) {
    const { name } = ConvertGroupDirectMessageRequest.parse(body);
    return messaging.convertGroupDirectMessage(this.redis, workspaceId, user.userId, channelId, name);
  }
}
