import PanasonicPlatformLogger from './logger';
import {
  APP_VERSION,
  APP_CLIENT_ID,
  AUTH_0_CLIENT,
  REDIRECT_URI,
} from './settings';
import {
  ComfortCloudDevice,
  ComfortCloudDeviceStatus,
  ComfortCloudDeviceUpdatePayload,
  ComfortCloudGroupResponse,
  PanasonicPlatformConfig,
} from './types';
import axios, { AxiosError } from 'axios';
import { CookieJar } from 'tough-cookie';
import { wrapper } from 'axios-cookiejar-support';
import * as cheerio from 'cheerio';
import jsSHA from 'jssha';
import crypto from 'crypto';

/**
 * This class exposes login, device status fetching, and device status update functions.
 */
export default class ComfortCloudApi {
  token;
  tokenRefresh;
  clientId;
  state;
  location;
  csrf;
  code;
  parameters = {};

  constructor(
    private readonly config: PanasonicPlatformConfig,
    private readonly log: PanasonicPlatformLogger,
  ) {

  }

  /**
   * Logs in the user with Comfort Cloud and
   * saves the retrieved token on the instance.
  */
  async login() {
    this.log.debug('Comfort Cloud: login()');

    // 2 FA TOTP (not necessary for know, it only calculate PIN, but Panasonic API not require it yet).

    // Check if the key is given and if it has 32 characters
    if (this.config.key2fa && this.config.key2fa.length === 32) {
      await this.setup2fa();
    } else {
      this.log.debug('No 2FA key or incorrect key (not necessary for know).');
    }

    // NEW API - START ----------------------------------------------------------------------------------

    // How to check API:
    // https://github.com/homebridge-panasonic-ac-platform/homebridge-panasonic-ac-platform/blob/master/docs/app.md

    // Based on:
    // https://github.com/sockless-coding/panasonic_cc/blob/master/custom_components/panasonic_cc/pcomfortcloud/panasonicauthentication.py
    // https://github.com/lostfields/python-panasonic-comfort-cloud
    // https://github.com/craibo/panasonic_cc/
    // https://github.com/little-quokka/python-panasonic-comfort-cloud/

    // STEP 0 - prepare ---------------------------------------------------------------

    const auth0client = AUTH_0_CLIENT;
    this.log.debug(`auth0client: ${auth0client}`);

    const app_client_id = APP_CLIENT_ID;
    this.log.debug(`app_client_id: ${app_client_id}`);

    function generateRandomString(length) {
      const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
      let result = '';
      for (let i = 0; i < length; i++) {
        result += characters[Math.floor(Math.random() * characters.length)];
      }
      return result;
    }

    function getQuerystringParameterFromHeaderEntryUrl(response, headerEntry, querystringParameter, baseUrl) {
      const headerEntryValue = response.headers[headerEntry];
      const parsedUrl = new URL(headerEntryValue.startsWith('/') ? baseUrl + headerEntryValue : headerEntryValue);
      const params = new URLSearchParams(parsedUrl.search);
      return params.get(querystringParameter) || null;
    }

    // taken from AuthO docs
    function base64URLEncode(str) {
      return str.toString('base64')
        .replace(/\+/g, '-')
        .replace(/\//g, '_')
        .replace(/=/g, '');
    }

    function sha256(buffer) {
      return crypto.createHash('sha256').update(buffer).digest();
    }

    const code_verifier = base64URLEncode(crypto.randomBytes(32));
    this.log.debug(`code_verifier: ${code_verifier}`);

    const code_challenge = base64URLEncode(sha256(code_verifier));
    this.log.debug(`code_challenge: ${code_challenge}`);

    const state = generateRandomString(20);
    this.log.debug(`state: ${state}`);

    // Setup CookieJar
    const jar = new CookieJar();
    const client = wrapper(axios.create({ jar }));

    // STEP 1 - authorize ----------------------------------------------------------------

    await client.request({
      method: 'get',
      url: 'https://authglb.digital.panasonic.com/authorize',
      headers: {
        'user-agent': 'okhttp/4.10.0',
      },
      params: {
        'scope': 'openid offline_access comfortcloud.control a2w.control',
        'audience': 'https://digital.panasonic.com/' + APP_CLIENT_ID + '/api/v1/',
        'protocol': 'oauth2',
        'response_type': 'code',
        'code_challenge': code_challenge,
        'code_challenge_method': 'S256',
        'auth0Client': AUTH_0_CLIENT,
        'client_id': APP_CLIENT_ID,
        'redirect_uri': REDIRECT_URI,
        'state': state,
      },
      maxRedirects: 0,
      validateStatus: status => (status >= 200 && status < 300) || status === 302,
    })
      .then((response) => {
        this.log.debug('Comfort Cloud Login - Step 1 - Success');
        this.log.debug(response.data);
        this.location = response.headers.location;
        this.log.debug(`location: ${this.location}`);
        this.state = getQuerystringParameterFromHeaderEntryUrl(response, 'location', 'state', 'https://authglb.digital.panasonic.com');
        this.log.debug(`state: ${this.state}`);
      })
      .catch((error: AxiosError) => {
        this.log.error('Comfort Cloud Login - Step 1 - Error');
        this.log.debug(JSON.stringify(error, null, 2));
        return Promise.reject(error);
      });

    // STEP 2 - authorize - follow redirect --------------------------------------------------

    await client.request({
      method: 'get',
      url: 'https://authglb.digital.panasonic.com' + this.location,
      maxRedirects: 0,
      validateStatus: status => (status >= 200 && status < 300) || status === 200,
    })
      .then((response) => {
        this.log.debug('Comfort Cloud Login - Step 2 - Success');
        //this.log.debug(response.data);
        this.csrf = (response.headers['set-cookie'] as string[])
          .find(cookie => cookie.includes('_csrf'))
          ?.match(new RegExp('^_csrf=(.+?);'))
          ?.[1];
        this.log.debug(`csrf: ${this.csrf}`);
      })
      .catch((error: AxiosError) => {
        this.log.error('Comfort Cloud Login - Step 2 - Error');
        this.log.debug(JSON.stringify(error, null, 2));
        return Promise.reject(error);
      });

    // STEP 3 - login ----------------------------------------------------------------

    await client.request({
      method: 'post',
      url: 'https://authglb.digital.panasonic.com/usernamepassword/login',
      headers: {
        'Auth0-Client': AUTH_0_CLIENT,
        'user-agent': 'okhttp/4.10.0',
      },
      data: {
        'client_id': APP_CLIENT_ID,
        'redirect_uri': REDIRECT_URI,
        'tenant': 'pdpauthglb-a1',
        'response_type': 'code',
        'scope': 'openid offline_access comfortcloud.control a2w.control',
        'audience': 'https://digital.panasonic.com/' + APP_CLIENT_ID + '/api/v1/',
        '_csrf': this.csrf,
        'state': this.state,
        '_intstate': 'deprecated',
        'username': this.config.email,
        'password': this.config.password,
        'lang': 'en',
        'connection': 'PanasonicID-Authentication',
      },
      maxRedirects: 0,
      validateStatus: status => (status >= 200 && status < 300) || status === 200,
    })
      .then((response) => {
        this.log.debug('Comfort Cloud Login - Step 3 - Success');
        this.log.debug(response.data);

        // get wa, wresult, wctx from body
        const $ = cheerio.load(response.data);
        const elements = $('input[type="hidden"]');
        this.log.debug(`elements: ${elements}`);

        // Extract hidden input parameters and store them in a dictionary
        for (const el of elements) {
          this.parameters[el.attribs.name] = el.attribs.value;
        }
      })
      .catch((error: AxiosError) => {
        this.log.error('Comfort Cloud Login - Step 3 - Error');
        this.log.debug(JSON.stringify(error, null, 2));
        return Promise.reject(error);
      });


    // STEP 4 - login callback ------------------------------------------------------------

    await client.request({
      method: 'post',
      url: 'https://authglb.digital.panasonic.com/login/callback',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'User-Agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 '
          + '(KHTML, like Gecko) Chrome/113.0.0.0 Mobile Safari/537.36',
      },
      data: this.parameters,
      maxRedirects: 0,
      validateStatus: status => (status >= 200 && status < 300) || status === 302,
    })
      .then((response) => {
        this.log.debug('Comfort Cloud Login - Step 4 - Success');
        this.log.debug(response.data);
        this.location = response.headers.location;
        this.log.debug(`location: ${this.location}`);
      })
      .catch((error: AxiosError) => {
        this.log.error('Comfort Cloud Login - Step 4 - Error');
        this.log.debug(JSON.stringify(error, null, 2));
        return Promise.reject(error);
      });

    // STEP 5 - login follow redirect ----------------------------------------------------------

    await client.request({
      method: 'get',
      url: 'https://authglb.digital.panasonic.com' + this.location,
      maxRedirects: 0,
      validateStatus: status => (status >= 200 && status < 300) || status === 302,
    })
      .then((response) => {
        this.log.debug('Comfort Cloud Login - Step 5 - Success');
        this.log.debug(response.data);
        this.code = getQuerystringParameterFromHeaderEntryUrl(response, 'location', 'code', 'https://authglb.digital.panasonic.com');
        this.log.debug(`code: ${this.code}`);
      })
      .catch((error: AxiosError) => {
        this.log.error('Comfort Cloud Login - Step 5 - Error');
        this.log.debug(JSON.stringify(error, null, 2));
        return Promise.reject(error);
      });


    // STEP 6 - get new token -------------------------------------------------------------------

    await client.request({
      method: 'post',
      url: 'https://authglb.digital.panasonic.com/oauth/token',
      headers: {
        'Auth0-Client': AUTH_0_CLIENT,
        'user-agent': 'okhttp/4.10.0',
      },
      data: {
        'scope': 'openid',
        'client_id': APP_CLIENT_ID,
        'grant_type': 'authorization_code',
        'code': this.code,
        'redirect_uri': REDIRECT_URI,
        'code_verifier': code_verifier,
      },
      maxRedirects: 0,
      validateStatus: status => (status >= 200 && status < 300) || status === 302,
    })
      .then((response) => {
        this.log.debug('Comfort Cloud Login - Step 6 - Success');
        this.log.debug(response.data);
        this.token = response.data.access_token;
        this.log.debug(`token: ${this.token}`);
        this.tokenRefresh = response.data.refresh_token;
        this.log.debug(`tokenRefresh: ${this.tokenRefresh}`);
      })
      .catch((error: AxiosError) => {
        this.log.error('Comfort Cloud Login - Step 6 - Error');
        this.log.debug(JSON.stringify(error, null, 2));
        return Promise.reject(error);
      });


    // STEP 7 - get client id --------------------------------------------------------------

    await client.request({
      method: 'post',
      url: 'https://accsmart.panasonic.com/auth/v2/login',
      headers: {
        ...this.getBaseRequestHeaders(),
        'X-User-Authorization-V2': 'Bearer ' + this.token,
      },
      data: {
        'language': 0,
      },
      validateStatus: status => (status >= 200 && status < 300) || status === 200,
    })
      .then((response) => {
        this.log.debug('Comfort Cloud Login - Step 7 - Success');
        this.log.debug(response.data);
        this.clientId = response.data.clientId;
        this.log.debug(`clientId: ${this.clientId}`);
      })
      .catch((error: AxiosError) => {
        this.log.error('Comfort Cloud Login - Step 7 - Error');
        this.log.debug(JSON.stringify(error, null, 2));
        return Promise.reject(error);
      });


    // STEP 8 - set timer to refresh token --------------------------------------------

    // Refresh token just a moment before the expiration of 24 hours
    setTimeout(this.refreshToken.bind(this), 86300000);

  }


  // refresh token --------------------------------------------------------------------

  async refreshToken() {

    this.log.debug('Comfort Cloud - refreshToken()');

    await axios.request({
      method: 'post',
      url: 'https://authglb.digital.panasonic.com/oauth/token',
      headers: {
        'Auth0-Client': AUTH_0_CLIENT,
        'Content-Type': 'application/json',
        'User-Agent': 'okhttp/4.10.0',
      },
      data: {
        'scope': 'openid offline_access comfortcloud.control a2w.control',
        'client_id': APP_CLIENT_ID,
        'refresh_token': this.tokenRefresh,
        'grant_type': 'refresh_token',
      },
    })
      .then((response) => {
        this.log.debug('Comfort Cloud - refreshToken() - Success');
        this.log.debug(response.data);
        this.token = response.data.access_token;
        this.log.debug(`token: ${this.token}`);
        this.tokenRefresh = response.data.refresh_token;
        this.log.debug(`tokenRefresh: ${this.tokenRefresh}`);

        // Refresh token just a moment before the expiration of 24 hours
        setTimeout(this.refreshToken.bind(this), 86300000);
      })
      .catch((error: AxiosError) => {
        this.log.error('Comfort Cloud - refreshToken() - Error');
        this.log.debug(JSON.stringify(error, null, 2));
        this.token = null;
        this.tokenRefresh = null;
        this.login.bind(this);
        return Promise.reject(error);
      });
  }

  // NEW API - END ----------------------------------------------------------------------------------

  // 2 FA TOTP ----------------------------------------------------------------------------------

  async setup2fa() {

    // Decoding Base32 to bytes
    function base32ToBytes(base32) {
      const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
      let bits = 0;
      let value = 0;
      const bytes = [];

      for (let i = 0; i < base32.length; i++) {
        const char = base32.charAt(i).toUpperCase();
        value = (value << 5) | alphabet.indexOf(char);
        bits += 5;

        if (bits >= 8) {
          bits -= 8;
          const bytes: number[] = [];
          bytes.push((value >>> bits) & 0xff);
        }
      }
      return bytes;
    }

    // Converting a number to bytes (for a timer)
    function intToBytes(num) {
      const bytes = new Array(8);
      for (let i = 7; i >= 0; i--) {
        bytes[i] = num & 0xff;
        num = num >>> 8;
      }
      return bytes;
    }

    // SHA1
    function sha1(msg) {
      function rotateLeft(n, s) {
        return (n << s) | (n >>> (32 - s));
      }
      let h0 = 0x67452301, h1 = 0xEFCDAB89, h2 = 0x98BADCFE, h3 = 0x10325476, h4 = 0xC3D2E1F0;

      let padded = msg.slice();
      padded.push(0x80);
      while ((padded.length % 64) !== 56) {
        padded.push(0);
      }
      const len = msg.length * 8;
      padded = padded.concat([0, 0, 0, 0, (len >>> 24) & 0xff, (len >>> 16) & 0xff, (len >>> 8) & 0xff, len & 0xff]);

      for (let i = 0; i < padded.length; i += 64) {
        const w = new Array(80);
        for (let t = 0; t < 16; t++) {
          w[t] = (padded[i + t * 4] << 24) | (padded[i + t * 4 + 1] << 16) | (padded[i + t * 4 + 2] << 8) | padded[i + t * 4 + 3];
        }
        for (let t = 16; t < 80; t++) {
          w[t] = rotateLeft(w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16], 1);
        }

        let a = h0, b = h1, c = h2, d = h3, e = h4;
        for (let t = 0; t < 80; t++) {
          const f = t < 20 ? (b & c) | (~b & d) : t < 40 ? b ^ c ^ d : t < 60 ? (b & c) | (b & d) | (c & d) : b ^ c ^ d;
          const k = [0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6][Math.floor(t / 20)];
          const temp = (rotateLeft(a, 5) + f + e + k + w[t]) >>> 0;
          e = d; d = c; c = rotateLeft(b, 30); b = a; a = temp;
        }
        h0 = (h0 + a) >>> 0; h1 = (h1 + b) >>> 0; h2 = (h2 + c) >>> 0; h3 = (h3 + d) >>> 0; h4 = (h4 + e) >>> 0;
      }

      return [(h0 >>> 24) & 0xff, (h0 >>> 16) & 0xff, (h0 >>> 8) & 0xff, h0 & 0xff,
        (h1 >>> 24) & 0xff, (h1 >>> 16) & 0xff, (h1 >>> 8) & 0xff, h1 & 0xff,
        (h2 >>> 24) & 0xff, (h2 >>> 16) & 0xff, (h2 >>> 8) & 0xff, h2 & 0xff,
        (h3 >>> 24) & 0xff, (h3 >>> 16) & 0xff, (h3 >>> 8) & 0xff, h3 & 0xff,
        (h4 >>> 24) & 0xff, (h4 >>> 16) & 0xff, (h4 >>> 8) & 0xff, h4 & 0xff];
    }

    // HMAC-SHA1
    function hmacSha1(key, message) {
      const blockSize = 64;
      let keyBytes = key.slice();
      if (keyBytes.length > blockSize) {
        keyBytes = sha1(keyBytes);
      }
      while (keyBytes.length < blockSize) {
        keyBytes.push(0);
      }

      const oKeyPad = keyBytes.map(b => b ^ 0x5c);
      const iKeyPad = keyBytes.map(b => b ^ 0x36);

      const inner = iKeyPad.concat(message);
      const innerHash = sha1(inner);
      const outer = oKeyPad.concat(innerHash);
      return sha1(outer);
    }

    // Generate TOTP code
    function generateTOTP(base32Secret) {
      const secretBytes = base32ToBytes(base32Secret);
      const timeStep = 30; // Standardowy interwał 30 sekund
      const epoch = Math.floor(Date.now() / 1000);
      const time = Math.floor(epoch / timeStep);
      const timeBytes = intToBytes(time);

      // Calculate HMAC-SHA1
      const hmac = hmacSha1(secretBytes, timeBytes);

      // Dynamiczne obcinanie
      const offset = hmac[hmac.length - 1] & 0xf;
      const binary = ((hmac[offset] & 0x7f) << 24)
        | ((hmac[offset + 1] & 0xff) << 16)
        | ((hmac[offset + 2] & 0xff) << 8)
        | (hmac[offset + 3] & 0xff);

      // Generate a 6-digit code
      const code = (binary % 1000000).toString().padStart(6, '0');
      return code;
    }

    // Show UTC time
    this.log.debug('UTC date: ' + this.getCurrentTimestamp());

    // Generate 6 digit PIN code calculated by key
    const key2fa = this.config.key2fa;
    const key2fa_masked = key2fa.replace(key2fa.substring(4, 28), '(...)');
    const code2fa = generateTOTP(key2fa);
    this.log.info('2FA TOTP, for key: ' + key2fa_masked + ', PIN code: ' + code2fa);
  }

  // Get devices ----------------------------------------------------------------------------------

  /**
   * Fetches all devices that are registered with the user's Comfort Cloud account.
   *
   * @returns A promise of all the user's devices.
   */
  async getDevices(): Promise<ComfortCloudDevice[]> {
    this.log.debug('Comfort Cloud: getDevices()');

    const fetchedDevices: ComfortCloudDevice[] = [];

    if (!this.token) {
      return Promise.reject('No auth token available (login probably failed). '
        + 'Check your credentials and restart Homebridge.');
    }

    return axios.request<ComfortCloudGroupResponse>({
      method: 'get',
      url: 'https://accsmart.panasonic.com/device/group',
      headers: {
        ...this.getBaseRequestHeaders(),
        'X-Client-Id': this.clientId,
        'X-User-Authorization-V2': 'Bearer ' + this.token,
      },
    })
      .then((response) => {
        this.log.debug('Comfort Cloud - getDevices(): Success');
        this.log.debug(JSON.stringify(response.data, null, 2));

        response.data.groupList.forEach(group => {
          group.deviceList.forEach(device => {
            fetchedDevices.push(device);
          });
        });
        if (fetchedDevices.length === 0) {
          this.log.error('No devices found. '
            + 'Check whether you have added at least one device to your Comfort Cloud account.');
        }
        return fetchedDevices;
      })
      .catch((error: AxiosError) => {
        this.log.debug('Comfort Cloud - getDevices(): Error');
        this.handleNetworkRequestError(error);
        return Promise.reject('Comfort Cloud - getDevices(): Error');
      });
  }

  // Get devices status ----------------------------------------------------------------------------------

  /**
   * Retrieves the status of a device.
   *
   * @param deviceGuid Comfort Cloud's globally unique identifier for the device.
   * @returns A promise of the status of the requested device.
   */
  public getDeviceStatus(deviceGuid: string, deviceName: string): Promise<ComfortCloudDeviceStatus> {
    this.log.debug(`${deviceName} (${deviceGuid}): Comfort Cloud: getDeviceStatus()`);

    if (!this.token) {
      return Promise.reject('No auth token available (login probably failed). '
        + 'Check your credentials and restart Homebridge.');
    }

    if (!deviceGuid) {
      return Promise.reject('Cannot get device status for undefined deviceGuid.');
    }

    return axios.request<ComfortCloudDeviceStatus>({
      method: 'get',
      url: `https://accsmart.panasonic.com/deviceStatus/now/${deviceGuid}`,
      headers: {
        ...this.getBaseRequestHeaders(),
        'X-Client-Id': this.clientId,
        'X-User-Authorization-V2': 'Bearer ' + this.token,
      },
    })
      .then((response) => {
        this.log.debug(`${deviceName} (${deviceGuid}): Comfort Cloud - getDeviceStatus() : Success`);
        this.log.debug(`${deviceName} (${deviceGuid}) - device status:\n${JSON.stringify(response.data, null, 2)}`);
        return response.data;
      })
      .catch((error: AxiosError) => {
        this.log.error(`Comfort Cloud - getDeviceStatus() for GUID '${deviceGuid}': Error`);
        this.log.error('Try restarting the AC (turn it off from the power completely'
          + ' and turn it on again) and Internet router and Homebridge.');
        this.log.error('Turn on debug for more info.');
        this.handleNetworkRequestError(error);
        return Promise.reject(`${deviceName} (${deviceGuid}): Comfort Cloud - getDeviceStatus() : Error`);
      });
  }

  // Set device status ----------------------------------------------------------------------------------

  /**
   * Sets the status of a device.
   *
   * @param deviceGuid Comfort Cloud's globally unique identifier for the device.
   * @param parameters Payload containing status update parameters.
   * @returns
   */
  setDeviceStatus(deviceGuid: string, deviceName: string, parameters: ComfortCloudDeviceUpdatePayload) {
    this.log.debug(`${deviceName} (${deviceGuid}): Comfort Cloud: setDeviceStatus()`);
    this.log.debug(`${deviceName} (${deviceGuid}): ${JSON.stringify(parameters)}`);

    if (!this.token) {
      this.log.error('No auth token available (login probably failed). '
        + 'Check your credentials and restart Homebridge.');
      return;
    }

    if (!deviceGuid) {
      this.log.error('Cannot set device status for undefined deviceGuid.');
      return;
    }

    if (this.config.suppressOutgoingUpdates) {
      this.log.debug(`${deviceName} (${deviceGuid}): Suppressing outgoing device update.`);
      return;
    }

    return axios.request({
      method: 'post',
      url: 'https://accsmart.panasonic.com/deviceStatus/control',
      headers: {
        ...this.getBaseRequestHeaders(),
        'X-Client-Id': this.clientId,
        'X-User-Authorization-V2': 'Bearer ' + this.token,
      },
      data: {
        'deviceGuid': deviceGuid,
        'parameters': parameters,
      },
    })
      .then((response) => {
        this.log.debug(`${deviceName} (${deviceGuid}): Comfort Cloud - setDeviceStatus(): Success`);
        this.log.debug(`${deviceName} (${deviceGuid}): ${JSON.stringify(response.data)}`);
      })
      .catch((error: AxiosError) => {
        this.log.error(`${deviceName} (${deviceGuid}): Comfort Cloud - setDeviceStatus(): Error`);
        this.log.error('Turn on debug for more info.');
        this.handleNetworkRequestError(error);
      });
  }

  // ----------------------------------------------------------------------------------

  /**
   * Generic Axios error handler that checks which type of
   * error occurred and prints the respective information.
   *
   * @see https://axios-http.com/docs/handling_errors
   * @param error The error that is passes into the Axios error handler
   */
  handleNetworkRequestError(error: AxiosError) {
    if (error.response) {
      // The request was made and the server responded with a status code
      // that falls out of the range of 2xx.
      this.log.debug(error.response);
    } else if (error.request) {
      // The request was made but no response was received.
      // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
      // http.ClientRequest in node.js
      this.log.debug(error.request);
    } else {
      // Something happened in setting up the request that triggered an error.
      this.log.debug(error.message);
    }
  }

  /**
   * @returns An object containing all required HTTP headers for Comfort Cloud requests.
   */
  getBaseRequestHeaders() {
    return {
      'Accept': 'application/json; charset=UTF-8',
      'Content-Type': 'application/json',
      'User-Agent': 'G-RAC',
      'X-APP-NAME': 'Comfort Cloud',
      'X-APP-TIMESTAMP': this.getCurrentTimestamp(),
      'X-APP-TYPE': '1',
      'X-APP-VERSION': this.config.overwriteVersion || APP_VERSION,
      'X-CFC-API-KEY': this.getCfcApiKey() ?? '0',
    };
  }

  getCfcApiKey(): string | undefined {
    try {
      // Parse the timestamp in 'YYYY-MM-DD HH:MM:SS' format and convert to UTC milliseconds
      const timestamp = this.getCurrentTimestamp();
      this.log.debug('Timestamp used for key generation and header: ' + timestamp);
      const date = new Date(timestamp + ' UTC'); // Added UTC to ensure consistent timezone handling
      const timestampMs = date.getTime().toString();

      const input = 'Comfort Cloud'
                   + '521325fb2dd486bf4831b47644317fca'
                   + timestampMs
                   + 'Bearer '
                   + this.token;

      const shaObj = new jsSHA('SHA-256', 'TEXT');
      shaObj.update(input);
      const hashStr = shaObj.getHash('HEX');
      return hashStr.slice(0, 9) + 'cfc' + hashStr.slice(9);
    } catch (error) {
      this.log.error('Failed to generate API key', error);
      return undefined;
    }
  }

  getCurrentTimestamp(): string {
    const now = new Date();
    return now.getUTCFullYear()
           + '-' + (now.getUTCMonth() + 1).toString().padStart(2, '0')
           + '-' + now.getUTCDate().toString().padStart(2, '0')
           + ' ' + now.getUTCHours().toString().padStart(2, '0')
           + ':' + now.getUTCMinutes().toString().padStart(2, '0')
           + ':' + now.getUTCSeconds().toString().padStart(2, '0');
  }

}




