import { CanActivate, ExecutionContext, Injectable, Logger, Inject } from '@nestjs/common';
import { WsException } from '@nestjs/websockets';
import { Socket } from 'socket.io';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import { IAdminTokenPayload } from '../../auth/interfaces/IToken-payload';

export interface AuthenticatedSocket extends Socket {
  adminId?: string;
  restId?: string;
}

@Injectable()
export class WsAdminAuthGuard implements CanActivate {
  private readonly logger = new Logger(WsAdminAuthGuard.name);

  constructor(
    @Inject(JwtService)
    private readonly jwtService: JwtService,
    @Inject(ConfigService)
    private readonly configService: ConfigService,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const client: Socket = context.switchToWs().getClient<Socket>();

    try {
      const token = this.extractToken(client);

      if (!token) {
        this.logger.warn('No token provided in WebSocket connection', {
          socketId: client.id,
          auth: client.handshake.auth,
          query: client.handshake.query,
        });
        throw new WsException('Authentication required');
      }

      const secret = this.configService.getOrThrow<string>('JWT_SECRET');
      const payload = await this.jwtService.verifyAsync<IAdminTokenPayload>(token, {
        secret,
      });

      if (!payload.adminId || !payload.restId) {
        this.logger.error('Invalid token payload structure', payload);
        throw new WsException('Invalid token payload');
      }

      // Attach admin info to socket
      (client as AuthenticatedSocket).adminId = payload.adminId;
      (client as AuthenticatedSocket).restId = payload.restId;

      this.logger.log(`Admin authenticated via WebSocket: ${payload.adminId}, restId: ${payload.restId}`);

      return true;
    } catch (error) {
      if (error instanceof WsException) {
        throw error;
      }
      this.logger.error('WebSocket authentication error', {
        error: error instanceof Error ? error.message : 'Unknown error',
        socketId: client.id,
      });
      throw new WsException('Authentication failed');
    }
  }

  private extractToken(client: Socket): string | undefined {
    // Try to get token from handshake auth (recommended for socket.io)
    const auth = client.handshake.auth;
    const authToken = (auth?.token as string | undefined) || (auth?.authorization as string | undefined);

    if (authToken) {
      // If it's "Bearer <token>", extract just the token
      if (typeof authToken === 'string' && authToken.startsWith('Bearer ')) {
        return authToken.substring(7);
      }
      return authToken;
    }

    // Try to get from query parameters (fallback)
    const queryToken = client.handshake.query?.token || client.handshake.query?.authorization;

    if (queryToken) {
      if (typeof queryToken === 'string' && queryToken.startsWith('Bearer ')) {
        return queryToken.substring(7);
      }
      return queryToken as string;
    }

    // Try to get from headers (if available)
    const headers = client.handshake.headers;
    const authHeader = headers.authorization || headers['authorization'];

    if (authHeader && typeof authHeader === 'string') {
      const [type, token] = authHeader.split(' ');
      if (type?.toLowerCase() === 'bearer' && token) {
        return token;
      }
    }

    return undefined;
  }

  // private extractToken(client: Socket): string | undefined {
  //   const tryGet = (...values: any[]) => values.find(v => typeof v === 'string' && v.length > 0);

  //   let token = tryGet(
  //     client.handshake.auth?.token,
  //     client.handshake.auth?.authorization,
  //     client.handshake.query?.token,
  //     client.handshake.query?.authorization,
  //     client.handshake.headers.authorization,
  //     client.handshake.headers['authorization'],
  //   );

  //   if (!token) return undefined;

  //   if (token.startsWith('Bearer ')) {
  //     token = token.slice(7);
  //   }

  //   return token;
  // }
}
