import { Service, PlatformAccessory, Characteristic, CharacteristicValue, Nullable } from 'homebridge';
import { tasmotaPlatform } from './platform';
import nunjucks from 'nunjucks';
import os from 'os';

import createDebug from 'debug';

const debug = createDebug('Tasmota:Service');

/**
 * Platform Accessory
 * An instance of this class is created for each accessory your platform registers
 * Each accessory may expose multiple services of different service types.
 */

interface Subscription {
  event: string, callback: any
}

export class TasmotaService {
  public service: Service;
  protected characteristic: Characteristic;
  protected device_class: string;
  public statusSubscribe: Subscription;
  public availabilitySubscribe: Subscription;
  protected CustomCharacteristic;
  public fakegato: string;
  public nunjucksEnvironment;
  protected uuid: string;

  constructor(
    public readonly platform: tasmotaPlatform,
    public readonly accessory: PlatformAccessory,
    protected readonly uniq_id: string,
  ) {
    /* eslint-disable */
    this.CustomCharacteristic = require('./lib/CustomCharacteristics')(platform.Service, platform.Characteristic);
    this.uuid = this.platform.api.hap.uuid.generate(this.accessory.context.device[this.uniq_id].uniq_id);
    this.device_class = accessory.context.device[this.uniq_id].dev_cla;

    this.nunjucksEnvironment = new nunjucks.Environment();

    // Home Assistant device template filters

    this.nunjucksEnvironment.addFilter('is_defined', function(val, cb) {
      // console.log('is_defined', val, cb);
      if (val || val === 0) {
        cb(null, val);
      } else {
        cb(new Error('missing key'), val);
      }
    }, true);

    this.nunjucksEnvironment.addGlobal('float', float);

    nunjucks.installJinjaCompat();
    nunjucks.configure({
      autoescape: true,
    });

  }

  enableFakegato() {
    // Enable historical logging

    if (this.platform.config.history && this.fakegato && !this.accessory.context.fakegatoService ?.addEntry) {
      var hostname = os.hostname().split(".")[0];
      this.accessory.context.fakegatoService = new this.platform.FakeGatoHistoryService('custom', this.accessory, {
        storage: 'fs',
        minutes: this.platform.config.historyInterval ?? 10,
        log: this.platform.log,
        filename: hostname + "_" + this.uniq_id + '_persist.json',
      });
      this.platform.log.debug('Creating fakegato service for %s %s', this.accessory.context.device[this.uniq_id].stat_t, this.accessory.context.device[this.uniq_id].name, this.accessory.context.device[this.uniq_id].uniq_id);
    } else {
      debug('fakegatoService exists', this.accessory.context.device[this.uniq_id].name);
    }
  }

  enableStatus() {
    this.refresh();
    if (this.characteristic) {
      if (this.accessory.context.device[this.uniq_id].stat_t) {
        this.platform.log.debug('Creating statusUpdate listener for %s %s', this.accessory.context.device[this.uniq_id].stat_t, this.accessory.context.device[this.uniq_id].name);
        this.statusSubscribe = { event: this.accessory.context.device[this.uniq_id].stat_t, callback: this.statusUpdate.bind(this) };
        this.accessory.context.mqttHost.on(this.accessory.context.device[this.uniq_id].stat_t, this.statusUpdate.bind(this));
        this.accessory.context.mqttHost.statusSubscribe(this.accessory.context.device[this.uniq_id].stat_t);
      }
      if (this.accessory.context.device[this.uniq_id].avty_t) {
        this.availabilitySubscribe = { event: this.accessory.context.device[this.uniq_id].avty_t, callback: this.availabilityUpdate.bind(this) };
        this.accessory.context.mqttHost.on(this.accessory.context.device[this.uniq_id].avty_t, this.availabilityUpdate.bind(this));
        this.availabilitySubscribe = this.accessory.context.mqttHost.availabilitySubscribe(this.accessory.context.device[this.uniq_id].avty_t);
      } else {
        this.platform.log.warn('Warning: Availability not supported for: %s', this.accessory.context.device[this.uniq_id].name);
      }
    }
  }

  deviceClassToHKCharacteristic(device_class: string) {
    switch (device_class) {
      case '-dt24-amp':
      case '_energy_current': // Amps
        return (this.CustomCharacteristic.ElectricCurrent);
      case '_energy_voltage': // Voltage
      case '-dt24-volt':  // dt24
        return (this.CustomCharacteristic.Voltage);
      case '_energy_power': // Watts
      case '-dt24-watt': // dt24
        return (this.CustomCharacteristic.CurrentConsumption);
      case '_energy_total': // Total Kilowatts
      case '-dt24-watt-hour':
        return (this.CustomCharacteristic.TotalConsumption);
        break;
    }
  }

  refresh() {
    // Get current status for accessory/service on startup
    // Wild cards in topic break this
    if (this.accessory.context.device[this.uniq_id].stat_t && !this.accessory.context.device[this.uniq_id].stat_t.match('/\+|#/g')) {
      const teleperiod = this.accessory.context.device[this.uniq_id].stat_t.substr(0, this.accessory.context.device[this.uniq_id].stat_t.lastIndexOf('/') + 1).replace('tele', 'cmnd') + 'teleperiod';
      this.accessory.context.mqttHost.sendMessage(teleperiod, this.platform.teleperiod.toString());
    }
  }

  statusUpdate(topic, message) {
    debug('statusUpdate', this.service.displayName, topic, message.toString());

    this.accessory.context.timeout = this.platform.autoCleanup(this.accessory);

    try {
      let value = this.parseValue(this.accessory.context.device[this.uniq_id].val_tpl, message.toString());

      // Sensor value tweaks or adjustments needed for homekit

      switch (this.device_class) {
        case 'temperature':
          if (this.accessory.context.device[this.uniq_id].unit_of_meas.toUpperCase() === 'F') {
            value = Math.round((value - 32) * 5 / 9 * 10) / 10;
          }
          break;
        case 'illuminance':
          // normalize LX in the range homebridge expects
          value = (value < 0.0001 ? 0.0001 : (value > 100000 ? 100000 : value));
          break;
        case 'co2':
          if (value > 1200) {
            this.service.setCharacteristic(this.platform.Characteristic.CarbonDioxideDetected, this.platform.Characteristic.CarbonDioxideDetected.CO2_LEVELS_ABNORMAL);
          } else {
            this.service.setCharacteristic(this.platform.Characteristic.CarbonDioxideDetected, this.platform.Characteristic.CarbonDioxideDetected.CO2_LEVELS_NORMAL);
          }
          break;
      }

      if (value instanceof Error) {
        // Error has already been handled
      } else {
        if (this.characteristic.value != value && this.delta(this.characteristic.value, value)) {
          this.platform.log.info('Updating \'%s:%s\' to %s', this.service.displayName, this.characteristic.displayName ?? '', value);
        } else {
          this.platform.log.debug('Updating \'%s:%s\' to %s', this.service.displayName, this.characteristic.displayName ?? '', value);
        }
      }

      this.characteristic.updateValue(value);

    } catch (err) {
      this.platform.log.error('ERROR: Message Parse Error', topic, message.toString())
    }
  }


  /**
   * Handle "LWT" Last Will and Testament messages from Tasmota
   * These are sent when the device is no longer available from the MQTT server.
   */

  availabilityUpdate(topic, message) {
    // debug("availabilityUpdate", this, topic, message.toString());
    this.platform.log.info('Marking accessory \'%s\' to %s', this.service.displayName, message);

    if (message.toString() === this.accessory.context.device[this.uniq_id].pl_not_avail) {
      const availability: Nullable<CharacteristicValue> | Error = new Error(this.accessory.displayName + ' ' + message.toString());
      this.characteristic.updateValue(availability);
    } else {
      // debug("availabilityUpdate", this.characteristic);
      this.characteristic.updateValue(this.characteristic.value);
    }
  }

  // Utility functions for status update

  delta(value1, value2) {
    // debug("delta", (parseInt(value1) !== parseInt(value2)));
    return (parseInt(value1) !== parseInt(value2));
  }


  parseValue(valueTemplate: string, value: string) {
    try {
      if (valueTemplate) {
        // debug('nunjucksEnvironment', this, this.nunjucksEnvironment);
        var template = nunjucks.compile(valueTemplate, this.nunjucksEnvironment);
        // debug('nunjucksEnvironment', template, this.nunjucksEnvironment, value);
        const result = template.render({ value_json: JSON.parse(value) });
        // debug('nunjucksEnvironment-result', valueTemplate, value, result);
        if (result) {
          return result;
        } else {
          return null;
        }
      } else {
        return value;
      }
    }
    catch (err) {
//      this.platform.log.error('ERROR: Template Parsing error', err.message);
//      debug('ERROR: Template Parsing error', valueTemplate, value);
//      return (err);
    }
  }
}

function float(val) {
  return (parseFloat(val));
}

export function isTrue(value: string | boolean | number): boolean {
  if (typeof (value) === 'string') {
    value = value.trim().toLowerCase();
  }
  switch (value) {
    case true:
    case "true":
    case 1:
    case "1":
    case "on":
    case "yes":
      return true;
    default:
      return false;
  }
}