import {RequestLeser as ProdressListener, type R2AnfrageBlaupause} from '@prodress/pdr2com';
import {type ResponseTupel, type ResponseObject, ProdressRequest, type RequestTupel} from './wrapper.js';
import {Socket} from 'node:net';

export interface ProdressHeader {
  port?: number;
  host?: string;
  database: string;
  handler: string;
  action: string;
}

// @todo move to nyi utils file
function stringifyError(error: Error): string {
  const {message, stack} = error;

  return JSON.stringify(
    {
      message,
      stack,
    },
    null,
    2,
  );
}

export class ProdressConnection {
  public static readonly TIMEOUT = 30_000;

  private port: number;
  private host: string;
  private socket: Socket;
  private request: ProdressRequest;
  private listener: ProdressListener;
  private requestTupelList: RequestTupel[];
  private responseTupelList: ResponseTupel[];

  public constructor(header: ProdressHeader, requestTupelList: RequestTupel[], responseTupelList: ResponseTupel[]) {
    this.port = header.port ?? 4788;
    this.host = header.host ?? 'localhost';
    this.socket = new Socket();
    this.request = new ProdressRequest(header.database, header.handler, header.action);
    this.listener = new ProdressListener();
    this.requestTupelList = requestTupelList;
    this.responseTupelList = responseTupelList;
  }

  public async handleRequest(): Promise<string> {
    let response: string = '';
    const responseAsPromise = new Promise<string>((resolve) => {
      this.socket.once('finished', () => {
        if (!this.socket.destroyed) {
          this.socket.destroy();
        }
        resolve(response);
      });
    });

    this.socket.setTimeout(ProdressConnection.TIMEOUT);
    this.socket.connect(this.port, this.host);

    this.socket.once('connect', () => {
      try {
        this.request.writeEntries(this.requestTupelList);
        this.request.sende(this.socket);
      } catch (error) {
        response = stringifyError(error as Error);
        this.finished();
      }
    });

    // @todo Rework Error Message
    this.socket.on('timeout', () => {
      const timeoutError = new Error('timeout');
      response = stringifyError(timeoutError);
      this.finished();
    });

    this.socket.on('error', (error: Error) => {
      response = stringifyError(error);
      this.finished();
    });

    this.socket.on('data', (data: Buffer) => {
      this.listener.verarbeiteDaten(data);
      this.socket.emit('dataRecieved');
    });

    this.socket.once('dataRecieved', async () => {
      try {
        const incomingResponse = await this.listener.leseRequest();
        const responseObject = this.readResponse(incomingResponse);
        response = JSON.stringify(responseObject, null, 2);
        this.finished();
      } catch (error) {
        response = stringifyError(error as Error);
        this.finished();
      }
    });
    return responseAsPromise;
  }

  private finished(): void {
    this.socket.emit('finished');
  }

  /**
   * reads the Reponse based on the provided responseTupelList and creates a ResponseObject
   * @param responseInstance The Listener Instance of the active TCP Socket
   * @returns ResponseObject
   */
  private readResponse(responseInstance: R2AnfrageBlaupause): ResponseObject {
    const response: ResponseObject = {};

    for (const tupel of this.responseTupelList) {
      const [key, type] = tupel;
      switch (type) {
        case 'string':
          response[key] = responseInstance.nächsterText();
          break;
        case 'integer':
          response[key] = responseInstance.nächsteGanzzahl();
          break;
        case 'float':
          response[key] = responseInstance.nächsteDezimalzahl();
          break;
        case 'date':
          response[key] = responseInstance.nächstesDatum();
          break;
        case 'date_time':
          //@todo missing method in lib
          response[key] = responseInstance.nächstesDatum();
          break;
      }
    }

    return response;
  }
}
