<?php

namespace Alexr\Whatsapp\Providers;

use Alexr\Whatsapp\DTO\NormalizedMessage;
use Alexr\Whatsapp\DTO\NormalizedStatusUpdate;
use Alexr\Whatsapp\DTO\NormalizedTemplate;
use Alexr\Whatsapp\DTO\SendMessageRequest;
use Alexr\Whatsapp\DTO\SendTemplateRequest;



// NOTE: Twilio SDK classes are NOT imported via `use` statements.
// The SDK is loaded lazily via require_once in loadSdk() to avoid
// loading the entire SDK on every WordPress page load.
// All Twilio classes are referenced by their FQCN: \Twilio\Rest\Client, etc.

/**
 * Class TwilioProvider
 *
 * WhatsApp provider implementation for Twilio using the official Twilio PHP SDK.
 * The SDK is lazy-loaded only when the provider is actually initialized.
 *
 * @package Alexr\Whatsapp\Providers
 */
class TwilioProvider extends AbstractWhatsappProvider
{
	public const PROVIDER_NAME = 'twilio';

	/**
	 * Twilio Account SID
	 */
	protected string $accountSid = '';

	/**
	 * Twilio Auth Token
	 */
	protected string $authToken = '';

	/**
	 * Twilio WhatsApp Phone Number
	 */
	protected string $phoneNumber = '';

	/**
	 * Whether the Twilio SDK has been loaded
	 */
	protected bool $sdkLoaded = false;

	/**
	 * @inheritDoc
	 */
	public function getProviderName(): string
	{
		return self::PROVIDER_NAME;
	}

	/**
	 * @inheritDoc
	 */
	protected function loadProviderConfig(): void
	{
		$this->accountSid = $this->config['twilio_sid'] ?? '';
		$this->authToken = $this->config['twilio_token'] ?? '';
		$this->phoneNumber = $this->config['twilio_phone'] ?? '';
	}

	/**
	 * Load the Twilio SDK if not already loaded.
	 * This is the key to lazy loading: the SDK autoloader is only
	 * require'd when we actually need to use Twilio classes.
	 */
	protected function loadSdk(): void
	{
		if ($this->sdkLoaded) {
			return;
		}

		if (!defined('ALEXR_PRO_PLUGIN_DIR')) {
			return;
		}

		$autoloadPath = ALEXR_PRO_PLUGIN_DIR . 'vendors/twilio/vendor/autoload.php';

		if (file_exists($autoloadPath)) {
			require_once $autoloadPath;
			$this->sdkLoaded = true;
		}
	}

	/**
	 * @inheritDoc
	 *
	 * Lazy-loads the Twilio SDK and initializes the client.
	 */
	protected function initClient(): void
	{
		if (empty($this->accountSid) || empty($this->authToken)) {
			return;
		}

		$this->loadSdk();

		if (!$this->sdkLoaded) {
			return;
		}

		$this->client = new \Twilio\Rest\Client($this->accountSid, $this->authToken);
	}

	/**
	 * Ensure the SDK client is available, loading it if necessary.
	 *
	 * @return bool
	 */
	protected function ensureClient(): bool
	{
		if ($this->client !== null) {
			return true;
		}

		// Retry initialization (maybe SDK wasn't available during construct)
		$this->initClient();

		return $this->client !== null;
	}

	/**
	 * @inheritDoc
	 */
	public function isConfigured(): bool
	{
		if (!$this->configModel) {
			return false;
		}

		if (empty($this->config['enabled'])) {
			return false;
		}

		return !empty($this->accountSid) && !empty($this->authToken) && !empty($this->phoneNumber);
	}

	/**
	 * @inheritDoc
	 */
	public function testConnection(): array
	{
		if (!$this->isConfigured()) {
			return $this->errorResponse(__eva('WhatsApp service is not configured'));
		}

		if (!$this->ensureClient()) {
			return $this->errorResponse(__eva('Twilio SDK not available. Please activate the PRO plugin.'));
		}

		try {
			$this->client->content->v1->contents->read([], 1);

			return $this->successResponse(__eva('Connection successful'));

		} catch (\Twilio\Exceptions\TwilioException $e) {
			$code = $e->getCode();

			if ($code === 20003 || $code === 401) {
				return $this->errorResponse(__eva('Invalid Twilio credentials (Account SID or Auth Token)'));
			}

			return $this->errorResponse($e->getMessage());
		}
	}

	/**
	 * @inheritDoc
	 */
	public function sendMessage(SendMessageRequest $request): array
	{
		if (!$this->isConfigured()) {
			return $this->errorResponse(__eva('WhatsApp service is not configured'));
		}

		if (!$this->ensureClient()) {
			return $this->errorResponse(__eva('Twilio SDK not available'));
		}

		$toPhone = $this->formatPhoneNumber($request->toPhone);

		try {
			$params = [
				'from' => 'whatsapp:' . $this->phoneNumber,
				'body' => $request->body,
			];

			if ($request->mediaUrl) {
				$params['mediaUrl'] = [$request->mediaUrl];
			}

			$message = $this->client->messages->create(
				'whatsapp:' . $toPhone,
				$params
			);

			return $this->successResponse(__eva('Message sent successfully'), [
				'message_id' => $message->sid,
				'status' => $message->status,
				'date_created' => $message->dateCreated ? $message->dateCreated->format('c') : null,
			]);

		} catch (\Twilio\Exceptions\TwilioException $e) {
			$this->logError('Send message failed', ['error' => $e->getMessage()]);
			return $this->errorResponse($e->getMessage());
		}
	}

	/**
	 * @inheritDoc
	 */
	public function sendTemplate(SendTemplateRequest $request): array
	{
		if (!$this->isConfigured()) {
			return $this->errorResponse(__eva('WhatsApp service is not configured'));
		}

		if (!$this->ensureClient()) {
			return $this->errorResponse(__eva('Twilio SDK not available'));
		}

		$toPhone = $this->formatPhoneNumber($request->toPhone);

		try {
			$params = [
				'from' => 'whatsapp:' . $this->phoneNumber,
				'contentSid' => $request->templateId,
			];

			$variables = $request->variables;
			if (!empty($variables)) {
				$params['contentVariables'] = json_encode($variables);
			}

			$message = $this->client->messages->create(
				'whatsapp:' . $toPhone,
				$params
			);

			return $this->successResponse(__eva('Template message sent successfully'), [
				'message_id' => $message->sid,
				'status' => $message->status,
				'date_created' => $message->dateCreated ? $message->dateCreated->format('c') : null,
			]);

		} catch (\Twilio\Exceptions\TwilioException $e) {
			$this->logError('Send template failed', ['error' => $e->getMessage()]);
			return $this->errorResponse($e->getMessage());
		}
	}

	/**
	 * Format template variables for Twilio
	 * Twilio uses numeric string keys: "1", "2", "3", etc.
	 *
	 * @param SendTemplateRequest $request
	 * @return array
	 */
	protected function formatTemplateVariables(SendTemplateRequest $request): array
	{
		$variables = $request->variables;

		// If already in Twilio format (numeric keys), return as-is
		if (!empty($variables) && isset($variables['1'])) {
			return $variables;
		}

		// If booking data format, convert to Twilio format
		if (isset($variables['customer_name']) || isset($variables['date'])) {
			$formatted = [];
			$index = 1;

			$mappings = [
				'customer_name',
				'restaurant_name' => $this->restaurantName,
				'date' => isset($variables['date']) ? $this->formatDate($variables['date']) : '',
				'time' => isset($variables['time']) ? $this->formatTime($variables['time']) : '',
				'party',
				'restaurant_phone' => $this->restaurantPhone,
			];

			foreach ($mappings as $key => $default) {
				if (is_int($key)) {
					$key = $default;
					$default = '';
				}

				$value = $variables[$key] ?? $default;
				if (!empty($value)) {
					$formatted[(string)$index] = (string)$value;
					$index++;
				}
			}

			// Add any extra variables
			foreach ($variables as $key => $value) {
				if (!in_array($key, ['customer_name', 'restaurant_name', 'date', 'time', 'party', 'restaurant_phone'])) {
					if (!empty($value)) {
						$formatted[(string)$index] = (string)$value;
						$index++;
					}
				}
			}

			return $formatted;
		}

		// Generic array, convert to numeric keys
		$formatted = [];
		$index = 1;
		foreach ($variables as $value) {
			$formatted[(string)$index] = (string)$value;
			$index++;
		}

		return $formatted;
	}

	/**
	 * @inheritDoc
	 *
	 * Uses the ContentAndApprovals endpoint to fetch templates WITH their
	 * approval status in a single API call, instead of N+1 calls.
	 */
	public function getTemplates(int $pageSize = 50, ?string $cursor = null): array
	{
		if (!$this->isConfigured()) {
			return $this->errorResponse(__eva('WhatsApp service is not configured'));
		}

		if (!$this->ensureClient()) {
			return $this->errorResponse(__eva('Twilio SDK not available'));
		}

		try {
			// ContentAndApprovals returns templates WITH approval status in one call
			// No more N+1 queries to fetch status individually per template!
			$contentAndApprovals = $this->client->content->v1->contentAndApprovals->read(
				min($pageSize, 50)
			);

			$templates = [];

			foreach ($contentAndApprovals as $item) {
				$template = $this->normalizeTemplateFromSdk($item);
				$templates[] = $template->toArray();
			}

			return $this->successResponse(__eva('Templates fetched successfully'), [
				'templates' => $templates,
				'total' => count($templates),
				'meta' => [
					'next_page_url' => null,
					'previous_page_url' => null,
				],
			]);

		} catch (\Twilio\Exceptions\TwilioException $e) {
			return $this->errorResponse($e->getMessage());
		}
	}

	/**
	 * @inheritDoc
	 */
	public function getTemplate(string $templateId): array
	{
		if (!$this->isConfigured()) {
			return $this->errorResponse(__eva('WhatsApp service is not configured'));
		}

		if (!$this->ensureClient()) {
			return $this->errorResponse(__eva('Twilio SDK not available'));
		}

		try {
			$content = $this->client->content->v1->contents($templateId)->fetch();

			$template = $this->normalizeTemplateFromSdk($content);

			// Fetch approval status for individual template
			try {
				$approval = $this->client->content->v1
					->contents($templateId)
					->approvalFetch()
					->fetch();

				$whatsappApproval = $approval->whatsapp ?? [];

				$template->approvalStatus = $whatsappApproval['status'] ?? NormalizedTemplate::STATUS_UNKNOWN;
				$template->rejectionReason = $whatsappApproval['rejection_reason'] ?? null;
				$template->category = $whatsappApproval['category'] ?? null;
			} catch (\Twilio\Exceptions\TwilioException $e) {
				$template->approvalStatus = NormalizedTemplate::STATUS_UNKNOWN;
			}

			return $this->successResponse(__eva('Template fetched successfully'), [
				'template' => $template->toArray(),
			]);

		} catch (\Twilio\Exceptions\TwilioException $e) {
			return $this->errorResponse($e->getMessage());
		}
	}

	/**
	 * Normalize a template from SDK ContentInstance or ContentAndApprovalsInstance
	 *
	 * @param mixed $content SDK instance
	 * @return NormalizedTemplate
	 */
	protected function normalizeTemplateFromSdk($content): NormalizedTemplate
	{
		$template = new NormalizedTemplate(
			$content->sid,
			$content->friendlyName ?? '',
			self::PROVIDER_NAME
		);

		$template->language = $content->language ?? null;

		$dateCreated = $content->dateCreated ?? null;
		$dateUpdated = $content->dateUpdated ?? null;
		$template->dateCreated = $dateCreated instanceof \DateTime ? $dateCreated->format('c') : $dateCreated;
		$template->dateUpdated = $dateUpdated instanceof \DateTime ? $dateUpdated->format('c') : $dateUpdated;

		$template->variables = $content->variables ?? [];

		// ContentAndApprovals includes approval status directly in the response
		if (isset($content->approvalRequests)) {
			$whatsappApproval = $content->approvalRequests['whatsapp'] ?? [];
			$template->approvalStatus = $whatsappApproval['status'] ?? NormalizedTemplate::STATUS_UNKNOWN;
			$template->rejectionReason = $whatsappApproval['rejection_reason'] ?? null;
			$template->category = $whatsappApproval['category'] ?? null;
		}

		$rawData = [
			'sid' => $content->sid,
			'friendly_name' => $content->friendlyName ?? '',
			'language' => $content->language ?? null,
			'variables' => $content->variables ?? [],
			'types' => $content->types ?? [],
		];

		$template->rawData = $rawData;

		$types = $content->types ?? [];

		if (isset($types['twilio/text'])) {
			$template->type = NormalizedTemplate::TYPE_TEXT;
			$template->body = $types['twilio/text']['body'] ?? '';
		} elseif (isset($types['twilio/media'])) {
			$template->type = NormalizedTemplate::TYPE_MEDIA;
			$template->body = $types['twilio/media']['body'] ?? '';
			$template->mediaUrl = $types['twilio/media']['media'][0] ?? null;
		} elseif (isset($types['twilio/call-to-action'])) {
			$template->type = NormalizedTemplate::TYPE_CALL_TO_ACTION;
			$template->body = $types['twilio/call-to-action']['body'] ?? '';
			$template->buttons = $this->normalizeButtons($types['twilio/call-to-action']['actions'] ?? []);
		} elseif (isset($types['twilio/quick-reply'])) {
			$template->type = NormalizedTemplate::TYPE_QUICK_REPLY;
			$template->body = $types['twilio/quick-reply']['body'] ?? '';
			$template->buttons = $this->normalizeButtons($types['twilio/quick-reply']['actions'] ?? []);
		} elseif (isset($types['twilio/card'])) {
			$template->type = NormalizedTemplate::TYPE_CARD;
			$template->body = $types['twilio/card']['body'] ?? '';
			$template->header = $types['twilio/card']['title'] ?? null;
			$template->mediaUrl = $types['twilio/card']['media'][0] ?? null;
			$template->buttons = $this->normalizeButtons($types['twilio/card']['actions'] ?? []);
		} elseif (isset($types['twilio/list-picker'])) {
			$template->type = NormalizedTemplate::TYPE_LIST;
			$template->body = $types['twilio/list-picker']['body'] ?? '';
			$template->buttons = $types['twilio/list-picker']['items'] ?? [];
		}

		return $template;
	}

	/**
	 * Normalize button actions
	 *
	 * @param array $actions
	 * @return array
	 */
	protected function normalizeButtons(array $actions): array
	{
		$buttons = [];
		foreach ($actions as $action) {
			$buttons[] = [
				'type' => $action['type'] ?? 'unknown',
				'text' => $action['title'] ?? $action['text'] ?? '',
				'url' => $action['url'] ?? null,
				'payload' => $action['id'] ?? null,
				'phone' => $action['phone'] ?? null,
			];
		}
		return $buttons;
	}

	/**
	 * @inheritDoc
	 */
	public function getMessageStatus(string $messageId): array
	{
		if (!$this->isConfigured()) {
			return $this->errorResponse(__eva('WhatsApp service is not configured'));
		}

		if (!$this->ensureClient()) {
			return $this->errorResponse(__eva('Twilio SDK not available'));
		}

		try {
			$message = $this->client->messages($messageId)->fetch();

			return $this->successResponse(__eva('Status fetched successfully'), [
				'message_id' => $message->sid,
				'status' => $message->status,
				'error_code' => $message->errorCode,
				'error_message' => $message->errorMessage,
				'date_created' => $message->dateCreated ? $message->dateCreated->format('c') : null,
				'date_sent' => $message->dateSent ? $message->dateSent->format('c') : null,
				'date_updated' => $message->dateUpdated ? $message->dateUpdated->format('c') : null,
			]);

		} catch (\Twilio\Exceptions\TwilioException $e) {
			return $this->errorResponse($e->getMessage());
		}
	}

	/**
	 * @inheritDoc
	 */
	public function parseIncomingWebhook(array $payload): NormalizedMessage
	{
		$message = new NormalizedMessage(self::PROVIDER_NAME);

		$from = $payload['From'] ?? '';
		$to = $payload['To'] ?? '';

		$message->fromPhone = str_replace('whatsapp:', '', $from);
		$message->toPhone = str_replace('whatsapp:', '', $to);

		$message->messageId = $payload['MessageSid'] ?? null;
		$message->accountId = $payload['AccountSid'] ?? null;

		$message->profileName = $payload['ProfileName'] ?? null;
		$message->waId = $payload['WaId'] ?? null;

		$message->body = $payload['Body'] ?? '';

		$buttonPayload = $payload['ButtonPayload'] ?? null;
		$buttonText = $payload['ButtonText'] ?? null;
		$message->isButtonResponse = !empty($buttonPayload) || !empty($buttonText);
		$message->buttonPayload = $buttonPayload;
		$message->buttonText = $buttonText ?? ($message->isButtonResponse ? $message->body : null);

		$message->originalMessageId = $payload['OriginalRepliedMessageSid'] ?? null;
		$message->isReply = !empty($message->originalMessageId);

		$message->mediaCount = (int)($payload['NumMedia'] ?? 0);
		$message->media = $this->extractMedia($payload);

		if (isset($payload['Latitude'])) {
			$message->latitude = floatval($payload['Latitude']);
			$message->longitude = floatval($payload['Longitude'] ?? 0);
			$message->locationAddress = $payload['Address'] ?? null;
		}

		$message->type = $message->getMessageType();
		$message->rawPayload = $payload;

		return $message;
	}

	/**
	 * Extract media from Twilio payload
	 *
	 * @param array $payload
	 * @return array
	 */
	protected function extractMedia(array $payload): array
	{
		$media = [];
		$numMedia = (int)($payload['NumMedia'] ?? 0);

		for ($i = 0; $i < $numMedia; $i++) {
			$mediaUrl = $payload["MediaUrl{$i}"] ?? null;
			$mediaType = $payload["MediaContentType{$i}"] ?? null;

			if ($mediaUrl) {
				$media[] = [
					'url' => $mediaUrl,
					'content_type' => $mediaType,
					'id' => null,
				];
			}
		}

		return $media;
	}

	/**
	 * @inheritDoc
	 */
	public function parseStatusCallback(array $payload): NormalizedStatusUpdate
	{
		$status = new NormalizedStatusUpdate(self::PROVIDER_NAME);

		$status->messageId = $payload['MessageSid'] ?? null;
		$status->accountId = $payload['AccountSid'] ?? null;
		$status->rawStatus = $payload['MessageStatus'] ?? null;
		$status->status = NormalizedStatusUpdate::normalizeStatus(
			$status->rawStatus ?? '',
			self::PROVIDER_NAME
		);

		$status->toPhone = str_replace('whatsapp:', '', $payload['To'] ?? '');
		$status->fromPhone = str_replace('whatsapp:', '', $payload['From'] ?? '');

		$status->errorCode = $payload['ErrorCode'] ?? null;
		$status->errorMessage = $payload['ErrorMessage'] ?? null;

		$status->rawPayload = $payload;

		return $status;
	}

	/**
	 * @inheritDoc
	 *
	 * Uses Twilio SDK's RequestValidator for proper signature validation.
	 * Falls back to manual validation if SDK is not loaded (e.g. webhook
	 * handlers that don't need the full SDK).
	 */
	public function validateWebhookSignature(string $signature, string $url, array $payload): bool
	{
		if (empty($this->authToken)) {
			return false;
		}

		// Try to use SDK's RequestValidator
		$this->loadSdk();

		if ($this->sdkLoaded) {
			$validator = new \Twilio\Security\RequestValidator($this->authToken);
			return $validator->validate($signature, $url, $payload);
		}

		// Fallback: manual validation (same logic as SDK, kept for safety)
		ksort($payload);

		$data = $url;
		foreach ($payload as $key => $value) {
			$data .= $key . $value;
		}

		$expectedSignature = base64_encode(hash_hmac('sha1', $data, $this->authToken, true));

		return hash_equals($expectedSignature, $signature);
	}

	/**
	 * @inheritDoc
	 *
	 * Uses Twilio SDK's MessagingResponse TwiML helper if available,
	 * falls back to manual XML construction.
	 */
	public function generateWebhookResponse(?string $replyMessage = null): string
	{
		if ($this->sdkLoaded) {
			$response = new \Twilio\TwiML\MessagingResponse();
			if ($replyMessage) {
				$response->message($replyMessage);
			}
			return (string) $response;
		}

		// Fallback to manual XML
		if ($replyMessage) {
			return '<?xml version="1.0" encoding="UTF-8"?>' .
			       '<Response><Message>' . htmlspecialchars($replyMessage) . '</Message></Response>';
		}

		return '<?xml version="1.0" encoding="UTF-8"?><Response></Response>';
	}

	/**
	 * @inheritDoc
	 */
	public function getWebhookResponseContentType(): string
	{
		return 'text/xml';
	}

	/**
	 * @inheritDoc
	 */
	public function getIncomingWebhookUrl(): string
	{
		return $this->getWebhookBaseUrl() . '/wp-admin/admin-ajax.php?action=alexr_whatsapp_webhook&restaurant_id=' . $this->restaurantId;
	}

	/**
	 * @inheritDoc
	 */
	public function getStatusCallbackUrl(): string
	{
		return $this->getWebhookBaseUrl() . '/wp-admin/admin-ajax.php?action=alexr_whatsapp_status_callback&restaurant_id=' . $this->restaurantId;
	}

	/**
	 * @inheritDoc
	 */
	public function getPhoneNumber(): string
	{
		return $this->phoneNumber;
	}

	/**
	 * @inheritDoc
	 */
	public function getConfig(): array
	{
		return [
			'provider' => self::PROVIDER_NAME,
			'enabled' => $this->isConfigured(),
			'restaurant_name' => $this->restaurantName,
			'restaurant_phone' => $this->restaurantPhone,
			'phone_number' => $this->phoneNumber,
			'date_format' => $this->dateFormat,
			'time_format' => $this->timeFormat,
		];
	}

	/**
	 * @inheritDoc
	 */
	public function isStatusCallback(array $payload): bool
	{
		return isset($payload['MessageStatus']);
	}

	/**
	 * @inheritDoc
	 */
	public function isIncomingMessage(array $payload): bool
	{
		return isset($payload['Body']) || isset($payload['ButtonPayload']) || isset($payload['NumMedia']);
	}
}
