import {MQWebSocket} from 'phenix-web-proto';
import {ILogger} from 'services/logger/LoggerInterface';
import LoggerFactory from 'services/logger/LoggerFactory';
import assertUnreachable from 'lang/assertUnreachable';
import {Subject, ReadOnlySubject, IDisposable} from '@techniker-me/tools';
import pcastProtocol from 'services/messaging/pcast.proto.json';
import analyticsProtocol from 'services/messaging/analytics.proto.json';

export enum PhenixWebSocketStatus {
  Offline = 0,
  Connecting = 1,
  Online = 2,
  Reconnecting = 3,
  Error = 4
}

export type PhenixWebSocketStatusType = 'Offline' | 'Connecting' | 'Online' | 'Reconnecting' | 'Error';

export class PhenixWebSocketStatusMapping {
  public static convertPhenixWebSocketStatusToPhenixWebSocketStatusType(status: PhenixWebSocketStatus): PhenixWebSocketStatusType {
    switch (status) {
      case PhenixWebSocketStatus.Error:
        return 'Error';
      case PhenixWebSocketStatus.Offline:
        return 'Offline';
      case PhenixWebSocketStatus.Connecting:
        return 'Connecting';
      case PhenixWebSocketStatus.Online:
        return 'Online';
      case PhenixWebSocketStatus.Reconnecting:
        return 'Reconnecting';
      default:
        assertUnreachable(status);
    }
  }

  public static convertPhenixWebSocketStatusTypeToPhenixWebSocketStatus(statusType: PhenixWebSocketStatusType): PhenixWebSocketStatus {
    switch (statusType) {
      case 'Error':
        return PhenixWebSocketStatus.Error;
      case 'Offline':
        return PhenixWebSocketStatus.Offline;
      case 'Connecting':
        return PhenixWebSocketStatus.Connecting;
      case 'Online':
        return PhenixWebSocketStatus.Online;
      case 'Reconnecting':
        return PhenixWebSocketStatus.Reconnecting;
      default:
        assertUnreachable(statusType);
    }
  }
}

export enum PhenixWebSocketMessage {
  Authenticate = 'pcast.Authenticate',
  Bye = 'pcast.Bye'
}

export class PhenixWebSocket implements IDisposable {
  private readonly _logger: ILogger = LoggerFactory.getLogger('PhenixWebSocket');
  private readonly _status: Subject<PhenixWebSocketStatus> = new Subject(PhenixWebSocketStatus.Offline);
  private readonly _readOnlyStatus: ReadOnlySubject<PhenixWebSocketStatus> = new ReadOnlySubject(this._status);
  private readonly _websocketMQ: MQWebSocket;

  constructor(uri: string) {
    console.log('PhenixWebSocket constructor', uri);
    this._websocketMQ = new MQWebSocket(uri, this._logger, [pcastProtocol, analyticsProtocol]);
    this.initialize();
  }

  get status(): ReadOnlySubject<PhenixWebSocketStatus> {
    return this._readOnlyStatus;
  }

  public isConnected(): boolean {
    return this._status.value === PhenixWebSocketStatus.Online;
  }

  public isConnecting(): boolean {
    return this._status.value === PhenixWebSocketStatus.Connecting;
  }

  public isReconnecting(): boolean {
    return this._status.value === PhenixWebSocketStatus.Reconnecting;
  }

  public sendMessage<T>(message: PhenixWebSocketMessage, data: T): Promise<unknown> {
    if (!Object.values(PhenixWebSocketMessage).includes(message)) {
      throw new Error(`Invalid message: ${message}`);
    }

    // Check if WebSocket is in a valid state for sending messages
    const currentStatus = this._status.value;
    if (currentStatus !== PhenixWebSocketStatus.Online) {
      return Promise.reject(new Error(`WebSocket is not ready. Current status: ${PhenixWebSocketStatus[currentStatus]}`));
    }

    return new Promise((resolve, reject) => {
      return this._websocketMQ.sendRequest(message, data, (error: unknown, response: unknown) => {
        if (error) {
          reject(error);
        } else {
          resolve({error, response});
        }
      });
    });
  }

  public dispose(): void {
    this._websocketMQ.disconnect();
  }

  private initialize(): void {
    this._websocketMQ.onEvent('connecting', () => {
      this._logger.info('WebSocket connecting...');
      this._status.value = PhenixWebSocketStatus.Connecting;
    });

    this._websocketMQ.onEvent('connected', () => {
      this._logger.info('WebSocket connected successfully');
      this._status.value = PhenixWebSocketStatus.Online;
    });

    this._websocketMQ.onEvent('disconnected', () => {
      this._logger.info('WebSocket disconnected');
      this._status.value = PhenixWebSocketStatus.Offline;
    });

    this._websocketMQ.onEvent('reconnecting', () => {
      this._logger.info('WebSocket reconnecting...');
      this._status.value = PhenixWebSocketStatus.Reconnecting;
    });

    this._websocketMQ.onEvent('error', (error: unknown) => {
      this._logger.error('WebSocket error occurred:', error);
      this._status.value = PhenixWebSocketStatus.Error;
    });
  }
}
