<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop_interrupt\Unit\Plugin\FlowDropNodeProcessor;

use Drupal\flowdrop\DTO\ParameterBagInterface;
use Drupal\flowdrop_interrupt\Exception\InterruptRequiredException;
use Drupal\flowdrop_interrupt\Message\TimerInterruptMessage;
use Drupal\flowdrop_interrupt\Plugin\FlowDropNodeProcessor\ScheduleNode;
use Drupal\Tests\UnitTestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;

/**
 * Tests the ScheduleNode datetime parsing and pause behaviour.
 *
 * @coversDefaultClass \Drupal\flowdrop_interrupt\Plugin\FlowDropNodeProcessor\ScheduleNode
 * @group flowdrop_interrupt
 */
class ScheduleNodeTest extends UnitTestCase {

  use ProphecyTrait;

  /**
   * Builds a ScheduleNode with a stubbed config factory.
   */
  protected function createNode(int $maxDelay = 31536000): ScheduleNode {
    $container = new ContainerBuilder();
    $container->set('config.factory', $this->getConfigFactoryStub([
      'flowdrop_interrupt.settings' => ['timer_max_delay' => $maxDelay],
    ]));
    return ScheduleNode::create($container, [], 'schedule', []);
  }

  /**
   * Builds a parameter bag prophecy for `when`.
   */
  protected function params(string $when): ParameterBagInterface {
    $params = $this->prophesize(ParameterBagInterface::class);
    $params->getString('when')->willReturn($when);
    return $params->reveal();
  }

  /**
   * A future ISO 8601 datetime throws a TimerInterruptMessage at that time.
   */
  public function testProcessParsesIso8601(): void {
    $node = $this->createNode();
    $future = time() + 7200;
    $iso = date('c', $future);

    try {
      $node->process($this->params($iso));
      $this->fail('Expected InterruptRequiredException');
    }
    catch (InterruptRequiredException $e) {
      $message = $e->getInterruptMessage();
      $this->assertInstanceOf(TimerInterruptMessage::class, $message);
      $this->assertSame($future, $message->fireAt);
    }
  }

  /**
   * A numeric unix timestamp is accepted as-is.
   */
  public function testProcessParsesUnixTimestamp(): void {
    $node = $this->createNode();
    $future = time() + 600;

    try {
      $node->process($this->params((string) $future));
      $this->fail('Expected InterruptRequiredException');
    }
    catch (InterruptRequiredException $e) {
      $message = $e->getInterruptMessage();
      $this->assertInstanceOf(TimerInterruptMessage::class, $message);
      $this->assertSame($future, $message->fireAt);
    }
  }

  /**
   * A past datetime passes straight through without pausing.
   */
  public function testPastTimeReturnsImmediately(): void {
    $node = $this->createNode();
    $past = time() - 3600;

    $output = $node->process($this->params((string) $past));

    $this->assertTrue($output['resolved_by_timer']);
    $this->assertSame($past, $output['scheduled_at']);
    $this->assertEqualsWithDelta(time(), $output['resumed_at'], 2);
  }

  /**
   * Unparseable runtime input throws InvalidArgumentException, not a pause.
   */
  public function testProcessRejectsUnparseableWhen(): void {
    $node = $this->createNode();

    $this->expectException(\InvalidArgumentException::class);
    $node->process($this->params('not a datetime at all %%%'));
  }

  /**
   * Sub-epoch-floor numerics are rejected, not silently treated as 1970.
   *
   * A bare year ("2026") is numeric and would otherwise parse to a
   * 1970-era timestamp — already past — making the node a silent no-op.
   */
  public function testProcessRejectsSmallNumerics(): void {
    $node = $this->createNode();

    $this->expectException(\InvalidArgumentException::class);
    $node->process($this->params('2026'));
  }

  /**
   * Validation accepts parseable input, rejects garbage and over-cap times.
   */
  public function testValidateParams(): void {
    $node = $this->createNode(maxDelay: 86400);

    // Empty passes — the value may arrive via connection at runtime.
    $this->assertTrue($node->validateParams([])->isValid());
    $this->assertTrue($node->validateParams(['when' => ''])->isValid());

    $this->assertTrue($node->validateParams(['when' => date('c', time() + 3600)])->isValid());
    $this->assertTrue($node->validateParams(['when' => (string) (time() + 3600)])->isValid());
    // Past times are valid config — they pass through at runtime.
    $this->assertTrue($node->validateParams(['when' => '2020-01-01T00:00:00+00:00'])->isValid());

    $this->assertFalse($node->validateParams(['when' => 'not a datetime at all %%%'])->isValid());
    $this->assertFalse($node->validateParams(['when' => ['array']])->isValid());
    // A bare year is numeric → 1970-era timestamp → must be rejected, not
    // silently passed through as "already due".
    $this->assertFalse($node->validateParams(['when' => '2026'])->isValid());
    // 2 days out > 1 day cap.
    $this->assertFalse($node->validateParams(['when' => date('c', time() + 172800)])->isValid());
  }

}
