<?php

namespace Drupal\Tests\eb\Traits;

use Drupal\eb\Result\ExecutionResult;
use Drupal\eb\Result\PreviewResult;
use Drupal\eb\Result\RollbackResult;
use Drupal\eb\Result\ValidationResult;

/**
 * Provides custom assertion methods for Entity Builder result objects.
 */
trait ValidationAssertionsTrait {

  /**
   * Asserts that validation passed without errors.
   *
   * @param \Drupal\eb\Result\ValidationResult $result
   *   The validation result.
   * @param string $message
   *   Optional assertion message.
   */
  protected function assertValidationResultPasses(ValidationResult $result, string $message = ''): void {
    $message = $message ?: 'Validation should pass.';
    $errors = $result->getErrors();

    if (!empty($errors)) {
      $errorMessages = array_map(
        fn($e) => is_array($e) ? ($e['message'] ?? 'Unknown') : (string) $e,
        $errors
      );
      $message .= ' Errors: ' . implode('; ', $errorMessages);
    }

    $this->assertTrue($result->isValid(), $message);
    $this->assertEmpty($errors, 'Validation result should have no errors.');
  }

  /**
   * Asserts that validation failed with errors.
   *
   * @param \Drupal\eb\Result\ValidationResult $result
   *   The validation result.
   * @param string $message
   *   Optional assertion message.
   */
  protected function assertValidationResultFails(ValidationResult $result, string $message = ''): void {
    $message = $message ?: 'Validation should fail.';
    $this->assertFalse($result->isValid(), $message);
    $this->assertNotEmpty($result->getErrors(), 'Validation result should have errors.');
  }

  /**
   * Asserts that validation contains a specific error message.
   *
   * @param \Drupal\eb\Result\ValidationResult $result
   *   The validation result.
   * @param string $expectedMessage
   *   The expected error message (partial match).
   * @param string $message
   *   Optional assertion message.
   */
  protected function assertValidationHasError(ValidationResult $result, string $expectedMessage, string $message = ''): void {
    $message = $message ?: "Validation should contain error: $expectedMessage";
    $found = FALSE;

    foreach ($result->getErrors() as $error) {
      $errorMsg = is_array($error) ? ($error['message'] ?? '') : (string) $error;
      if (str_contains((string) $errorMsg, $expectedMessage)) {
        $found = TRUE;
        break;
      }
    }

    $this->assertTrue($found, $message);
  }

  /**
   * Asserts that validation contains a specific error code.
   *
   * @param \Drupal\eb\Result\ValidationResult $result
   *   The validation result.
   * @param string $expectedCode
   *   The expected error code.
   * @param string $message
   *   Optional assertion message.
   */
  protected function assertValidationHasErrorCode(ValidationResult $result, string $expectedCode, string $message = ''): void {
    $message = $message ?: "Validation should contain error code: $expectedCode";
    $found = FALSE;

    foreach ($result->getErrors() as $error) {
      $code = is_array($error) ? ($error['code'] ?? '') : '';
      if ($code === $expectedCode) {
        $found = TRUE;
        break;
      }
    }

    $this->assertTrue($found, $message);
  }

  /**
   * Asserts that validation has a specific number of errors.
   *
   * @param \Drupal\eb\Result\ValidationResult $result
   *   The validation result.
   * @param int $expectedCount
   *   The expected error count.
   * @param string $message
   *   Optional assertion message.
   */
  protected function assertValidationErrorCount(ValidationResult $result, int $expectedCount, string $message = ''): void {
    $message = $message ?: "Validation should have exactly $expectedCount error(s).";
    $this->assertCount($expectedCount, $result->getErrors(), $message);
  }

  /**
   * Asserts that validation has warnings.
   *
   * @param \Drupal\eb\Result\ValidationResult $result
   *   The validation result.
   * @param string $message
   *   Optional assertion message.
   */
  protected function assertValidationHasWarnings(ValidationResult $result, string $message = ''): void {
    $message = $message ?: 'Validation should have warnings.';
    $this->assertNotEmpty($result->getWarnings(), $message);
  }

  /**
   * Asserts that validation contains a specific warning message.
   *
   * @param \Drupal\eb\Result\ValidationResult $result
   *   The validation result.
   * @param string $expectedWarning
   *   The expected warning message (partial match).
   * @param string $message
   *   Optional assertion message.
   */
  protected function assertValidationHasWarning(ValidationResult $result, string $expectedWarning, string $message = ''): void {
    $message = $message ?: "Validation should contain warning: $expectedWarning";
    $found = FALSE;

    foreach ($result->getWarnings() as $warning) {
      $warningMsg = is_array($warning) ? ($warning['message'] ?? '') : (string) $warning;
      if (str_contains((string) $warningMsg, $expectedWarning)) {
        $found = TRUE;
        break;
      }
    }

    $this->assertTrue($found, $message);
  }

  /**
   * Asserts that execution was successful.
   *
   * @param \Drupal\eb\Result\ExecutionResult $result
   *   The execution result.
   * @param string $message
   *   Optional assertion message.
   */
  protected function assertExecutionResultSuccess(ExecutionResult $result, string $message = ''): void {
    $message = $message ?: 'Execution should succeed.';
    $errors = $result->getErrors();

    if (!empty($errors)) {
      $message .= ' Errors: ' . implode('; ', $errors);
    }

    $this->assertTrue($result->isSuccess(), $message);
  }

  /**
   * Asserts that execution failed.
   *
   * @param \Drupal\eb\Result\ExecutionResult $result
   *   The execution result.
   * @param string $message
   *   Optional assertion message.
   */
  protected function assertExecutionResultFailure(ExecutionResult $result, string $message = ''): void {
    $message = $message ?: 'Execution should fail.';
    $this->assertFalse($result->isSuccess(), $message);
  }

  /**
   * Asserts that execution result contains rollback data.
   *
   * @param \Drupal\eb\Result\ExecutionResult $result
   *   The execution result.
   * @param string $message
   *   Optional assertion message.
   */
  protected function assertExecutionHasRollbackData(ExecutionResult $result, string $message = ''): void {
    $message = $message ?: 'Execution result should contain rollback data.';
    $this->assertNotEmpty($result->getRollbackData(), $message);
  }

  /**
   * Asserts that execution result has affected entities.
   *
   * @param \Drupal\eb\Result\ExecutionResult $result
   *   The execution result.
   * @param string $message
   *   Optional assertion message.
   */
  protected function assertExecutionHasAffectedEntities(ExecutionResult $result, string $message = ''): void {
    $message = $message ?: 'Execution result should have affected entities.';
    $this->assertNotEmpty($result->getAffectedEntities(), $message);
  }

  /**
   * Asserts that rollback was successful.
   *
   * @param \Drupal\eb\Result\RollbackResult $result
   *   The rollback result.
   * @param string $message
   *   Optional assertion message.
   */
  protected function assertRollbackResultSuccess(RollbackResult $result, string $message = ''): void {
    $message = $message ?: 'Rollback should succeed.';
    $this->assertTrue($result->isSuccess(), $message);
  }

  /**
   * Asserts that rollback failed.
   *
   * @param \Drupal\eb\Result\RollbackResult $result
   *   The rollback result.
   * @param string $message
   *   Optional assertion message.
   */
  protected function assertRollbackResultFailure(RollbackResult $result, string $message = ''): void {
    $message = $message ?: 'Rollback should fail.';
    $this->assertFalse($result->isSuccess(), $message);
  }

  /**
   * Asserts that preview result has operations (created, modified, or deleted).
   *
   * @param \Drupal\eb\Result\PreviewResult $result
   *   The preview result.
   * @param string $message
   *   Optional assertion message.
   */
  protected function assertPreviewHasOperations(PreviewResult $result, string $message = ''): void {
    $message = $message ?: 'Preview should have operations.';
    $hasOperations = !empty($result->getCreatedEntities())
      || !empty($result->getModifiedEntities())
      || !empty($result->getDeletedEntities());
    $this->assertTrue($hasOperations, $message);
  }

  /**
   * Asserts that preview contains a specific operation type.
   *
   * @param \Drupal\eb\Result\PreviewResult $result
   *   The preview result.
   * @param string $operationType
   *   The operation type (create, update, delete).
   * @param string $message
   *   Optional assertion message.
   */
  protected function assertPreviewHasOperationType(PreviewResult $result, string $operationType, string $message = ''): void {
    $message = $message ?: "Preview should contain '$operationType' operation.";

    // Use getOperationType() which checks created/modified/deleted entities.
    $actualType = $result->getOperationType();
    $this->assertEquals($operationType, $actualType, $message);
  }

  /**
   * Asserts that preview has details (description contains information).
   *
   * @param \Drupal\eb\Result\PreviewResult $result
   *   The preview result.
   * @param string $message
   *   Optional assertion message.
   */
  protected function assertPreviewHasDetails(PreviewResult $result, string $message = ''): void {
    $message = $message ?: 'Preview should have details.';
    // Details are added to description via addDetails().
    $this->assertNotEmpty($result->getDescription(), $message);
  }

}
