import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import {
  OnGatewayConnection,
  WebSocketGateway,
  WebSocketServer,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
import { UsersRepository } from '../users/users.repository';

type SocketWithUser = Socket & { data: { userId?: string } };

@WebSocketGateway({
  cors: {
    origin:
      process.env.NODE_ENV === 'production'
        ? (process.env.CORS_ORIGINS ?? '').split(',').map((value) => value.trim()).filter(Boolean)
        : true,
    credentials: true,
  },
  namespace: 'notifications',
})
export class NotificationsGateway implements OnGatewayConnection {
  @WebSocketServer()
  server!: Server;

  constructor(
    private readonly jwtService: JwtService,
    private readonly configService: ConfigService,
    private readonly usersRepository: UsersRepository,
  ) {}

  async handleConnection(client: SocketWithUser) {
    const token = this.extractToken(client);
    if (!token) {
      client.disconnect(true);
      return;
    }

    try {
      const payload = this.jwtService.verify<{ sub: string; tokenType: string }>(token, {
        secret: this.configService.get<string>('jwt.accessSecret', { infer: true }),
      });
      if (payload.tokenType !== 'access') {
        client.disconnect(true);
        return;
      }

      const user = await this.usersRepository.findById(payload.sub);
      if (!user || user.isDisabled) {
        client.disconnect(true);
        return;
      }

      client.data.userId = payload.sub;
      await client.join(this.userRoom(payload.sub));
    } catch {
      client.disconnect(true);
    }
  }

  emitCreated(
    recipientId: string,
    notification: unknown,
    unreadCount: number,
    unreadCounts?: Record<string, number>,
  ): void {
    this.server.to(this.userRoom(recipientId)).emit('notification_created', notification);
    this.server.to(this.userRoom(recipientId)).emit('notifications_unread_count', { unreadCount });
    this.server.to(this.userRoom(recipientId)).emit('notification:new', notification);
    if (unreadCounts) {
      this.server.to(this.userRoom(recipientId)).emit('notification:unread_counts', unreadCounts);
    }
  }

  emitUnreadCount(recipientId: string, unreadCount: number, unreadCounts?: Record<string, number>): void {
    this.server.to(this.userRoom(recipientId)).emit('notifications_unread_count', { unreadCount });
    if (unreadCounts) {
      this.server.to(this.userRoom(recipientId)).emit('notification:unread_counts', unreadCounts);
    }
  }

  private extractToken(client: Socket): string | null {
    const authToken = client.handshake.auth?.token;
    if (typeof authToken === 'string' && authToken.trim()) {
      return authToken.replace(/^Bearer\s+/i, '').trim();
    }

    const headerAuth = client.handshake.headers.authorization;
    if (typeof headerAuth === 'string' && headerAuth.trim()) {
      return headerAuth.replace(/^Bearer\s+/i, '').trim();
    }

    return null;
  }

  private userRoom(userId: string): string {
    return `user:${userId}`;
  }
}
