import Strings from 'lang/Strings';
import ILogger from '../../logger/LoggerInterface';
import LoggerFactory from '../../logger/LoggerFactory';
import {Subject, ReadOnlySubject} from '@techniker-me/tools';
import {MQWebSocket} from 'phenix-web-proto'; //TODO(AZ): add types
import PCastProtobuf from './proto/pcast.proto.json' with {type: 'json'};
import AnalyticsProtobuf from './proto/Analytics.proto.json' with {type: 'json'};
import {PhenixWebSocketStatus, PhenixWebSocketStatusMapping} from './PhenixWebSocketStatus';
import {PhenixWebSocketMessage, PhenixWebSocketMessageMapping} from './PhenixWebSocketMessage';

export type AuthenticationResponse = {
  status: string;
  applicationId?: string;
  sessionId?: string;
};

export interface IPhenixWebSocketResponse {
  status: 'ok';
  applicationId: string;
  sessionId: string;
  redirect: string;
  roles: string[];
  [key: string]: unknown;
}

export class PhenixWebSocket extends MQWebSocket {
  private readonly _logger: ILogger;
  private readonly _status: Subject<PhenixWebSocketStatus> = new Subject<PhenixWebSocketStatus>(PhenixWebSocketStatus.Offline);
  private readonly _readOnlySubject: ReadOnlySubject<PhenixWebSocketStatus> = new ReadOnlySubject<PhenixWebSocketStatus>(this._status);
  private readonly _socketId: string = Strings.randomString(10);
  private _sessionId: string | null = null;
  private _pendingRequests: number = 0;

  constructor(url: string) {
    const logger = LoggerFactory.getLogger('PhenixWebSocket');
    super(url, logger, [PCastProtobuf, AnalyticsProtobuf]);

    this._logger = logger;
    this.initialize();
  }

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

  get pendingRequests(): number {
    return this._pendingRequests;
  }

  get socketId(): string {
    return this._socketId;
  }

  get sessionId(): string | null {
    return this._sessionId;
  }

  set sessionId(sessionId: string | null) {
    this._sessionId = sessionId;
  }

  public async sendMessage<T>(kind: PhenixWebSocketMessage, message: T): Promise<IPhenixWebSocketResponse> {
    if (this._status.value !== PhenixWebSocketStatus.Online) {
      throw new Error(
        `Unable to send message, web socket is not [Online] WebSocket status [${PhenixWebSocketStatusMapping.convertPhenixWebSocketStatusToPhenixWebSocketStatusType(this._status.value)}]`
      );
    }

    this._pendingRequests++;

    const messageKind = PhenixWebSocketMessageMapping.convertPhenixWebSocketMessageToPhenixWebSocketMessageType(kind);

    this._logger.debug(`Sending [${messageKind}] message [%j]`, message);

    return new Promise<IPhenixWebSocketResponse>((resolve, reject) => {
      super.sendRequest(messageKind, message, (error: unknown, response: IPhenixWebSocketResponse) => {
        this._pendingRequests--;

        if (error) {
          reject(error);
        } else {
          resolve(response);
        }
      });
    });
  }

  private setStatus(status: PhenixWebSocketStatus): void {
    this._status.value = status;
  }

  private initialize(): void {
    super.onEvent('connected', () => {
      this.setStatus(PhenixWebSocketStatus.Online);
    });

    super.onEvent('disconnected', () => {
      this.setStatus(PhenixWebSocketStatus.Offline);
    });

    super.onEvent('error', (error: unknown) => {
      this._logger.error('Error [%s]', error);
      this.setStatus(PhenixWebSocketStatus.Error);
    });

    super.onEvent('reconnecting', () => {
      this.setStatus(PhenixWebSocketStatus.Reconnecting);
    });

    super.onEvent('reconnected', () => {
      this.setStatus(PhenixWebSocketStatus.Online);
    });

    super.onEvent('timeout', () => {
      this.setStatus(PhenixWebSocketStatus.Error);
    });
  }
}
