<?php

namespace FlowSystems\WebhookActions\Services\Ai;

defined('ABSPATH') || exit;

use FlowSystems\WebhookActions\Repositories\CredentialRepository;
use FlowSystems\WebhookActions\Services\CredentialCipher;
use WP_Error;

/**
 * Bring-your-own-key transport for Anthropic's Messages API.
 *
 * Used when the WordPress AI Client is not configured. The API key is read from
 * the credentials vault (decrypted at call time only) and never leaves the server
 * or appears in a response. This is the free-tier fallback; Pro routes through our
 * hosted backend instead and needs no key.
 */
class AnthropicTransport implements LlmTransportInterface {
  private const ENDPOINT        = 'https://api.anthropic.com/v1/messages';
  private const API_VERSION     = '2023-06-01';
  public  const DEFAULT_MODEL   = 'claude-sonnet-4-6';

  private int    $credentialId;
  private string $model;

  /** @var array<string, mixed>|null */
  private ?array $lastRequest = null;

  /** @var array<string, mixed> */
  private array $lastResponseMeta = [];

  public function __construct(int $credentialId, string $model = self::DEFAULT_MODEL) {
    $this->credentialId = $credentialId;
    $this->model        = $model !== '' ? $model : self::DEFAULT_MODEL;
  }

  public function generateText(string $system, array $messages, array $options = []): string|WP_Error {
    $key = $this->resolveKey();
    if (is_wp_error($key)) {
      return $key;
    }

    $this->lastResponseMeta = [];

    $body = [
      'model'      => $options['model'] ?? $this->model,
      'max_tokens' => (int) ($options['max_tokens'] ?? 4096),
      'messages'   => $this->normalizeMessages($messages),
    ];
    if ($system !== '') {
      $body['system'] = $system;
    }
    if (isset($options['temperature'])) {
      $body['temperature'] = (float) $options['temperature'];
    }

    $this->lastRequest = [
      'endpoint' => self::ENDPOINT,
      'headers'  => [
        'x-api-key'         => '[redacted]',
        'anthropic-version' => self::API_VERSION,
        'content-type'      => 'application/json',
      ],
      'body'     => $body,
    ];

    $response = wp_remote_post(self::ENDPOINT, [
      // Big agent prompts (system + replayed read results) can push slow providers
      // past 60s (field trace: cURL 28 on the final round of a read loop).
      'timeout' => (int) apply_filters('fswa_ai_http_timeout', 120),
      'headers' => [
        'x-api-key'         => $key,
        'anthropic-version' => self::API_VERSION,
        'content-type'      => 'application/json',
      ],
      'body'    => wp_json_encode($body),
    ]);

    if (is_wp_error($response)) {
      return $response;
    }

    $code = (int) wp_remote_retrieve_response_code($response);
    $data = json_decode((string) wp_remote_retrieve_body($response), true);

    if ($code < 200 || $code >= 300) {
      $message = $data['error']['message'] ?? __('The AI provider returned an error.', 'flowsystems-webhook-actions');
      return new WP_Error('fswa_anthropic_error', $message, ['status' => $code]);
    }

    $stop                   = $data['stop_reason'] ?? null;
    $this->lastResponseMeta = is_string($stop) && $stop !== '' ? ['finish_reason' => $stop] : [];

    // Concatenate any text blocks in the content array.
    $text = '';
    foreach ((array) ($data['content'] ?? []) as $block) {
      if (($block['type'] ?? '') === 'text') {
        $text .= (string) ($block['text'] ?? '');
      }
    }

    return $text !== '' ? $text : new WP_Error('fswa_anthropic_empty', __('The AI provider returned no text.', 'flowsystems-webhook-actions'));
  }

  public function id(): string {
    return 'anthropic_byok';
  }

  public function model(): string {
    return $this->model;
  }

  public function lastRequest(): ?array {
    return $this->lastRequest;
  }

  public function lastResponseMeta(): array {
    return $this->lastResponseMeta;
  }

  /**
   * Decrypt the vault-stored API key for this transport.
   *
   * @return string|WP_Error
   */
  private function resolveKey(): string|WP_Error {
    $row = (new CredentialRepository())->findWithSecret($this->credentialId);
    if (!$row) {
      return new WP_Error('fswa_ai_key_missing', __('The configured AI provider key was not found in the vault.', 'flowsystems-webhook-actions'));
    }
    $key = (new CredentialCipher())->decrypt((string) ($row['secret_ciphertext'] ?? ''));
    if ($key === null || $key === '') {
      return new WP_Error('fswa_ai_key_undecryptable', __('The AI provider key could not be decrypted.', 'flowsystems-webhook-actions'));
    }
    return $key;
  }

  /**
   * Coerce transcript turns into the Messages API shape (role + string content).
   *
   * @param array<int, array{role:string,content:string}> $messages
   * @return array<int, array{role:string,content:string}>
   */
  private function normalizeMessages(array $messages): array {
    $out = [];
    foreach ($messages as $message) {
      $role = ($message['role'] ?? 'user') === 'assistant' ? 'assistant' : 'user';
      $out[] = ['role' => $role, 'content' => (string) ($message['content'] ?? '')];
    }
    return $out;
  }
}
