<?php

declare(strict_types=1);

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

use Drupal\flowdrop\DTO\ExecutionContextDTO;
use Drupal\flowdrop\DTO\ParameterBagInterface;
use Drupal\flowdrop_stategraph\ApprovalGate\ApprovalGateHandler;
use Drupal\flowdrop_stategraph\ApprovalGate\ApprovalStatus;
use Drupal\flowdrop_stategraph\DTO\Checkpoint;
use Drupal\flowdrop_stategraph\DTO\GraphState;
use Drupal\flowdrop_stategraph\Exception\ApprovalRequiredException;
use Drupal\flowdrop_stategraph\Plugin\FlowDropNodeProcessor\ApprovalGateNode;
use Drupal\Tests\UnitTestCase;
use Prophecy\PhpUnit\ProphecyTrait;

/**
 * Tests the ApprovalGateNode processor.
 *
 * @coversDefaultClass \Drupal\flowdrop_stategraph\Plugin\FlowDropNodeProcessor\ApprovalGateNode
 * @group flowdrop_stategraph
 */
class ApprovalGateNodeTest extends UnitTestCase {

  use ProphecyTrait;

  /**
   * @covers ::validateParams
   */
  public function testValidateParamsMissingStateFails(): void {
    $node = new ApprovalGateNode([], 'approval_gate', ['id' => 'approval_gate']);

    $result = $node->validateParams([]);
    $this->assertFalse($result->isValid());
  }

  /**
   * @covers ::validateParams
   */
  public function testValidateParamsWithStatePasses(): void {
    $node = new ApprovalGateNode([], 'approval_gate', ['id' => 'approval_gate']);

    $result = $node->validateParams(['__state__' => ['data' => []]]);
    $this->assertTrue($result->isValid());
  }

  /**
   * @covers ::process
   */
  public function testProcessNoRequestIdThrowsApprovalRequired(): void {
    $node = new ApprovalGateNode([], 'approval_gate', ['id' => 'approval_gate']);

    $params = $this->prophesize(ParameterBagInterface::class);
    $params->get('message', 'Approval required to continue')->willReturn('Please approve');
    $params->get('_approval_request_id')->willReturn(NULL);
    $params->get('__state__', [])->willReturn([
      'messages' => [],
      'data' => [],
      'metadata' => [],
      'currentNodeId' => 'approval_gate',
      'threadId' => 'test-thread',
      'iterationCount' => 0,
      'isComplete' => FALSE,
    ]);

    $this->expectException(ApprovalRequiredException::class);
    $node->process($params->reveal());
  }

  /**
   * @covers ::process
   */
  public function testProcessResumeWithApproved(): void {
    $node = new ApprovalGateNode([], 'approval_gate', ['id' => 'approval_gate']);

    $handler = $this->prophesize(ApprovalGateHandler::class);
    $handler->getStatus('req_123')->willReturn(ApprovalStatus::Approved);
    $handler->getRequest('req_123')->willReturn([
      'notes' => 'Looks good',
      'resolvedBy' => 1,
      'resolvedAt' => 1000,
    ]);

    // Inject the handler via reflection since we can't use the container.
    $ref = new \ReflectionProperty(ApprovalGateNode::class, 'approvalHandler');
    $ref->setValue($node, $handler->reveal());

    $params = $this->prophesize(ParameterBagInterface::class);
    $params->get('message', 'Approval required to continue')->willReturn('Approve this');
    $params->get('_approval_request_id')->willReturn('req_123');
    $params->get('__state__', [])->willReturn([
      'messages' => [],
      'data' => [],
      'metadata' => [],
      'currentNodeId' => 'approval_gate',
      'threadId' => 'test-thread',
      'iterationCount' => 0,
      'isComplete' => FALSE,
    ]);

    $output = $node->process($params->reveal());

    $this->assertTrue($output['approved']);
    $this->assertEquals('Looks good', $output['approval_notes']);
    $this->assertEquals(1, $output['approved_by']);
  }

  /**
   * @covers ::process
   */
  public function testProcessResumeWithRejected(): void {
    $node = new ApprovalGateNode([], 'approval_gate', ['id' => 'approval_gate']);

    $handler = $this->prophesize(ApprovalGateHandler::class);
    $handler->getStatus('req_456')->willReturn(ApprovalStatus::Rejected);
    $handler->getRequest('req_456')->willReturn([
      'notes' => 'Not ready',
      'resolvedBy' => 2,
      'resolvedAt' => 2000,
    ]);

    $ref = new \ReflectionProperty(ApprovalGateNode::class, 'approvalHandler');
    $ref->setValue($node, $handler->reveal());

    $params = $this->prophesize(ParameterBagInterface::class);
    $params->get('message', 'Approval required to continue')->willReturn('Approve');
    $params->get('_approval_request_id')->willReturn('req_456');
    $params->get('__state__', [])->willReturn([
      'messages' => [],
      'data' => [],
      'metadata' => [],
      'currentNodeId' => 'approval_gate',
      'threadId' => 'test-thread',
      'iterationCount' => 0,
      'isComplete' => FALSE,
    ]);

    $output = $node->process($params->reveal());

    $this->assertFalse($output['approved']);
    $this->assertEquals('Not ready', $output['rejection_reason']);
  }

  /**
   * @covers ::process
   */
  public function testProcessResumeStillPendingThrows(): void {
    $node = new ApprovalGateNode([], 'approval_gate', ['id' => 'approval_gate']);

    $handler = $this->prophesize(ApprovalGateHandler::class);
    $handler->getStatus('req_789')->willReturn(ApprovalStatus::Pending);
    $state = GraphState::fromArray([
      'messages' => [],
      'data' => [],
      'metadata' => [],
      'currentNodeId' => 'approval_gate',
      'threadId' => 'test-thread',
      'iterationCount' => 0,
      'isComplete' => FALSE,
    ]);
    $checkpoint = Checkpoint::create($state, 'approval_gate');
    $handler->getCheckpoint('req_789')->willReturn($checkpoint);

    $ref = new \ReflectionProperty(ApprovalGateNode::class, 'approvalHandler');
    $ref->setValue($node, $handler->reveal());

    $params = $this->prophesize(ParameterBagInterface::class);
    $params->get('message', 'Approval required to continue')->willReturn('Approve');
    $params->get('_approval_request_id')->willReturn('req_789');
    $params->get('__state__', [])->willReturn([
      'messages' => [],
      'data' => [],
      'metadata' => [],
      'currentNodeId' => 'approval_gate',
      'threadId' => 'test-thread',
      'iterationCount' => 0,
      'isComplete' => FALSE,
    ]);

    $this->expectException(ApprovalRequiredException::class);
    $node->process($params->reveal());
  }

  /**
   * @covers ::process
   */
  public function testProcessWithoutHandlerStillThrows(): void {
    $node = new ApprovalGateNode([], 'approval_gate', ['id' => 'approval_gate']);

    // No handler injected - should still throw ApprovalRequiredException.
    $params = $this->prophesize(ParameterBagInterface::class);
    $params->get('message', 'Approval required to continue')->willReturn('Approve');
    $params->get('_approval_request_id')->willReturn(NULL);
    $params->get('__state__', [])->willReturn([
      'messages' => [],
      'data' => [],
      'metadata' => [],
      'currentNodeId' => 'approval_gate',
      'threadId' => 'test-thread',
      'iterationCount' => 0,
      'isComplete' => FALSE,
    ]);

    $this->expectException(ApprovalRequiredException::class);
    $node->process($params->reveal());
  }

  /**
   * @covers ::setExecutionContext
   */
  public function testSetExecutionContextUsedForNodeId(): void {
    $node = new ApprovalGateNode([], 'approval_gate', ['id' => 'approval_gate']);

    $context = new ExecutionContextDTO(
      initialData: [],
      workflowId: 'wf_1',
      pipelineId: 'pl_1',
      executionId: 'exec_1',
      nodeId: 'custom_gate_1',
      metadata: ['node_id' => 'custom_gate_1'],
    );
    $node->setExecutionContext($context);

    $params = $this->prophesize(ParameterBagInterface::class);
    $params->get('message', 'Approval required to continue')->willReturn('Approve');
    $params->get('_approval_request_id')->willReturn(NULL);
    $params->get('__state__', [])->willReturn([
      'messages' => [],
      'data' => [],
      'metadata' => [],
      'currentNodeId' => 'custom_gate_1',
      'threadId' => 'test-thread',
      'iterationCount' => 0,
      'isComplete' => FALSE,
    ]);

    try {
      $node->process($params->reveal());
      $this->fail('Expected ApprovalRequiredException');
    }
    catch (ApprovalRequiredException $e) {
      $this->assertEquals('custom_gate_1', $e->nodeId);
    }
  }

}
