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

use DI\Bridge\Slim\Bridge;
use DI\ContainerBuilder;
use Slim\Routing\RouteCollectorProxy;
use Slim\Views\Twig;
use Slim\Views\TwigMiddleware;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use SpaethTech\UCRM\SDK\Middleware\QueryRoutingMiddleware;
use SpaethTech\UCRM\SDK\Controllers\ServicesCheckController;
use SpaethTech\UCRM\SDK\Controllers\ServicesSyncController;
use SpaethTech\UCRM\SDK\Controllers\ServicesWebhookController;
use SpaethTech\UCRM\SDK\Controllers\PluginController;
use SpaethTech\UCRM\SDK\Controllers\ApiErrorController;
use SpaethTech\UCRM\SDK\Controllers\FallbackController;
use SpaethTech\UCRM\SDK\Twig\TimeDiffExtension;

require_once __DIR__ . "/bootstrap.php";

$builder = new ContainerBuilder();
$builder->addDefinitions(__DIR__ . "/container.php");
$container = $builder->build();

$app = Bridge::create($container);

$app->add(TwigMiddleware::createFromContainer($app, Twig::class));

$app->addBodyParsingMiddleware();
$app->addRoutingMiddleware();

$errorMiddleware = $app->addErrorMiddleware(true, true, true);

$app->add(new QueryRoutingMiddleware());

// Auth middleware for X-Auth-Token validation
$authMiddleware = function (Request $request, $handler) use ($container) {
  // error_log('[AUTH] Middleware called for: ' . $request->getUri()->getPath());

  $authHeader = $request->getHeaderLine('X-Auth-Token');
  // error_log('[AUTH] Auth header value: ' . ($authHeader ?: 'EMPTY'));

  if (empty($authHeader)) {
    // error_log('[AUTH] Missing auth header - returning 400');
    $response = new \Slim\Psr7\Response();
    $response->getBody()->write(json_encode(['error' => 'Missing X-Auth-Token header']));
    return $response->withStatus(400)->withHeader('Content-Type', 'application/json');
  }

  $plugin = $container->get(\SpaethTech\UCRM\SDK\Plugin::class);
  $expectedToken = $plugin->getAuth()->get('x_auth_token');
  // error_log('[AUTH] Expected token: ' . substr($expectedToken, 0, 10) . '...');
  // error_log('[AUTH] Received token: ' . substr($authHeader, 0, 10) . '...');

  if ($authHeader !== $expectedToken) {
    // error_log('[AUTH] Invalid token - returning 401');
    $response = new \Slim\Psr7\Response();
    $response->getBody()->write(json_encode(['error' => 'Invalid X-Auth-Token']));
    return $response->withStatus(401)->withHeader('Content-Type', 'application/json');
  }

  // error_log('[AUTH] Token valid - proceeding to handler');
  return $handler->handle($request);
};

$app->group("/api", function (RouteCollectorProxy $group) use ($authMiddleware) {

  $group->get(
    "/ping",
    function (Request $request, Response $response) {
      $response->getBody()->write(json_encode(["ping" => "pong"]));
      return $response->withHeader("Content-Type", "application/json");
    }
  );

  $group->get("/plugin", [PluginController::class, 'info'])->add($authMiddleware);

  $group->post("/services/check", [ServicesCheckController::class, 'check'])->add($authMiddleware);

  // No auth middleware for /services/sync
  $group->post("/services/sync", [ServicesSyncController::class, 'sync']);

  // Single service webhook endpoint - handles all webhook types
  $group->post("/services/webhook", [ServicesWebhookController::class, 'webhook'])->add($authMiddleware);

  $group->map(
    ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"],
    "/{path:.*}",
    [ApiErrorController::class, 'notFound']
  )->add($authMiddleware);
});

// Helper function to filter changelog by search query
$filterChangelogBySearch = function ($changelog, $searchQuery) {
  if (empty($searchQuery)) {
    return $changelog;
  }

  $filtered = ['unreleased' => ['changesets' => []], 'releases' => []];
  $query = strtolower($searchQuery);

  // Filter unreleased changesets
  foreach ($changelog['unreleased']['changesets'] as $changeset) {
    $filteredCommits = [];
    foreach ($changeset['commits'] as $commit) {
      $searchText = strtolower(($commit['message'] ?? '') . ' ' . ($commit['author'] ?? '') . ' ' . implode(' ', $commit['files'] ?? []));
      if (strpos($searchText, $query) !== false) {
        $filteredCommits[] = $commit;
      }
    }
    if (!empty($filteredCommits)) {
      $changeset['commits'] = $filteredCommits;
      $filtered['unreleased']['changesets'][] = $changeset;
    }
  }

  // Filter releases
  foreach ($changelog['releases'] as $version => $release) {
    $filteredChangesets = [];
    foreach ($release['changesets'] as $changeset) {
      $filteredCommits = [];
      foreach ($changeset['commits'] as $commit) {
        $searchText = strtolower(($commit['message'] ?? '') . ' ' . ($commit['author'] ?? '') . ' ' . implode(' ', $commit['files'] ?? []));
        if (strpos($searchText, $query) !== false) {
          $filteredCommits[] = $commit;
        }
      }
      if (!empty($filteredCommits)) {
        $changeset['commits'] = $filteredCommits;
        $filteredChangesets[] = $changeset;
      }
    }
    if (!empty($filteredChangesets)) {
      $release['changesets'] = $filteredChangesets;
      $filtered['releases'][$version] = $release;
    }
  }

  return $filtered;
};

// Combined changelog route with optional version parameter
$app->get("/changelog[/{version}]", function (Request $request, Response $response, Twig $twig, ?string $version = null) use ($filterChangelogBySearch) {
  // Default to unreleased if no version specified
  $version = $version ?: 'unreleased';

  // Load changelog data
  $changelogPath = __DIR__ . '/CHANGELOG.json';

  if (!file_exists($changelogPath)) {
    throw new \Exception('CHANGELOG.json not found');
  }

  $changelogData = json_decode(file_get_contents($changelogPath), true);

  if (json_last_error() !== JSON_ERROR_NONE) {
    throw new \Exception('Invalid JSON in CHANGELOG.json: ' . json_last_error_msg());
  }

  // Extract config and changelog data
  $config = $changelogData['config'] ?? [];
  
  // Sort releases in descending version order (newest first)
  $releases = $changelogData['releases'] ?? [];
  uksort($releases, function($a, $b) {
    // Remove 'v' prefix and split versions for comparison
    $versionA = explode('.', ltrim($a, 'v'));
    $versionB = explode('.', ltrim($b, 'v'));
    
    // Compare major, minor, patch in descending order
    for ($i = 0; $i < max(count($versionA), count($versionB)); $i++) {
      $partA = isset($versionA[$i]) ? (int)$versionA[$i] : 0;
      $partB = isset($versionB[$i]) ? (int)$versionB[$i] : 0;
      
      if ($partA !== $partB) {
        return $partB - $partA; // Descending order
      }
    }
    return 0;
  });
  
  $changelog = [
    'unreleased' => $changelogData['unreleased'] ?? ['changesets' => []],
    'releases' => $releases
  ];

  // Validate version exists
  if ($version !== 'unreleased' && !isset($changelog['releases'][$version])) {
    // Version not found, redirect to unreleased
    return $response->withHeader('Location', '/crm/_plugins/provisioner-v2/public.php?/changelog')->withStatus(302);
  }

  // Get search query - check both locations since middleware might not be extracting it
  $queryParams = $request->getQueryParams();
  // Try direct access first (if middleware extracted it)
  $searchQuery = $queryParams['q'] ?? '';
  // If not found, try inside parameters array (raw from URL)
  if (empty($searchQuery)) {
    $searchQuery = $queryParams['parameters']['q'] ?? '';
  }

  // Keep original changelog for dropdown, but also provide filtered version
  $originalChangelog = $changelog;
  $filteredChangelog = $changelog;
  if (!empty($searchQuery)) {
    $filteredChangelog = $filterChangelogBySearch($changelog, $searchQuery);
  }

  // Add the Twig extension for time_diff filter
  $twigEnvironment = $twig->getEnvironment();
  if (!$twigEnvironment->hasExtension(TimeDiffExtension::class)) {
    $twigEnvironment->addExtension(new TimeDiffExtension());
  }

  // Render template
  return $twig->render($response, 'changelog.twig', [
    'config' => $config,
    'changelog' => $searchQuery ? $filteredChangelog : $originalChangelog,  // Use filtered for search view
    'all_changelog' => $originalChangelog,  // Always pass full changelog for dropdown
    'current_version' => $version,
    'search_query' => $searchQuery
  ]);
});

// Define app routes
$app->get("/{path:.*}", [FallbackController::class, 'handlePublicRoute']);

// Run app
$app->run();
