import { WEB_CONTRACT } from '../../protocol/contract';
import {
  type HelloMessage,
  type IncomingMessage,
  type OutgoingMessage,
  PROTOCOL_VERSION,
  type RemoteCommandMessage,
  type SetValueMessage,
} from '../../protocol/messages';
import { isIncomingMessage } from '../../protocol/validation';

type SocketHandlers = {
  onConnecting: () => void;
  onTransportOpen: () => void;
  onMessage: (message: IncomingMessage) => void;
  onClose: () => void;
  onError: (message: string) => void;
};

let requestSequence = 0;

export class RemoteSessionClient {
  private socket: WebSocket | null = null;
  private intentionalDisconnect = false;

  constructor(
    private readonly url: string,
    private readonly handlers: SocketHandlers,
  ) {}

  connect(pairingToken?: string): void {
    this.disconnect();
    this.intentionalDisconnect = false;
    this.handlers.onConnecting();

    const socket = new WebSocket(this.url);
    this.socket = socket;

    socket.addEventListener('open', () => {
      this.handlers.onTransportOpen();
      this.send(this.createHelloMessage(pairingToken));
    });
    socket.addEventListener('message', (event) => this.handleMessage(event));
    socket.addEventListener('close', () => {
      if (this.socket === socket) {
        this.socket = null;
      }
      if (!this.intentionalDisconnect) {
        this.handlers.onClose();
      }
    });
    socket.addEventListener('error', () => this.handlers.onError('WebSocket connection failed.'));
  }

  disconnect(): void {
    this.intentionalDisconnect = true;
    this.socket?.close();
    this.socket = null;
  }

  isOpen(): boolean {
    return Boolean(this.socket && this.socket.readyState === WebSocket.OPEN);
  }

  setValue(trainerId: string, target: string, value: unknown, cheatId?: string): string | null {
    const requestId = createRequestId(`set_${target}`);
    const message: SetValueMessage = {
      type: 'set_value',
      version: PROTOCOL_VERSION,
      requestId,
      payload: { trainerId, target, value, cheatId },
    };
    return this.send(message) ? requestId : null;
  }

  launchGame(gameId: string, titleId?: string): boolean {
    return this.sendCommand('launch', gameId, titleId);
  }

  stopPlaying(gameId?: string, titleId?: string): boolean {
    return this.sendCommand('stop', gameId, titleId);
  }

  private send(message: OutgoingMessage): boolean {
    const socket = this.socket;
    if (!socket || socket.readyState !== WebSocket.OPEN) {
      return false;
    }
    socket.send(JSON.stringify(message));
    return true;
  }

  private sendCommand(action: 'launch' | 'stop', gameId?: string, titleId?: string): boolean {
    const message: RemoteCommandMessage = {
      type: 'remote_command',
      version: PROTOCOL_VERSION,
      requestId: createRequestId(`command_${action}`),
      payload: { action, gameId, titleId },
    };
    return this.send(message);
  }

  private createHelloMessage(pairingToken?: string): HelloMessage {
    return {
      type: 'hello',
      version: PROTOCOL_VERSION,
      requestId: createRequestId('hello'),
      payload: {
        client: 'mobile-web',
        clientVersion: WEB_CONTRACT.clientVersion,
        pairingToken,
        capabilities: { supportsDeltaValues: true, supportsTrainerSwitch: true },
      },
    };
  }

  private handleMessage(event: MessageEvent): void {
    try {
      const parsed = JSON.parse(String(event.data)) as unknown;
      if (!isIncomingMessage(parsed)) {
        this.handlers.onError('Received an invalid protocol message.');
        return;
      }
      this.handlers.onMessage(parsed);
    } catch (error) {
      this.handlers.onError(error instanceof Error ? error.message : 'Failed to parse websocket message.');
    }
  }
}

function createRequestId(prefix: string): string {
  requestSequence += 1;
  return `${prefix}_${Date.now()}_${requestSequence}`;
}
