/*
 * Sinric Pro Manager - Google Home / Alexa Voice Control for StepperMote
 * =====================================================================
 * This module provides voice control for motorised blinds via Sinric Pro.
 *
 * Supported voice commands:
 *   - "Hey Google, open/close the [device name]"
 *   - "Hey Google, set [device name] to 50%"
 *   - "Hey Google, open [device name] a little"
 *
 * Device type: SinricProBlinds (supports rangeValue 0-100%)
 *
 * Requirements:
 *   - SinricPro library (https://github.com/sinricpro/esp8266-esp32-sdk)
 *   - WebSockets library (auto-installed as transitive dependency)
 *   - Valid Sinric Pro credentials in secrets.h
 */

#include "sinric_manager.h"
#include "config.h"
#include "secrets.h"
#include "StepperManager.h"

// Include WiFi before SinricPro (required for proper initialization)
#include <WiFi.h>

// SinricPro SDK includes
#include <SinricPro.h>
#include <SinricProBlinds.h>

// External references
extern StepperManager stepperManager;

// Global instance
SinricManager sinricManager;

// =============================================================================
// SINRIC PRO CALLBACKS
// =============================================================================

/**
 * Called when Google Home / Alexa requests a specific blinds position.
 * e.g. "Hey Google, set bedroom blinds to 50%"
 *      "Hey Google, open the bedroom blinds"  → position = 100
 *      "Hey Google, close the bedroom blinds" → position = 0
 *
 * @param deviceId The Sinric Pro device ID (for multi-device setups)
 * @param position Reference to the requested position (0-100%)
 * @return true if command was handled successfully
 */
bool onRangeValue(const String &deviceId, int &position) {
  Serial.printf("[Sinric] Voice command: set position to %d%%\n", position);
  stepperManager.setTargetPercentage(position);
  return true;
}

/**
 * Called when Google Home / Alexa adjusts position relatively.
 * e.g. "Hey Google, open the blinds a little"   → delta = +25
 *      "Hey Google, close the blinds a little"  → delta = -25
 *
 * @param deviceId The Sinric Pro device ID
 * @param positionDelta Reference to the requested adjustment (modified to reflect actual)
 * @return true if command was handled successfully
 */
bool onAdjustRangeValue(const String &deviceId, int &positionDelta) {
  int current = stepperManager.getCurrentPercentage();
  int target = constrain(current + positionDelta, 0, 100);
  positionDelta = target - current;  // Report actual delta applied
  Serial.printf("[Sinric] Voice command: adjust %+d%% → target %d%%\n",
                positionDelta, target);
  stepperManager.setTargetPercentage(target);
  return true;
}

// =============================================================================
// SINRIC MANAGER IMPLEMENTATION
// =============================================================================

SinricManager::SinricManager()
    : _connected(false), _lastReportedPosition(-1), _initialized(false),
      _lastHandleTime(0) {}

void SinricManager::begin() {
  // Validate credentials are present
  if (strlen(SINRIC_APP_KEY) < 10) {
    Serial.println("[Sinric] ERROR: App Key not configured in secrets.h");
    return;
  }
  if (strlen(SINRIC_APP_SECRET) < 10) {
    Serial.println("[Sinric] ERROR: App Secret not configured in secrets.h");
    return;
  }
  if (strlen(SINRIC_DEVICE_ID) < 10) {
    Serial.println("[Sinric] ERROR: Device ID not configured in secrets.h");
    return;
  }

  // Prevent double initialization
  if (_initialized) {
    Serial.println("[Sinric] Already initialized - skipping");
    return;
  }

  Serial.println("[Sinric] ========================================");
  Serial.println("[Sinric] Initializing Sinric Pro (Google Home Blinds)...");
  Serial.printf("[Sinric] App Key: %.8s...%s\n", SINRIC_APP_KEY,
                strlen(SINRIC_APP_KEY) > 8 ? "(truncated)" : "");
  Serial.printf("[Sinric] Device ID: %s\n", SINRIC_DEVICE_ID);

  // Register as a Blinds device and attach callbacks
  SinricProBlinds &myBlinds = SinricPro[SINRIC_DEVICE_ID];
  myBlinds.onRangeValue(onRangeValue);
  myBlinds.onAdjustRangeValue(onAdjustRangeValue);

  // Setup connection event handlers
  SinricPro.onConnected([]() {
    Serial.println("[Sinric] *** CONNECTED to Sinric Pro cloud! ***");
    Serial.println("[Sinric] Voice commands are now active.");
    sinricManager.setConnected(true);

    // Report current blinds position on connection
    int pos = stepperManager.getCurrentPercentage();
    SinricProBlinds &blinds = SinricPro[SINRIC_DEVICE_ID];
    blinds.sendRangeValueEvent(pos);
    Serial.printf("[Sinric] Synced initial position: %d%%\n", pos);
  });

  SinricPro.onDisconnected([]() {
    Serial.println("[Sinric] Disconnected from Sinric Pro cloud");
    sinricManager.setConnected(false);
  });

  // Start the Sinric Pro connection
  // Note: SinricPro.handle() must be called in loop() to maintain connection
  SinricPro.begin(SINRIC_APP_KEY, SINRIC_APP_SECRET);

  _initialized = true;
  Serial.println("[Sinric] Initialization complete");
  Serial.println("[Sinric] ========================================");
}

void SinricManager::update() {
  // Skip if not initialized (credentials missing)
  if (!_initialized) {
    return;
  }

  // Process Sinric Pro WebSocket events ONLY if WiFi is connected.
  // When offline, calling handle() can block the main loop synchronously.
  if (WiFi.status() == WL_CONNECTED) {
    // RESPONSIVENESS FIX (Bug #5): Rate-limit SinricPro.handle() to every 50ms.
    // The WebSocket processing can block the main loop for 100-500ms if the
    // server is slow or the buffer is large. At 20 calls/second, voice commands
    // remain responsive while freeing the loop for MQTT and touch processing.
    unsigned long now = millis();
    if (now - _lastHandleTime >= 50) {
      _lastHandleTime = now;
      SinricPro.handle();
    }
  }
}

void SinricManager::reportPosition(int position) {
  // Skip if not connected
  if (!_connected || !_initialized) {
    return;
  }

  // Avoid duplicate reports
  if (position == _lastReportedPosition) {
    return;
  }

  _lastReportedPosition = position;

  // Send position event to Sinric Pro cloud
  SinricProBlinds &myBlinds = SinricPro[SINRIC_DEVICE_ID];
  myBlinds.sendRangeValueEvent(position);

  Serial.printf("[Sinric] Reported position change: %d%%\n", position);
}
