<?php declare(strict_types=1);
/**
 * @author Ryan Spaeth <rspaeth@spaethtech.com>
 * @copyright 2025 - Spaeth Technologies, Archous Networks
 */

namespace SpaethTech\UCRM\SDK\Controllers;

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;

class ServicesWebhookController extends BaseController
{
  /**
   * Single webhook endpoint that handles all service webhook types
   * Examines the request body to determine which handler to call
   */
  public function webhook(Request $request, Response $response): Response
  {
    try {
      $payload = $request->getParsedBody();

      // Log all incoming webhook requests for debugging
      // error_log("[WEBHOOK] Incoming request body: " . json_encode($payload, JSON_PRETTY_PRINT));

      // Handle test webhooks from UISP GUI
      if (
        (isset($payload['entity']) && $payload['entity'] === 'webhook' && isset($payload['changeType']) && $payload['changeType'] === 'test') ||
        (isset($payload['eventName']) && $payload['eventName'] === 'test')
      ) {
        // error_log("[WEBHOOK] Test webhook received from UISP GUI");

        // Debug: Check server configuration for test webhooks
        $server = $this->plugin->getServer();
        // $provisioner_url = $server->getSetting("PROVISIONER_URL");
        // $auth_token = $server->getSetting("X_AUTH_TOKEN");
        $provisioner_url = $this->plugin->getConfig()->get("provisioner_url");
        $auth_token = $this->plugin->getAuth()->get("x_auth_token");
        $plugin_url = $this->plugin->getPublicUrl() . ".php?";
        // error_log("[WEBHOOK] Test Debug: PLUGIN_URL = " . ($plugin_url ?: 'NULL'));
        // error_log("[WEBHOOK] Test Debug: PROVISIONER_URL = " . ($provisioner_url ?: 'NULL'));
        // error_log("[WEBHOOK] Test Debug: X_AUTH_TOKEN = " . ($auth_token ? 'SET (' . strlen($auth_token) . ' chars)' : 'NULL'));

        return $this->jsonResponse($response, [
          'success' => true,
          'message' => 'Test webhook received successfully',
          'timestamp' => date('c'),
          'uuid' => $this->extractWebhookUuid($payload)
        ]);
      }

      // TODO: Auth validation using webhook UUID
      // Extract webhook UUID and validate against UISP /crm/api/v1.0/webhook-events/{uuid}
      $webhookUuid = $this->extractWebhookUuid($payload);
      if ($webhookUuid) {
        // error_log("[WEBHOOK] Webhook UUID: {$webhookUuid}");
        // TODO: Call UISP /crm/api/v1.0/webhook-events/{$webhookUuid} for auth validation
        // This will prevent unauthorized external calls
      } else {
        // error_log("[WEBHOOK] No webhook UUID found in payload");
      }

      // For now, just forward ALL webhooks to server for documentation/debugging
      // error_log("[WEBHOOK] Forwarding webhook to server for documentation");
      $this->forwardRawWebhookToServer($payload);

      return $this->jsonResponse($response, [
        'success' => true,
        'message' => 'Webhook received and forwarded to server for logging',
        'timestamp' => date('c'),
        'uuid' => $this->extractWebhookUuid($payload)
      ]);

    } catch (\Exception $e) {
      // error_log("[WEBHOOK] Error processing webhook: " . $e->getMessage());
      return $this->jsonError($response, "Webhook processing failed: " . $e->getMessage(), 500);
    }
  }

  /**
   * Determine webhook type from UISP webhook payload
   */
  private function determineWebhookType(array $payload): ?string
  {
    // UISP webhooks typically have an 'entity' and 'changeType' field
    if (isset($payload['entity']) && isset($payload['changeType'])) {
      $entity = $payload['entity'];
      $changeType = $payload['changeType'];

      // Only handle service entity webhooks
      if ($entity === 'service') {
        // Map UISP changeType to our webhook type names
        $typeMap = [
          'insert' => 'add',          // Service created/activated
          'update' => 'edit',         // Service modified
          'archive' => 'archive',     // Service archived
          'delete' => 'end',          // Service ended
          'suspend' => 'suspend',     // Service suspended
          'activate' => 'activate',   // Service activated
          'postpone' => 'postpone',   // Service postponed
          'suspend_cancel' => 'suspend_cancel', // Suspension cancelled
          'outage' => 'outage'        // Service outage
        ];

        return $typeMap[$changeType] ?? null;
      }
    }

    // Fallback: check for explicit webhook_type field
    if (isset($payload['webhook_type'])) {
      return $payload['webhook_type'];
    }

    // Fallback: check for type field
    if (isset($payload['type'])) {
      return $payload['type'];
    }

    return null;
  }

  /**
   * Extract webhook UUID from UISP webhook payload for auth validation
   */
  private function extractWebhookUuid(array $payload): ?string
  {
    // UISP webhooks typically include a UUID field for validation
    if (isset($payload['uuid'])) {
      return $payload['uuid'];
    }

    // Check other possible UUID field names
    if (isset($payload['id'])) {
      return $payload['id'];
    }

    if (isset($payload['webhook_id'])) {
      return $payload['webhook_id'];
    }

    if (isset($payload['eventId'])) {
      return $payload['eventId'];
    }

    return null;
  }

  /**
   * Core webhook handling logic - ignore webhook body, use our own service resolution
   */
  private function handleServiceWebhook(Request $request, Response $response, string $webhookType): Response
  {
    try {
      $payload = $request->getParsedBody();
      $service_id = null;

      // Extract service_id from various possible locations in webhook payload
      if (isset($payload['service_id'])) {
        $service_id = (int) $payload['service_id'];
      } elseif (isset($payload['serviceId'])) {
        $service_id = (int) $payload['serviceId'];
      } elseif (isset($payload['entityId'])) {
        $service_id = (int) $payload['entityId'];
      } elseif (isset($payload['extraData']['entity']['id'])) {
        $service_id = (int) $payload['extraData']['entity']['id'];
      } elseif (isset($payload['entity']['id'])) {
        $service_id = (int) $payload['entity']['id'];
      } elseif (isset($payload['id'])) {
        $service_id = (int) $payload['id'];
      }

      if (!$service_id) {
        // error_log("[WEBHOOK] No service_id found in {$webhookType} webhook payload");
        return $this->jsonError($response, "No service_id found in webhook payload", 400);
      }

      // error_log("[WEBHOOK] {$webhookType}: Processing service_id {$service_id}");

      // IGNORE webhook body data - it's unreliable
      // Use our own resolution logic to get current, accurate state
      $resolution = $this->checkService($service_id);

      if (!$resolution) {
        // error_log("[WEBHOOK] {$webhookType}: Failed to resolve service_id {$service_id}");
        return $this->jsonError($response, "Failed to resolve service", 500);
      }

      // Notify server of changes based on our resolution
      $this->notifyServer($service_id, $resolution, $webhookType);

      // Forward webhook to server for debugging/documentation (dev mode)
      $this->forwardWebhookToServer($payload, $webhookType, $service_id, $resolution);

      return $this->jsonResponse($response, [
        'success' => true,
        'webhook_type' => $webhookType,
        'service_id' => $service_id,
        'action_taken' => $this->determineAction($resolution)
      ]);

    } catch (\Exception $e) {
      // error_log("[WEBHOOK] {$webhookType}: Error processing webhook: " . $e->getMessage());
      return $this->jsonError($response, "Webhook processing failed: " . $e->getMessage(), 500);
    }
  }

  /**
   * Use our existing services/check logic to get accurate service state
   */
  private function checkService(int $service_id): ?array
  {
    try {
      // Make internal call to our own /api/services/check endpoint
      $server = $this->plugin->getServer();
      $plugin_url = $this->plugin->getPublicUrl() . ".php?";

      // Build the full URL for our own services/check endpoint
      $url = "{$plugin_url}/api/services/check";

      $response = $this->client->request(
        'POST',
        $url,
        [
          'json' => ['ids' => [$service_id]],
          'headers' => [
            'Content-Type' => 'application/json'
          ],
          'timeout' => 10,
          'http_errors' => false
        ]
      );

      $httpCode = $response->getStatusCode();
      $responseBody = $response->getBody()->getContents();

      if ($httpCode !== 200) {
        // error_log("[WEBHOOK] services/check returned HTTP {$httpCode}: {$responseBody}");
        return null;
      }

      $data = json_decode($responseBody, true);
      return $data[$service_id] ?? null;

    } catch (\Exception $e) {
      // error_log("[WEBHOOK] Service check failed: " . $e->getMessage());
      return null;
    }
  }

  /**
   * Notify the server about service changes
   */
  private function notifyServer(int $service_id, array $resolution, string $webhookType): void
  {
    try {
      // Get server configuration
      $server = $this->plugin->getServer();
      $provisioner_url = $this->plugin->getConfig()->get("provisioner_url");
      $auth_token = $this->plugin->getAuth()->get("x_auth_token");

      if (!$provisioner_url || !$auth_token) {
        // error_log("[WEBHOOK] {$webhookType}: Missing server URL or auth token");
        return;
      }

      if ($resolution['should_delete']) {
        // Call DELETE /api/v2/configs/{service_id}
        $url = "{$provisioner_url}/api/v2/configs/{$service_id}";
        $this->makeServerRequest('DELETE', $url, $auth_token, null);
        // error_log("[WEBHOOK] {$webhookType}: Called DELETE for service_id {$service_id}");

      } elseif ($resolution['final'] && $resolution['final'] !== $service_id) {
        // Call PATCH /api/v2/configs/{service_id}
        $url = "{$provisioner_url}/api/v2/configs/{$service_id}";
        $data = [
          'final' => $resolution['final'],
          'final_status' => $resolution['final_status']
        ];
        $this->makeServerRequest('PATCH', $url, $auth_token, $data);
        // error_log("[WEBHOOK] {$webhookType}: Called PATCH for service_id {$service_id} -> {$resolution['final']}");

      } else {
        // error_log("[WEBHOOK] {$webhookType}: No action needed for service_id {$service_id}");
      }

    } catch (\Exception $e) {
      // error_log("[WEBHOOK] Failed to notify server: " . $e->getMessage());
    }
  }

  /**
   * Forward raw webhook to server for debugging/documentation (dev mode)
   */
  private function forwardRawWebhookToServer(array $payload): void
  {
    try {
      // Get server configuration
      $provisioner_url = $this->plugin->getConfig()->get("provisioner_url");
      $auth_token = $this->plugin->getAuth()->get("x_auth_token");

      if (!$provisioner_url || !$auth_token) {
        // error_log("[WEBHOOK] Forward: Missing server URL or auth token");
        return;
      }

      // Just forward the raw payload as-is
      $url = "{$provisioner_url}/api/v2/configs/webhook";
      $this->makeServerRequest('POST', $url, $auth_token, $payload);

      // error_log("[WEBHOOK] Forwarded raw webhook to server for documentation");

    } catch (\Exception $e) {
      // Don't fail the main webhook processing if forwarding fails
      // error_log("[WEBHOOK] Failed to forward raw webhook to server (non-critical): " . $e->getMessage());
    }
  }

  /**
   * Forward webhook to server for debugging/documentation (dev mode)
   */
  private function forwardWebhookToServer(array $payload, string $webhookType, int $service_id, array $resolution): void
  {
    try {
      // Get server configuration
      $server = $this->plugin->getServer();
      // $provisioner_url = $server->getSetting("PROVISIONER_URL");
      // $auth_token = $server->getSetting("X_AUTH_TOKEN");
      $provisioner_url = $this->plugin->getConfig()->get("provisioner_url");
      $auth_token = $this->plugin->getAuth()->get("x_auth_token");

      // Debug: Log what we're getting
      // error_log("[WEBHOOK] Forward: PROVISIONER_URL = " . ($provisioner_url ?: 'NULL'));
      // error_log("[WEBHOOK] Forward: X_AUTH_TOKEN = " . ($auth_token ? 'SET (' . strlen($auth_token) . ' chars)' : 'NULL'));

      if (!$provisioner_url || !$auth_token) {
        // error_log("[WEBHOOK] Forward: Missing server URL or auth token");
        return;
      }

      // Prepare forwarded webhook data
      $forwardData = [
        'webhook_type' => $webhookType,
        'service_id' => $service_id,
        'resolution' => $resolution,
        'original_payload' => $payload,
        'plugin_action' => $this->determineAction($resolution),
        'forwarded_at' => date('c'),
        'plugin_version' => 'v2'
      ];

      $url = "{$provisioner_url}/api/v2/configs/webhook";
      $this->makeServerRequest('POST', $url, $auth_token, $forwardData);

      // error_log("[WEBHOOK] Forwarded {$webhookType} webhook to server for debugging");

    } catch (\Exception $e) {
      // Don't fail the main webhook processing if forwarding fails
      // error_log("[WEBHOOK] Failed to forward webhook to server (non-critical): " . $e->getMessage());
    }
  }

  /**
   * Make HTTP request to server API
   */
  private function makeServerRequest(string $method, string $url, string $authToken, ?array $data): void
  {
    $requestOptions = [
      'headers' => [
        'Content-Type' => 'application/json',
        'X-Auth-Token' => $authToken
      ],
      'timeout' => 10,
      'http_errors' => false
    ];

    if ($data && in_array($method, ['POST', 'PATCH', 'PUT'])) {
      $requestOptions['json'] = $data;
    }

    $response = $this->client->request($method, $url, $requestOptions);

    $httpCode = $response->getStatusCode();
    $responseBody = $response->getBody()->getContents();

    if ($httpCode >= 400) {
      throw new \Exception("Server returned HTTP {$httpCode}: {$responseBody}");
    }

    // error_log("[WEBHOOK] Server request successful: {$method} {$url} -> HTTP {$httpCode}");
  }

  /**
   * Determine what action was taken for logging
   */
  private function determineAction(array $resolution): string
  {
    if ($resolution['should_delete']) {
      return 'delete_config';
    } elseif ($resolution['final'] && $resolution['final'] !== $resolution['original_id']) {
      return 'update_config';
    } else {
      return 'no_action';
    }
  }

  /**
   * Helper to return JSON response
   */
  private function jsonResponse(Response $response, array $data): Response
  {
    $json = json_encode($data);
    if ($json === false) {
      return $this->jsonError($response, "Failed to encode JSON response", 500);
    }

    $response->getBody()->write($json);
    return $response->withHeader("Content-Type", "application/json");
  }

  /**
   * Helper to return JSON error response
   */
  private function jsonError(Response $response, string $message, int $code): Response
  {
    $errorData = [
      "error" => $message,
      "code" => $code,
      "timestamp" => date('c')
    ];

    $json = json_encode($errorData);
    if ($json === false) {
      $json = '{"error":"JSON encoding failed","code":500}';
    }

    $response->getBody()->write($json);
    return $response
      ->withHeader("Content-Type", "application/json")
      ->withStatus($code);
  }
}