/**
 * Teltonika device API client.
 * Handles authentication, token refresh, and SMS operations.
 * Self-signed certificate support is enabled by default.
 */

const axios = require('axios');
const https = require('https');
const { getConfig } = require('./db');
const log   = require('./logger');

// Accept self-signed certificates on the LAN router
const httpsAgent = new https.Agent({ rejectUnauthorized: false });

class TeltonikaClient {
  constructor() {
    this.token        = null;
    this.tokenExpires = null;
    // Shared promise while a login is in-flight; concurrent callers await it
    this._loginPromise = null;
  }

  _baseURL() {
    const ip = getConfig('device_ip');
    if (!ip) throw new Error('Device IP not configured');
    return `https://${ip}/api`;
  }

  _isTokenExpired() {
    if (!this.token || !this.tokenExpires) return true;
    // Consider expired 30 s before actual expiry so we refresh proactively
    return Date.now() >= this.tokenExpires - 30_000;
  }

  /**
   * Ensure a valid token exists.
   * Only one login attempt runs at a time; concurrent callers await the same promise.
   */
  async _ensureToken() {
    if (!this._isTokenExpired()) return;

    if (!this._loginPromise) {
      this._loginPromise = this.login().finally(() => {
        this._loginPromise = null;
      });
    }

    // All callers await the single in-flight login
    await this._loginPromise;
  }

  async login() {
    const username = getConfig('device_username');
    const password = getConfig('device_password');
    if (!username || !password) throw new Error('Device credentials not configured');

    log.debug('Teltonika', 'Authenticating…');
    const response = await axios.post(
      `${this._baseURL()}/login`,
      { username, password },
      { httpsAgent, timeout: 10_000 }
    );

    const data = response.data?.data || response.data;
    if (!data?.token) {
      throw new Error(`Login failed: ${JSON.stringify(response.data)}`);
    }

    this.token        = data.token;
    const expiresIn   = (data.expires || 250) * 1000;
    this.tokenExpires = Date.now() + expiresIn;

    log.info('Teltonika', `Authenticated, token expires in ${data.expires}s`);
    log.debug('Teltonika', `Token: ${data.token.substring(0, 8)}…`);
  }

  /**
   * Make an authenticated request, retrying once after re-login on 401.
   * @param {string} method
   * @param {string} path
   * @param {object} [data]   - request body (POST/PUT)
   * @param {object} [params] - URL query parameters (GET)
   */
  async _request(method, path, data, params) {
    await this._ensureToken();
    const qs = params ? '?' + new URLSearchParams(params).toString() : '';
    log.info('Teltonika', `${method} ${this._baseURL()}${path}${qs}`);

    try {
      return await axios({
        method,
        url:     `${this._baseURL()}${path}`,
        data,
        params,
        headers: { Authorization: `Bearer ${this.token}` },
        httpsAgent,
        timeout: 15_000,
      });
    } catch (err) {
      if (err.response) {
        const body = err.response.data;
        const teltonikaCode = body?.errors?.[0]?.code ?? body?.code;
        const teltonikaMsg  = body?.errors?.[0]?.error ?? body?.error;

        if (err.response.status === 403 && teltonikaCode === 120) {
          log.error('Teltonika',
            `Permission denied on ${method} ${path}. `
          );
        } else {
          log.warn('Teltonika', `HTTP ${err.response.status} on ${method} ${path} — code=${teltonikaCode} error="${teltonikaMsg}"`);
        }
      }
      // Token was rejected by the device — force a re-login and retry once
      if (err.response?.status === 401) {
        log.warn('Teltonika', '401 received — token expired on device, re-authenticating…');
        this.token        = null;
        this.tokenExpires = null;
        await this._ensureToken();

        log.debug('Teltonika', `Retrying ${method} ${path}`);
        return axios({
          method,
          url:     `${this._baseURL()}${path}`,
          data,
          params,
          headers: { Authorization: `Bearer ${this.token}` },
          httpsAgent,
          timeout: 15_000,
        });
      }
      throw err;
    }
  }

  /**
   * Send an SMS message.
   * @param {string} number  - Recipient phone number (e.g. "+46701234567")
   * @param {string} message - Message text
   * @returns {object} API response data
   */
  async sendSMS(number, message) {
    const modem = getConfig('device_modem') || '1-1';
    log.info('Teltonika', `Sending SMS to ${number} via modem ${modem}`);
    const response = await this._request('POST', '/messages/actions/send', {
      data: { number, message, modem },
    });
    log.debug('Teltonika', 'Send response:', JSON.stringify(response.data));
    return response.data;
  }

  /**
   * Read all SMS messages stored on the device.
   * @returns {Array} Array of message objects
   */
  async readMessages() {
    const modem = getConfig('device_modem') || '1-1';
    log.debug('Teltonika', `Reading messages for modem ${modem}`);
    const response = await this._request('GET', '/messages', undefined, { modem });
    const raw = response.data;

    // The API may return { data: [...] } or { data: { messages: [...] } } or plain array
    let messages = [];
    if (Array.isArray(raw)) {
      messages = raw;
    } else if (Array.isArray(raw?.data)) {
      messages = raw.data;
    } else if (Array.isArray(raw?.data?.messages)) {
      messages = raw.data.messages;
    } else if (raw?.data) {
      messages = Object.values(raw.data).filter(Array.isArray)[0] || [];
    }

    return messages.map(m => ({
      id:     String(m.id ?? m.index ?? ''),
      sender: m.sender || m.number || m.from || '',
      text:   m.text   || m.message || m.body || '',
      date:   m.date   || m.timestamp || m.created_at || '',
      read:   !!m.read,
      type:   m.type   || (m.direction === 'out' ? 'sent' : 'inbox'),
    }));
  }

  /**
   * Test connectivity to the device.
   */
  async testConnection() {
    try {
      this.token        = null;
      this.tokenExpires = null;
      await this.login();
      return { success: true };
    } catch (err) {
      return { success: false, error: err.message };
    }
  }
}

// Singleton instance
const client = new TeltonikaClient();

module.exports = client;
