<?php

/**
 * Webhook handling for Hypodash.
 */
class Hypodash_Webhook
{

	/**
	 * Plugin identifier.
	 *
	 * @var string
	 */
	private $plugin_name;

	/**
	 * Plugin version.
	 *
	 * @var string
	 */
	private $version;

	/**
	 * Set up the class.
	 *
	 * @param string $plugin_name Plugin name.
	 * @param string $version     Plugin version.
	 */
	public function __construct($plugin_name, $version)
	{
		$this->plugin_name = $plugin_name;
		$this->version     = $version;
		$this->load_logger();
	}

	/**
	 * Load the logger utility.
	 */
	private function load_logger()
	{
		if (! class_exists('Hypodash_Logger')) {
			require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-hypodash-logger.php';
		}
	}

	/**
	 * Register REST routes.
	 */
	public function register_routes()
	{
		register_rest_route(
			HYPODASH_WEBHOOK_NAMESPACE,
			HYPODASH_WEBHOOK_ROUTE,
			array(
				'args'                => array(),
				'permission_callback' => '__return_true',
				'callback'            => array($this, 'handle_receive'),
				'methods'             => WP_REST_Server::CREATABLE,
			)
		);
	}

	public function handle_receive(WP_REST_Request $request)
	{
		$raw_body = (string) $request->get_body();
		$payload = $request->get_json_params();

		$secret = trim((string) get_option(HYPODASH_OPTION_WEBHOOK_SECRET, ''));
		if ($secret !== '') {
			$signature = $request->get_header('x-hypodash-signature');
			$signature_validation = $this->validate_signature($signature, $raw_body, $secret);
			if (is_wp_error($signature_validation)) {
				return $signature_validation;
			}
		}

		if (empty($payload) && ! empty($raw_body)) {
			$payload = json_decode($raw_body, true);
			Hypodash_Logger::debug('Parsed raw body as JSON', array('raw_body_length' => strlen($raw_body)));
		}

		Hypodash_Logger::info('Webhook received', array('payload' => $payload));

		$validation = $this->validate_payload($payload);
		if (is_wp_error($validation)) {
			Hypodash_Logger::error('Payload validation failed: ' . $validation->get_error_message());
			return $validation;
		}

		$result = $this->create_post_from_payload($payload);
		if (is_wp_error($result)) {
			Hypodash_Logger::error('Post creation failed: ' . $result->get_error_message());
			return $result;
		}

		Hypodash_Logger::info('Webhook processed successfully', array('post_id' => $result));

		return rest_ensure_response(
			array(
				'success' => true,
				'integration_id' => $result,
			)
		);
	}

	/**
	 * Validate that required keys exist.
	 *
	 * @param mixed $payload Payload array.
	 * @return true|WP_Error
	 */
	private function validate_payload($payload)
	{
		if (empty($payload) || ! is_array($payload)) {
			$error = new WP_Error('hypodash_invalid_payload', __('Payload must be a JSON object.', 'hypodash'), array('status' => 400));
			Hypodash_Logger::warning('Payload validation failed: invalid JSON or empty payload');
			return $error;
		}

		$required = array('name', 'output_html', 'publish_date');
		foreach ($required as $field) {
			if (empty($payload[$field])) {
				$error = new WP_Error(
					'hypodash_missing_field',
					/* translators: %s is the missing field name. */
					sprintf(__('Missing required field: %s', 'hypodash'), $field),
					array('status' => 400)
				);
				Hypodash_Logger::warning('Payload validation failed: missing field', array('field' => $field));
				return $error;
			}
		}

		return true;
	}

	/**
	 * Verify the webhook signature when a secret is configured.
	 *
	 * @param string|null $signature Header value.
	 * @param string      $body      Raw request body.
	 * @param string      $secret    Stored webhook secret.
	 * @return true|WP_Error
	 */
	private function validate_signature($signature, $body, $secret)
	{
		$signature = trim((string) $signature);
		if ($signature === '') {
			Hypodash_Logger::warning('Webhook signature header missing');
			return new WP_Error('hypodash_missing_signature', __('Missing webhook signature header.', 'hypodash'), array('status' => 403));
		}

		$normalized = $this->normalize_signature_header($signature);
		if ($normalized === '') {
			Hypodash_Logger::warning('Webhook signature header malformed', array('header' => $signature));
			return new WP_Error('hypodash_invalid_signature', __('Webhook signature header is malformed.', 'hypodash'), array('status' => 403));
		}

		$expected = hash_hmac('sha256', (string) $body, $secret);
		if (! hash_equals($expected, $normalized)) {
			Hypodash_Logger::warning('Webhook signature mismatch', array('expected' => $expected, 'received' => $normalized));
			return new WP_Error('hypodash_invalid_signature', __('Webhook signature did not match.', 'hypodash'), array('status' => 403));
		}

		Hypodash_Logger::debug('Webhook signature verified');
		return true;
	}

	/**
	 * Normalize the signature header value.
	 *
	 * Accepts signatures with or without the sha256= prefix and lowercases the hex output.
	 */
	private function normalize_signature_header($signature)
	{
		$value = trim((string) $signature);
		if (stripos($value, 'sha256=') === 0) {
			$value = substr($value, 7);
		}
		return strtolower($value);
	}

	/**
	 * Insert the post, download images, and rewrite HTML.
	 *
	 * @param array $payload Webhook payload.
	 * @return int|WP_Error Newly created post ID or error.
	 */
	private function create_post_from_payload(array $payload)
	{
		$publish_date = isset($payload['publish_date']) ? sanitize_text_field($payload['publish_date']) : '';
		$date_info = $this->parse_publish_date($publish_date);
		Hypodash_Logger::debug('Parsed publish date', $date_info);

		$post_content = wp_kses_post($payload['output_html']);
		$meta_description = isset($payload['meta_description']) ? sanitize_textarea_field($payload['meta_description']) : '';

		$post_args = array(
			'post_title'    => sanitize_text_field($payload['name']),
			'post_content'  => $post_content,
			'post_status'   => $date_info['post_status'],
			'post_type'     => 'post',
			'post_date'     => $date_info['post_date'],
			'post_date_gmt' => $date_info['post_date_gmt'],
		);
		if ($meta_description !== '') {
			$post_args['post_excerpt'] = $meta_description;
		}

		// If an admin has set a default category, apply it to the inserted post.
		$default_cat = get_option('hypodash_default_category', 0);
		$default_cat = absint($default_cat);
		if ($default_cat) {
			$post_args['post_category'] = array($default_cat);
		}

		$post_id = wp_insert_post($post_args, true);
		if (is_wp_error($post_id)) {
			Hypodash_Logger::error('Failed to insert post: ' . $post_id->get_error_message());
			return $post_id;
		}

		Hypodash_Logger::info('Post created', array('post_id' => $post_id, 'title' => $post_args['post_title']));

		$content_with_images = $this->attach_images_and_rewrite_html($payload, $post_id, $post_args['post_content']);
		if (is_wp_error($content_with_images)) {
			Hypodash_Logger::error('Failed to attach images: ' . $content_with_images->get_error_message());
			return $content_with_images;
		}

		if ($content_with_images !== $post_args['post_content']) {
			wp_update_post(
				array(
					'ID'           => $post_id,
					'post_content' => $content_with_images,
				)
			);
			Hypodash_Logger::debug('Post content updated with rewritten image URLs', array('post_id' => $post_id));
		}

		// If a featured_image was provided, download and attach it, then set as post thumbnail.
		if (! empty($payload['featured_image']) && is_array($payload['featured_image'])) {
			$fi = $payload['featured_image'];
			$fi_temp = isset($fi['temp_url']) ? esc_url_raw(trim($fi['temp_url'])) : '';
			$fi_heading = isset($fi['heading']) ? sanitize_text_field($fi['heading']) : '';

			if (! empty($fi_temp)) {
				$new_src = $this->download_and_attach_image($fi_temp, $post_id, $fi_heading);
				if (is_wp_error($new_src)) {
					Hypodash_Logger::warning('Failed to download featured image', array('error' => $new_src->get_error_message(), 'temp_url' => $fi_temp));
				} else {
					$attachment_id = attachment_url_to_postid($new_src);
					if ($attachment_id) {
						set_post_thumbnail($post_id, $attachment_id);
						Hypodash_Logger::info('Set featured image from payload', array('post_id' => $post_id, 'attachment_id' => $attachment_id));
					} else {
						Hypodash_Logger::warning('Could not resolve attachment ID for featured image URL', array('url' => $new_src));
					}
				}
			}
		}

		return $post_id;
	}

	/**
	 * Convert publish date to WordPress-friendly values.
	 *
	 * @param string $publish_date Date string from payload.
	 * @return array
	 */
	private function parse_publish_date($publish_date)
	{
		$timezone = wp_timezone();
		$timestamp = current_time('timestamp');

		try {
			$dt = DateTime::createFromFormat('Y-m-d H:i:s', $publish_date, $timezone);
			if (! $dt) {
				$dt = new DateTime($publish_date, $timezone);
			}
		} catch (Exception $e) {
			$dt = false;
		}

		if (! $dt) {
			$dt = new DateTime('@' . $timestamp);
			$dt->setTimezone($timezone);
		}

		$post_date     = $dt->format('Y-m-d H:i:s');
		$post_date_gmt = get_gmt_from_date($post_date);
		$post_status   = $dt->getTimestamp() > $timestamp ? 'future' : 'publish';

		return array(
			'post_date'     => $post_date,
			'post_date_gmt' => $post_date_gmt,
			'post_status'   => $post_status,
		);
	}

	/**
	 * Download images, attach them to the post, and rewrite HTML sources.
	 *
	 * @param array  $payload  Incoming payload.
	 * @param int    $post_id  WordPress post ID.
	 * @param string $html     Original HTML.
	 * @return string|WP_Error
	 */
	private function attach_images_and_rewrite_html(array $payload, $post_id, $html)
	{
		if (empty($payload['images']) || ! is_array($payload['images'])) {
			Hypodash_Logger::debug('No images in payload');
			return $html;
		}

		Hypodash_Logger::info('Attaching images to post', array('post_id' => $post_id, 'image_count' => count($payload['images'])));

		require_once ABSPATH . 'wp-admin/includes/file.php';
		require_once ABSPATH . 'wp-admin/includes/media.php';
		require_once ABSPATH . 'wp-admin/includes/image.php';

		$replacements   = array();
		$featured_set   = false;
		$failed_images  = 0;

		foreach ($payload['images'] as $index => $image) {
			$temp_url = isset($image['temp_url']) ? esc_url_raw(trim($image['temp_url'])) : '';
			$path     = isset($image['path']) ? $image['path'] : '';
			$heading  = isset($image['heading']) ? sanitize_text_field($image['heading']) : '';

			if (empty($temp_url)) {
				Hypodash_Logger::warning('Skipped image: no temp_url', array('image_index' => $index));
				continue;
			}

			Hypodash_Logger::debug('Attempting to download image', array('index' => $index, 'temp_url' => $temp_url, 'heading' => $heading));

			$new_src = $this->download_and_attach_image($temp_url, $post_id, $heading);
			if (is_wp_error($new_src)) {
				$failed_images++;
				Hypodash_Logger::warning('Failed to download image (continuing): ' . $new_src->get_error_message(), array('temp_url' => $temp_url, 'heading' => $heading));
				continue;
			}

			Hypodash_Logger::debug('Image downloaded and attached', array('index' => $index, 'temp_url' => $temp_url, 'new_src' => $new_src));

			$replacements[$temp_url] = $new_src;
			if (! empty($path)) {
				$replacements[$path] = $new_src;
			}

			$attachment_id = attachment_url_to_postid($new_src);
			if (! $featured_set && $attachment_id) {
				set_post_thumbnail($post_id, $attachment_id);
				$featured_set = true;
				Hypodash_Logger::debug('Set featured image', array('post_id' => $post_id, 'attachment_id' => $attachment_id));
			}
		}

		if (! empty($replacements)) {
			$html = str_replace(array_keys($replacements), array_values($replacements), $html);
			Hypodash_Logger::debug('Rewritten HTML image URLs', array('replacements' => count($replacements)));
		}

		if ($failed_images > 0) {
			Hypodash_Logger::warning('Some images could not be downloaded', array('failed_count' => $failed_images, 'successful_count' => count($replacements)));
		}

		return $html;
	}

	/**
	 * Download an image via WP HTTP API and attach to the post.
	 *
	 * @param string $url      Remote image URL.
	 * @param int    $post_id  Post to attach to.
	 * @param string $desc     Attachment description.
	 * @return string|WP_Error Attachment URL or error.
	 */
	private function download_and_attach_image($url, $post_id, $desc = '')
	{
		$url = esc_url_raw($url);
		if (empty($url)) {
			return new WP_Error('hypodash_invalid_url', 'Image URL is invalid.');
		}

		$response = wp_remote_get(
			$url,
			array(
				'timeout'   => 20,
				'sslverify' => false, // allow local/self-signed during development
			)
		);

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

		$code = wp_remote_retrieve_response_code($response);
		if ($code < 200 || $code > 299) {
			return new WP_Error('hypodash_http_error', 'Image download failed with HTTP ' . $code);
		}

		$body = wp_remote_retrieve_body($response);
		if (empty($body)) {
			return new WP_Error('hypodash_empty_body', 'Image download returned empty body.');
		}

		$tmp = wp_tempnam($url);
		if (! $tmp) {
			return new WP_Error('hypodash_temp_file', 'Could not create temporary file for download.');
		}

		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents
		file_put_contents($tmp, $body);

		// Use wp_parse_url() for consistent output across PHP versions.
		$path = wp_parse_url($url, PHP_URL_PATH);
		$filename = $path ? basename($path) : '';
		$filename = $filename ? sanitize_file_name($filename) : 'hypodash-image-' . time() . '.jpg';

		$file = array(
			'name'     => $filename,
			'tmp_name' => $tmp,
			'error'    => 0,
			'size'     => strlen($body),
		);

		$overrides = array(
			'test_form' => false,
			'test_size' => false,
		);

		$results = wp_handle_sideload($file, $overrides);
		if (isset($results['error'])) {
			return new WP_Error('hypodash_sideload', $results['error']);
		}

		$attachment = array(
			'post_mime_type' => $results['type'],
			'post_title'     => sanitize_file_name(pathinfo($filename, PATHINFO_FILENAME)),
			'post_content'   => '',
			'post_status'    => 'inherit',
		);

		$attach_id = wp_insert_attachment($attachment, $results['file'], $post_id);
		if (is_wp_error($attach_id)) {
			return $attach_id;
		}

		require_once ABSPATH . 'wp-admin/includes/image.php';
		$attach_data = wp_generate_attachment_metadata($attach_id, $results['file']);
		wp_update_attachment_metadata($attach_id, $attach_data);
		return wp_get_attachment_url($attach_id);
	}
}
