import { Logger, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import {
  OnGatewayConnection,
  OnGatewayDisconnect,
  SubscribeMessage,
  WebSocketGateway,
  WebSocketServer,
  MessageBody,
  ConnectedSocket,
} from '@nestjs/websockets';
import type { Server, Socket } from 'socket.io';

/**
 * Real-time board for a General Assembly. Clients join a room per assembly
 * and receive `vote.cast` events whenever a ballot is registered.
 *
 * Authentication: client MUST provide a valid JWT either as
 *   - `auth.token` in the Socket.IO handshake, or
 *   - `Authorization: Bearer <jwt>` header.
 */
@WebSocketGateway({
  namespace: '/ws/assemblies',
  cors: { origin: true, credentials: true },
})
export class AssembliesGateway implements OnGatewayConnection, OnGatewayDisconnect {
  private readonly logger = new Logger('AssembliesGateway');

  @WebSocketServer() server!: Server;

  constructor(
    private readonly jwt: JwtService,
    private readonly config: ConfigService,
  ) {}

  handleConnection(client: Socket) {
    try {
      const rawToken =
        (client.handshake.auth && (client.handshake.auth as any).token) ||
        (client.handshake.headers['authorization'] as string | undefined)?.replace(
          /^Bearer\s+/i,
          '',
        );
      if (!rawToken) throw new UnauthorizedException('Missing token');
      const payload = this.jwt.verify(rawToken, {
        secret: this.config.get<string>('JWT_ACCESS_SECRET'),
      });
      (client.data as any).user = { userId: payload.sub, email: payload.email };
      this.logger.log(`connected ${client.id} (user=${payload.email})`);
    } catch (err) {
      this.logger.warn(`rejected ${client.id}: ${(err as Error).message}`);
      client.emit('error', { message: 'unauthorized' });
      client.disconnect(true);
    }
  }
  handleDisconnect(client: Socket) {
    this.logger.log(`disconnected ${client.id}`);
  }

  @SubscribeMessage('subscribe')
  handleSubscribe(
    @ConnectedSocket() client: Socket,
    @MessageBody() data: { assemblyId: string },
  ) {
    if (!(client.data as any)?.user) {
      return { ok: false, error: 'unauthorized' };
    }
    const room = `assembly:${data.assemblyId}`;
    client.join(room);
    return { ok: true, room };
  }

  broadcastVoteCast(assemblyId: string, payload: {
    resolutionId: string;
    choice: string;
    totals: { for: number; against: number; abstain: number };
  }) {
    this.server.to(`assembly:${assemblyId}`).emit('vote.cast', payload);
  }

  broadcastStatusChange(assemblyId: string, status: string) {
    this.server.to(`assembly:${assemblyId}`).emit('status.change', { status });
  }
}
