import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import { TripTrackingGateway } from './trip-tracking.gateway';
import { LiveTrackingService } from './live-tracking.service';
import { TRACKING_SOCKET_EVENTS } from '../common/constants/events';
import type { Socket } from 'socket.io';

describe('TripTrackingGateway', () => {
  let gateway: TripTrackingGateway;
  let jwt: JwtService;
  let liveTracking: {
    authorizeJoin: jest.Mock;
    handleLocationUpdate: jest.Mock;
  };

  const mockEmit = jest.fn();
  const mockTo = jest.fn(() => ({ emit: mockEmit }));

  beforeEach(() => {
    jwt = {
      verify: jest.fn(),
    } as unknown as JwtService;
    liveTracking = {
      authorizeJoin: jest.fn().mockResolvedValue({ tripId: 'trip-1' }),
      handleLocationUpdate: jest.fn().mockResolvedValue({
        ok: true,
        persisted: true,
        broadcast: {
          requestId: 'trip-1',
          tripId: 'trip-1',
          userId: 'u1',
          latitude: 1,
          longitude: 2,
          timestamp: '2026-06-16T10:00:00.000Z',
        },
      }),
    };
    gateway = new TripTrackingGateway(
      jwt,
      { get: jest.fn() } as unknown as ConfigService,
      liveTracking as unknown as LiveTrackingService,
    );
    gateway.server = { to: mockTo } as never;
    mockEmit.mockClear();
    mockTo.mockClear();
  });

  describe('handleConnection', () => {
    it('disconnects when token missing', async () => {
      const disconnect = jest.fn();
      const client = {
        handshake: { auth: {}, headers: {} },
        disconnect,
      } as unknown as Socket;

      await gateway.handleConnection(client);
      expect(disconnect).toHaveBeenCalledWith(true);
    });

    it('accepts valid jwt', async () => {
      (jwt.verify as jest.Mock).mockReturnValue({
        uid: 'u1',
        role: 'admin',
      });
      const disconnect = jest.fn();
      const client = {
        handshake: { auth: { token: 'good-token' }, headers: {} },
        disconnect,
        data: {},
      } as unknown as Socket;

      await gateway.handleConnection(client);
      expect(disconnect).not.toHaveBeenCalled();
      expect(client.data.user).toBeDefined();
    });
  });

  describe('onJoin / onLeave', () => {
    it('joins trip room when authorized', async () => {
      const join = jest.fn().mockResolvedValue(undefined);
      const client = {
        data: { user: { uid: 'u1' }, joinedTrips: new Set<string>() },
        join,
      } as unknown as Socket;

      const result = await gateway.onJoin(client, { tripId: 'trip-1' });
      expect(result).toEqual({ ok: true, tripId: 'trip-1' });
      expect(join).toHaveBeenCalledWith('trip:trip-1');
    });

    it('leave removes trip room', async () => {
      const leave = jest.fn().mockResolvedValue(undefined);
      const joined = new Set(['trip-1']);
      const client = {
        data: { joinedTrips: joined },
        leave,
      } as unknown as Socket;

      const result = await gateway.onLeave(client, { tripId: 'trip-1' });
      expect(result).toEqual({ ok: true, tripId: 'trip-1' });
      expect(leave).toHaveBeenCalledWith('trip:trip-1');
      expect(joined.has('trip-1')).toBe(false);
    });
  });

  describe('onLocationUpdate', () => {
    it('broadcasts trip.location.updated to room', async () => {
      const client = {
        data: { user: { uid: 'u1' } },
      } as unknown as Socket;

      await gateway.onLocationUpdate(client, {
        tripId: 'trip-1',
        latitude: 1,
        longitude: 2,
        timestamp: '2026-06-16T10:00:00.000Z',
      });

      expect(mockTo).toHaveBeenCalledWith('trip:trip-1');
      expect(mockEmit).toHaveBeenCalledWith(
        TRACKING_SOCKET_EVENTS.LOCATION_UPDATED,
        expect.objectContaining({ tripId: 'trip-1' }),
      );
    });

    it('rejects unauthorized socket', async () => {
      const client = { data: {} } as unknown as Socket;
      const result = await gateway.onLocationUpdate(client, {
        tripId: 'trip-1',
        latitude: 1,
        longitude: 2,
        timestamp: '2026-06-16T10:00:00.000Z',
      });
      expect(result).toEqual({ ok: false, error: 'unauthorized' });
    });
  });
});
