<?php

declare(strict_types=1);

namespace FormConnector\Integration\Webhook;

use FormConnector\Integration\CareHQ\Signing;
use FormConnector\Features\License\License;

/**
 * CareHQ Webhook Receiver
 *
 * Exposes `POST /wp-json/carehq/v1/webhook` and verifies inbound CareHQ
 * deliveries using the per-site signing secret. Recognised events update
 * locally-cached state so the admin UI can react (banners, gating, etc.).
 *
 * @see two-way.md
 */
class WebhookHandler {

	private const NAMESPACE = 'carehq/v1';
	private const ROUTE     = '/webhook';

	/**
	 * Bind WordPress hooks. Safe to call multiple times.
	 */
	public static function register_hooks(): void {
		add_action('rest_api_init', [self::class, 'register_routes']);
	}

	/**
	 * Register the webhook REST route.
	 */
	public static function register_routes(): void {
		register_rest_route(self::NAMESPACE, self::ROUTE, [
			'methods'             => \WP_REST_Server::CREATABLE,
			'callback'            => [self::class, 'handle'],
			'permission_callback' => [self::class, 'verify'],
		]);
	}

	/**
	 * Permission callback — verifies the HMAC signature on the request.
	 *
	 * @return true|\WP_Error
	 */
	public static function verify(\WP_REST_Request $request): bool|\WP_Error {
		return Signing::verify_inbound($request);
	}

	/**
	 * Process a verified CareHQ webhook event.
	 */
	public static function handle(\WP_REST_Request $request): \WP_REST_Response {
		$event = json_decode($request->get_body(), true);
		if (!is_array($event)) {
			return new \WP_REST_Response([
				'success' => false,
				'message' => __('Invalid webhook payload.', 'form-connector'),
			], 400);
		}

		$type = isset($event['type']) ? (string) $event['type'] : '';
		$data = $event['data'] ?? [];

		// Persist a generic record of the most recent event for diagnostics.
		update_option(Signing::OPTION_LAST_WEBHOOK, [
			'id'         => $event['id'] ?? null,
			'type'       => $type,
			'created_at' => $event['createdAt'] ?? gmdate('c'),
			'received'   => gmdate('c'),
		], false);

		switch ($type) {
			case 'home.over_trial_limit':
				update_option(Signing::OPTION_ENTITLEMENT_BANNER, $data, false);
				break;

			case 'subscription.downgraded':
				update_option(Signing::OPTION_SUBSCRIPTION_STATE, $data, false);
				// Force a license re-check so plan/seat info reflects the change.
				(new License())->clear_validation_cache();
				delete_transient('fc_homes_cache');
				break;

			case 'admin_user.added':
				update_option(Signing::OPTION_ADMIN_USER_EVENT, $data, false);
				break;

			case 'plugin.update_available':
				update_option(Signing::OPTION_PLUGIN_UPDATE, self::normalize_plugin_update($data), false);
				break;

			case 'contact.status_changed':
			case 'lead.status_changed':
				self::handle_lead_status_update($data);
				break;

			default:
				// Allow other listeners to handle unknown event types.
				break;
		}

		/**
		 * Fires after a verified CareHQ webhook event has been stored.
		 *
		 * @param string $type  Event type identifier.
		 * @param array  $data  Event payload.
		 * @param array  $event Full event envelope.
		 */
		do_action('fc_carehq_webhook_received', $type, $data, $event);

		return new \WP_REST_Response(['success' => true], 200);
	}

	/**
	 * Normalize update webhook payloads to the same shape as health-ping status.
	 */
	private static function normalize_plugin_update(mixed $data): array {
		$data = is_array($data) ? $data : [];

		return [
			'current'         => sanitize_text_field((string) ($data['current'] ?? (defined('FC_VERSION') ? FC_VERSION : '0.0.0'))),
			'latest'          => sanitize_text_field((string) ($data['latest'] ?? $data['latestVersion'] ?? '')),
			'minimum'         => sanitize_text_field((string) ($data['minimum'] ?? $data['minimumVersion'] ?? '')),
			'updateAvailable' => (bool) ($data['updateAvailable'] ?? true),
			'updateRequired'  => (bool) ($data['updateRequired'] ?? $data['required'] ?? false),
			'checkedAt'       => gmdate('c'),
		];
	}

	/**
	 * Handle inbound lead/contact status updates from CareHQ.
	 *
	 * @param array $data Webhook payload data with contact_id, status, and optional value
	 */
	private static function handle_lead_status_update(array $data): void {
		$contact_id = sanitize_text_field((string) ($data['contact_id'] ?? $data['_id'] ?? ''));
		$status     = sanitize_text_field((string) ($data['status'] ?? $data['lead_status'] ?? ''));
		$value      = isset($data['value']) && $data['value'] !== null
			? (float) $data['value']
			: (isset($data['lead_value']) && $data['lead_value'] !== null
				? (float) $data['lead_value']
				: null);

		if (empty($contact_id) || empty($status)) {
			return;
		}

		// Map CareHQ status values to our enum
		$status_map = [
			'new'       => 'new',
			'qualified' => 'qualified',
			'won'       => 'won',
			'lost'      => 'lost',
			'open'      => 'new',
			'closed'    => 'won',
		];

		$mapped_status = $status_map[strtolower($status)] ?? 'unknown';

		\FormConnector\Features\Attribution\AttributionTracker::get_instance()
			->update_lead_status($contact_id, $mapped_status, $value);
	}
}
