import { Body, Controller, ForbiddenException, Get, Inject, Param, Post, UseGuards } from "@nestjs/common";
import { eq } from "drizzle-orm";
import type Redis from "ioredis";
import { getDb, schema } from "@slackwsh/data";
import {
  AcceptInviteByIdRequest,
  AcceptInviteRequest,
  ChangeMemberRoleRequest,
  CreateInviteRequest,
  DeclineInviteByIdRequest,
  EntityId,
  TransferOwnershipRequest,
  UpdateWorkspaceSettingsRequest,
  CreateWorkspaceRequest,
  CreateScheduledStatusRequest,
  SetAvailabilityRequest,
  SetStatusContextRequest,
  SetStatusRequest,
  UpdateAutomaticStatusPreferencesRequest,
} from "@slackwsh/contracts";
import { checkWorkspaceMembership } from "@slackwsh/messaging";
import { AuthGuard } from "../auth/auth.guard";
import { CurrentUser } from "../auth/current-user.decorator";
import type { RequestUser } from "../auth/auth.guard";
import { WorkspacesService } from "./workspaces.service";
import { StatusService } from "./status.service";
import { REDIS_PUBSUB } from "../common/redis-pubsub.provider";

@UseGuards(AuthGuard)
@Controller()
export class WorkspacesController {
  constructor(
    private readonly workspaces: WorkspacesService,
    private readonly statuses: StatusService,
    @Inject(REDIS_PUBSUB) private readonly redis: Redis,
  ) {}

  @Post("workspaces")
  async create(@CurrentUser() user: RequestUser, @Body() body: unknown) {
    const input = CreateWorkspaceRequest.parse(body);
    return this.workspaces.createWorkspace(user.userId, input);
  }

  @Get("workspaces/mine")
  async mine(@CurrentUser() user: RequestUser) {
    return { workspaces: await this.workspaces.listMyWorkspaces(user.userId) };
  }

  @Get("workspaces/:workspaceId/admin/overview")
  async adminOverview(@CurrentUser() user: RequestUser, @Param("workspaceId") workspaceId: string) {
    return this.workspaces.adminOverview(EntityId.parse(workspaceId), user.userId);
  }

  @Get("workspaces/:workspaceId/members")
  async members(@CurrentUser() user: RequestUser, @Param("workspaceId") workspaceId: string) {
    return { members: await this.workspaces.listMembers(EntityId.parse(workspaceId), user.userId) };
  }

  /** Same Redis hash the gateway writes — exposed here so /api/… proxies reach it. */
  @Get("workspaces/:workspaceId/presence")
  async presence(@CurrentUser() user: RequestUser, @Param("workspaceId") workspaceId: string) {
    const wsId = EntityId.parse(workspaceId);
    if (!(await checkWorkspaceMembership(wsId, user.userId))) {
      throw new ForbiddenException("not a member of this workspace");
    }
    const raw = await this.redis.hgetall(`presence:${wsId}`);
    const presence: Record<string, { status: string; lastSeen: string }> = {};
    for (const [id, json] of Object.entries(raw)) {
      try {
        presence[id] = JSON.parse(json);
      } catch {
        // skip malformed
      }
    }
    return { presence };
  }

  @Get("workspaces/:workspaceId/me/status")
  async myStatus(@CurrentUser() user: RequestUser, @Param("workspaceId") workspaceId: string) {
    return this.statuses.getMine(EntityId.parse(workspaceId), user.userId);
  }

  @Post("workspaces/:workspaceId/me/status")
  async setStatus(@CurrentUser() user: RequestUser, @Param("workspaceId") workspaceId: string, @Body() body: unknown) {
    return this.statuses.setStatus(EntityId.parse(workspaceId), user.userId, SetStatusRequest.parse(body));
  }

  @Post("workspaces/:workspaceId/me/availability")
  async setAvailability(@CurrentUser() user: RequestUser, @Param("workspaceId") workspaceId: string, @Body() body: unknown) {
    const { mode } = SetAvailabilityRequest.parse(body);
    return this.statuses.setAvailability(EntityId.parse(workspaceId), user.userId, mode);
  }

  @Post("workspaces/:workspaceId/me/status/context")
  async setStatusContext(@CurrentUser() user: RequestUser, @Param("workspaceId") workspaceId: string, @Body() body: unknown) {
    return this.statuses.setContext(EntityId.parse(workspaceId), user.userId, SetStatusContextRequest.parse(body));
  }

  @Post("workspaces/:workspaceId/me/status/automatic")
  async updateAutomaticStatus(@CurrentUser() user: RequestUser, @Param("workspaceId") workspaceId: string, @Body() body: unknown) {
    return this.statuses.updateAutomatic(
      EntityId.parse(workspaceId),
      user.userId,
      UpdateAutomaticStatusPreferencesRequest.parse(body),
    );
  }

  @Post("workspaces/:workspaceId/me/status/scheduled")
  async createScheduledStatus(@CurrentUser() user: RequestUser, @Param("workspaceId") workspaceId: string, @Body() body: unknown) {
    return this.statuses.createSchedule(EntityId.parse(workspaceId), user.userId, CreateScheduledStatusRequest.parse(body));
  }

  @Post("workspaces/:workspaceId/me/status/scheduled/:scheduleId")
  async updateScheduledStatus(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("scheduleId") scheduleId: string,
    @Body() body: unknown,
  ) {
    return this.statuses.updateSchedule(
      EntityId.parse(workspaceId),
      user.userId,
      EntityId.parse(scheduleId),
      CreateScheduledStatusRequest.parse(body),
    );
  }

  @Post("workspaces/:workspaceId/me/status/scheduled/:scheduleId/delete")
  async deleteScheduledStatus(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("scheduleId") scheduleId: string,
  ) {
    return this.statuses.deleteSchedule(EntityId.parse(workspaceId), user.userId, EntityId.parse(scheduleId));
  }

  @Post("workspaces/:workspaceId/invites")
  async createInvite(@CurrentUser() user: RequestUser, @Param("workspaceId") workspaceId: string, @Body() body: unknown) {
    const input = CreateInviteRequest.parse(body);
    return this.workspaces.createInvite(EntityId.parse(workspaceId), user.userId, input);
  }

  @Get("invites/pending")
  async pendingInvites(@CurrentUser() user: RequestUser) {
    return { invites: await this.workspaces.listPendingInvitesForEmail(user.userId, await this.currentUserEmail(user.userId)) };
  }

  @Post("invites/accept")
  async acceptInvite(@CurrentUser() user: RequestUser, @Body() body: unknown) {
    const { token } = AcceptInviteRequest.parse(body);
    return this.workspaces.acceptInvite(token, user.userId, await this.currentUserEmail(user.userId));
  }

  @Post("invites/accept-by-id")
  async acceptInviteById(@CurrentUser() user: RequestUser, @Body() body: unknown) {
    const { inviteId } = AcceptInviteByIdRequest.parse(body);
    return this.workspaces.acceptInviteById(inviteId, user.userId, await this.currentUserEmail(user.userId));
  }

  @Post("invites/decline")
  async declineInvite(@CurrentUser() user: RequestUser, @Body() body: unknown) {
    const { inviteId } = DeclineInviteByIdRequest.parse(body);
    return this.workspaces.declineInviteById(inviteId, user.userId, await this.currentUserEmail(user.userId));
  }

  @Get("notifications")
  async notifications(@CurrentUser() user: RequestUser) {
    return { notifications: await this.workspaces.listNotifications(user.userId) };
  }

  @Post("notifications/:notificationId/read")
  async markNotificationRead(@CurrentUser() user: RequestUser, @Param("notificationId") notificationId: string) {
    return this.workspaces.markNotificationRead(user.userId, EntityId.parse(notificationId));
  }

  @Post("workspaces/:workspaceId/members/:targetUserId/role")
  async changeRole(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("targetUserId") targetUserId: string,
    @Body() body: unknown,
  ) {
    const { role } = ChangeMemberRoleRequest.parse(body);
    await this.workspaces.changeMemberRole(EntityId.parse(workspaceId), user.userId, EntityId.parse(targetUserId), role);
    return { updated: true };
  }

  @Post("workspaces/:workspaceId/members/:targetUserId/deactivate")
  async deactivate(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("targetUserId") targetUserId: string,
  ) {
    await this.workspaces.setMemberActive(EntityId.parse(workspaceId), user.userId, EntityId.parse(targetUserId), false);
    return { deactivated: true };
  }

  @Post("workspaces/:workspaceId/members/:targetUserId/reactivate")
  async reactivate(
    @CurrentUser() user: RequestUser,
    @Param("workspaceId") workspaceId: string,
    @Param("targetUserId") targetUserId: string,
  ) {
    await this.workspaces.setMemberActive(EntityId.parse(workspaceId), user.userId, EntityId.parse(targetUserId), true);
    return { reactivated: true };
  }

  @Post("workspaces/:workspaceId/transfer-ownership")
  async transferOwnership(@CurrentUser() user: RequestUser, @Param("workspaceId") workspaceId: string, @Body() body: unknown) {
    const { newOwnerUserId } = TransferOwnershipRequest.parse(body);
    await this.workspaces.transferOwnership(EntityId.parse(workspaceId), user.userId, newOwnerUserId);
    return { transferred: true };
  }

  @Post("workspaces/:workspaceId/settings")
  async updateSettings(@CurrentUser() user: RequestUser, @Param("workspaceId") workspaceId: string, @Body() body: unknown) {
    const input = UpdateWorkspaceSettingsRequest.parse(body);
    await this.workspaces.updateSettings(EntityId.parse(workspaceId), user.userId, input);
    return { updated: true };
  }

  private async currentUserEmail(userId: number): Promise<string> {
    const db = getDb();
    const [row] = await db.select().from(schema.users).where(eq(schema.users.id, userId)).limit(1);
    if (!row) throw new Error("current user not found");
    return row.email;
  }
}
