<?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 ServicesCheckController extends BaseController
{
  // Configuration constants for recursion limits
  private const MAX_ANCESTORS = 10;
  private const MAX_DESCENDANTS = 10;

  public function check(Request $request, Response $response): Response
  {
    try {
      // Check if this is an internal call (from another controller)
      $isInternal = $request->getAttribute('internal', false);

      // Only validate auth token for external calls
      if (!$isInternal) {
        $authToken = $request->getHeaderLine('X-Auth-Token');
        if (empty($authToken)) {
          return $this->json($response, "Missing X-Auth-Token header", 401);
        }

        // TODO: Implement proper token validation against instance database
        // For now, just ensure token is provided (basic security)
      }

      $server = $this->plugin->getServer();

      $ucrmDB = $server->getUcrmDB();

      if (!$ucrmDB) {
        return $this->json($response, "Database connection failed", 500);
      }

      $timezone = $server->getSetting("APP_TIMEZONE");
      if (!date_default_timezone_set($timezone ?? "Etc/UTC")) {
        return $this->json($response, "Failed to set timezone", 500);
      }
      $today = date("Y-m-d");

      $body = $request->getParsedBody();

      if (!isset($body["ids"])) {
        return $this->json($response, "Missing required parameter: ids", 400);
      }

      if (!is_array($body["ids"])) {
        return $this->json($response, "Parameter 'ids' must be an array", 400);
      }

      $body_ids = array_filter($body["ids"]);
      $original_ids = $body_ids;

      if (count($body_ids) === 0) {
        return $this->json($response, "No IDs provided", 400);
      }

      $processed_ids = [];
      $chains = [];
      $chain_origins = [];

      while (count($body_ids) > 0) {
        $id = array_shift($body_ids);

        $original_id = $chain_origins[$id] ?? $id;

        if (in_array($id, $processed_ids)) {
          continue;
        }
        $processed_ids[] = $id;

        if (in_array($original_id, $original_ids) && !isset($chains[$original_id])) {
          $chains[$original_id] = [];
        }

        if (!is_int($id)) {
          if (in_array($original_id, $original_ids)) {
            $chains[$original_id][] = [
              "id" => $id,
              "check" => "is_int()",
              "value" => "Invalid ID",
              "next" => null,
            ];
          }
          continue;
        }



        try {
          // Get COMPLETE service chain using two separate recursive CTEs
          $stmt = $ucrmDB->prepare("
            WITH RECURSIVE 
            -- Get all ancestors (services that eventually lead to our service)
            ancestors AS (
              SELECT service_id, status, active_from, active_to, superseded_by_service_id, deleted_at, invoicing_start, 0 as level
              FROM service 
              WHERE superseded_by_service_id = ?
              
              UNION ALL
              
              SELECT s.service_id, s.status, s.active_from, s.active_to, s.superseded_by_service_id, s.deleted_at, s.invoicing_start, a.level - 1
              FROM service s
              INNER JOIN ancestors a ON s.superseded_by_service_id = a.service_id
              WHERE a.level > -" . self::MAX_ANCESTORS . "
            ),
            -- Get all descendants (services our service eventually leads to)  
            descendants AS (
              SELECT service_id, status, active_from, active_to, superseded_by_service_id, deleted_at, invoicing_start, 0 as level
              FROM service
              WHERE service_id = (SELECT superseded_by_service_id FROM service WHERE service_id = ? LIMIT 1)
              
              UNION ALL
              
              SELECT s.service_id, s.status, s.active_from, s.active_to, s.superseded_by_service_id, s.deleted_at, s.invoicing_start, d.level + 1
              FROM service s  
              INNER JOIN descendants d ON s.service_id = d.superseded_by_service_id
              WHERE d.level < " . self::MAX_DESCENDANTS . "
            )
            -- Combine: ancestors + self + descendants
            SELECT DISTINCT service_id, status, active_from, active_to, superseded_by_service_id, deleted_at, invoicing_start FROM ancestors
            UNION ALL
            SELECT DISTINCT service_id, status, active_from, active_to, superseded_by_service_id, deleted_at, invoicing_start FROM descendants  
            UNION ALL
            SELECT DISTINCT service_id, status, active_from, active_to, superseded_by_service_id, deleted_at, invoicing_start FROM service WHERE service_id = ?
            ORDER BY service_id
          ");
          $stmt->execute([$id, $id, $id]);
          $services = $stmt->fetchAll();
        } catch (\Exception $e) {
          return $this->json($response, "Database query failed: " . $e->getMessage(), 500);
        }

        // Build the pristine chain - NEVER modify the actual service data
        $chain = [];
        foreach ($services as $service) {
          $chain[] = [
            "id" => (int) $service["service_id"],
            "check" => null,  // Reserved for future use
            "value" => null,  // Reserved for future use  
            "next" => $service["superseded_by_service_id"] ? (int) $service["superseded_by_service_id"] : null,
            "status" => (int) $service["status"],
            "active_from" => $service["active_from"],
            "active_to" => $service["active_to"],
            "deleted_at" => $service["deleted_at"],
            "invoicing_start" => $service["invoicing_start"],
          ];
        }

        // Store the pristine chain
        $chains[$original_id] = $chain;

        // Now do SEPARATE processing logic to determine correct service, status, etc.
        $correct_service = null;
        $correct_status = null;
        $should_delete = false;

        // Find the current "correct" service from the chain
        foreach ($services as $service) {
          // Logic to determine which is the current correct service
          if ($service["service_id"] == $id) {
            // Found the originally requested service
            if ($service["status"] == 0 && $service["active_from"] && date("Y-m-d", strtotime($service["active_from"])) > $today) {
              // Future service - it's correct
              $correct_service = (int) $service["service_id"];
              $correct_status = (int) $service["status"];
              break;
            } elseif ($service["status"] == 5 && $service["superseded_by_service_id"]) {
              // Obsolete - find what it points to
              foreach ($services as $check_service) {
                if ($check_service["service_id"] == $service["superseded_by_service_id"]) {
                  $correct_service = (int) $check_service["service_id"];
                  $correct_status = (int) $check_service["status"];
                  break;
                }
              }
              break;
            } else {
              // Active, suspended, etc - use as-is
              $correct_service = (int) $service["service_id"];
              $correct_status = (int) $service["status"];
              break;
            }
          }
        }

        // Check if final service should be deleted
        if ($correct_service) {
          foreach ($services as $service) {
            if ($service["service_id"] == $correct_service && $service["deleted_at"] && $service["status"] != 6) {
              $should_delete = true;
              break;
            }
          }
        }
      }

      $final_results = [];
      foreach ($original_ids as $orig_id) {
        if (!isset($chains[$orig_id])) {
          $final_results[$orig_id] = [
            "final" => null,
            "final_status" => null,
            "should_delete" => false,
            "chain" => []
          ];
          continue;
        }

        $last_valid_id = null;
        foreach ($chains[$orig_id] as $entry) {
          // If this entry has a next service, check if it's not obsolete before using it
          if ($entry["next"] !== null) {
            // Find the next service in the chain to check its status
            $next_service_entry = null;
            foreach ($chains[$orig_id] as $check_entry) {
              if ($check_entry["id"] === $entry["next"]) {
                $next_service_entry = $check_entry;
                break;
              }
            }

            // Only use as final if the next service is not obsolete (status 5)
            if ($next_service_entry === null || $next_service_entry["status"] !== 5) {
              $last_valid_id = $entry["next"];
            }
          }
        }

        // If we have a chain but no next service found, look for the service itself
        // Find any entry with check=null (meaning it's the actual service data, not a status check)
        if ($last_valid_id === null && !empty($chains[$orig_id])) {
          foreach ($chains[$orig_id] as $entry) {
            // If this entry has no check (meaning it's current/valid service data) and no next
            // AND it's not obsolete (status 5)
            if ($entry["check"] === null && $entry["next"] === null && $entry["status"] !== 5) {
              $last_valid_id = $entry["id"];
              break;
            }
          }
        }

        // TIMING-AWARE LOGIC: Check if the potential final service is DEFERRED and not yet active
        $timing_aware_final = $last_valid_id;
        $final_status = null;
        $should_delete = false;
        $effective_date = null;

        // Check if original service is ended (status 2) when there's no final service
        if ($last_valid_id === null) {
          // Find the original service in the chain
          foreach ($chains[$orig_id] as $entry) {
            if ($entry["id"] === $orig_id && $entry["status"] === 2) {
              $should_delete = true;
              break;
            }
          }
        }

        // Process final service if exists
        if ($last_valid_id !== null) {
          // Find the final service entry in the chain
          $final_service_entry = null;
          foreach ($chains[$orig_id] as $entry) {
            if ($entry["id"] === $last_valid_id) {
              $final_service_entry = $entry;
              break;
            }
          }

          if ($final_service_entry) {
            $final_status = $final_service_entry["status"];

            // Handle timing-aware logic for different service statuses
            // The "final" field represents the EXPECTED/CORRECTED service_id for configs
            if ($final_service_entry["status"] === 6) {
              // DEFERRED services (status 6) - check if they're active based on date
              $effective_date = $final_service_entry["invoicing_start"] ?: $final_service_entry["active_from"];

              if ($effective_date && $effective_date > $today) {
                // DEFERRED service not active yet - configs should stay on current active service
                foreach ($chains[$orig_id] as $entry) {
                  // Look for currently active service (status 1) or the original service
                  if ($entry["id"] === $orig_id && $entry["status"] === 1) {
                    $timing_aware_final = $orig_id;
                    $final_status = 1; // ACTIVE
                    break;
                  } elseif ($entry["status"] === 1 && ($entry["active_to"] === null || $entry["active_to"] > $today)) {
                    $timing_aware_final = $entry["id"];
                    $final_status = 1; // ACTIVE
                    break;
                  }
                }
              }
              // If DEFERRED service IS active (date <= today), configs should use the DEFERRED service
            } elseif ($final_service_entry["status"] === 4) {
              // BLOCKED services (status 4) - immediately active but blocked
              // Configs should immediately use the blocked service
              // Keep $timing_aware_final as-is (already set to $last_valid_id)
            }

            // Check if we should delete configs
            // 1. Service chain ends in deleted non-DEFERRED service
            if ($final_service_entry["deleted_at"] !== null && $final_service_entry["status"] !== 6) {
              $should_delete = true;
            }
            // 2. Service is ended (status 2) - ended services are not configurable
            if ($final_service_entry["status"] === 2) {
              $should_delete = true;
            }
          } else {
            // If we can't find the final service in chain, check original service
            foreach ($chains[$orig_id] as $entry) {
              if ($entry["id"] === $orig_id) {
                $final_status = $entry["status"];
                // Delete if service is deleted (non-DEFERRED) OR ended
                if (($entry["deleted_at"] !== null && $entry["status"] !== 6) || $entry["status"] === 2) {
                  $should_delete = true;
                }
                break;
              }
            }
          }
        }

        $result = [
          "correct" => $timing_aware_final,
          "correct_status" => $final_status,
          "should_delete" => $should_delete,
          "chain" => $chains[$orig_id]
        ];

        // Add effective_date if DEFERRED service will become active in the future
        if ($effective_date && $effective_date > $today) {
          $result["effective_date"] = $effective_date;
        }

        $final_results[$orig_id] = $result;
      }

      $json = json_encode($final_results);
      if ($json === false) {
        return $this->json($response, "Failed to encode JSON response", 500);
      }

      $response->getBody()->write($json);
      return $response->withHeader("Content-Type", "application/json");
    } catch (\Exception $e) {
      return $this->json($response, "Internal server error: " . $e->getMessage(), 500);
    }
  }

}