<?php
/**
 * Syncs Discourse comments with WordPress posts.
 *
 * @package WPDiscourse
 */

namespace WPDiscourse\DiscourseComment;

use WPDiscourse\DiscourseBase;

/**
 * Class DiscourseComment
 */
class DiscourseComment extends DiscourseBase {

	/**
	 * An instance of the DiscourseCommentFormatter class.
	 *
	 * @access protected
	 * @var \WPDiscourse\DiscourseCommentFormatter\DiscourseCommentFormatter DiscourseCommentFormatter
	 */
	protected $comment_formatter;

	/**
	 * Logger context
	 *
	 * @access protected
	 * @var string
	 */
	protected $logger_context = 'comment';

	/**
	 * DiscourseComment constructor.
	 *
	 * @param \WPDiscourse\DiscourseCommentFormatter\DiscourseCommentFormatter $comment_formatter An instance of DiscourseCommentFormatter.
	 */
	public function __construct( $comment_formatter ) {
		$this->comment_formatter = $comment_formatter;

		add_action( 'init', array( $this, 'setup_options' ) );
		add_action( 'init', array( $this, 'setup_logger' ) );
		add_filter( 'get_comments_number', array( $this, 'get_comments_number' ), 10, 2 );
		add_action( 'wpdc_sync_discourse_comments', array( $this, 'sync_comments' ), 10, 3 );
		add_filter( 'comments_template', array( $this, 'comments_template' ), 20, 1 );
		add_action( 'wp_enqueue_scripts', array( $this, 'discourse_comments_js' ) );
		add_action( 'rest_api_init', array( $this, 'initialize_comment_route' ) );
	}

	/**
	 * Enqueues the comment scripts and styles.
	 *
	 * Hooks into 'wp_enqueue_scripts'.
	 */
	public function discourse_comments_js() {
		if ( ! empty( $this->options['ajax-load'] ) ) {
			$data = array(
				'commentsURL' => home_url( '/wp-json/wp-discourse/v1/discourse-comments' ),
			);
			wp_enqueue_script( 'load_comments_js' );
			wp_localize_script( 'load_comments_js', 'wpdc', $data );
		}

		if ( ! empty( $this->options['load-comment-css'] ) ) {
			wp_enqueue_style( 'comment_styles' );
		}
	}

	/**
	 * Registers a Rest API route for returning comments at /wp-json/wp-discourse/v1/discourse-comments.
	 */
	public function initialize_comment_route() {
		if ( ! empty( $this->options['ajax-load'] ) ) {
			register_rest_route(
				'wp-discourse/v1',
				'discourse-comments',
				array(
					array(
						'methods'             => \WP_REST_Server::READABLE,
						'permission_callback' => function () {
							return true;
						},
						'callback'            => array( $this, 'get_discourse_comments' ),
					),
				)
			);
		}
	}

	/**
	 * Handles the REST request for Discourse comments.
	 *
	 * @param \WP_REST_Request Object $request The WP_REST_Request for Discourse comments.
	 *
	 * @return string
	 */
	public function get_discourse_comments( $request ) {
		$post_id = isset( $request['post_id'] ) ? intval( ( $request['post_id'] ) ) : 0;

		if ( empty( $post_id ) ) {

			return '';
		}

		$status = get_post_status( $post_id );
		$post   = get_post( $post_id );

		if ( 'publish' !== $status || ! empty( $post->post_password ) ) {

			return '';
		}

		return wp_kses_post( $this->comment_formatter->format( $post_id ) );
	}

	/**
	 * Returns the comment type for posts that have been published to Discourse, 0 if the post has not been published
	 * to Discourse, or if the 'enable-discourse-comments' option is not enabled.
	 *
	 * @param int    $post_id The post ID to check.
	 * @param string $context The caller context.
	 *
	 * @return int|mixed|string
	 */
	public function get_comment_type_for_post( $post_id, $context ) {
		$discourse_post_id = get_post_meta( $post_id, 'discourse_post_id', true );
		if ( empty( $this->options['enable-discourse-comments'] ) || empty( $discourse_post_id ) ) {

			return 0;
		}

		$comment_type = $this->options['comment-type'];
		// For posts published to Discourse prior to WP Discourse version 2.0.7 the "Update Discourse Topic" button will need to be
		// clicked for the 'publish_post_category' metadata to get set as post_metadata.
		$publish_category_id = get_post_meta( $post_id, 'publish_post_category', true );

		if ( 'display-comments' === $comment_type || 'display-comments-link' === $comment_type ) {

			return $comment_type;
		} else {
			$discourse_category = $this->get_discourse_category_by_id( $publish_category_id );

			if ( is_wp_error( $discourse_category ) ) {
				$log_args = array(
					'message' => $discourse_category->get_error_message(),
				);
				$this->logger->error( "{$context}.get_discourse_category", $log_args );
			}

			// If the Display Subcategories option is not enabled and a linked Discourse topic is moved to a subcategory, comments will not be displayed on WordPress.
			// If the Display Subcategories option is enabled, the subcategory security settings will be respected.
			if ( is_wp_error( $discourse_category ) || empty( $discourse_category ) || 1 === intval( $discourse_category['read_restricted'] ) ) {

				return 'display-comments-link';
			} else {

				return 'display-comments';
			}
		}
	}

	/**
	 * Syncs Discourse comments to WordPress.
	 *
	 * @param int    $post_id The WordPress post id.
	 * @param bool   $force Force comment sync.
	 * @param string $comment_type Set comment type for the post.
	 *
	 * @return null
	 */
	public function sync_comments( $post_id, $force = false, $comment_type = null ) {
		global $wpdb;

		$discourse_options     = $this->options;
		$use_discourse_webhook = ! empty( $discourse_options['use-discourse-webhook'] );
		$time                  = date_create()->format( 'U' );
		// Every 10 minutes do a json call to sync comments.
		$last_sync   = (int) get_post_meta( $post_id, 'discourse_last_sync', true );
		$sync_period = apply_filters( 'wpdc_comment_sync_period', 600, $post_id );
		$sync_post   = $last_sync + $sync_period < $time;

		// If the comments webhook is enabled, comments may be synced more often than once every 10 minutes.
		// For now, the 10 minute sync period is used as a fallback even when the webhook is enabled.
		// Once we give authors some feedback about the webhooks success after a post is published, the fallback can be removed.
		if ( $use_discourse_webhook ) {
			$sync_post = $sync_post || 1 === intval( get_post_meta( $post_id, 'wpdc_sync_post_comments', true ) );
		}

		if ( $sync_post || $force ) {
			// Avoids a double sync.
			$got_lock = $wpdb->get_row( "SELECT GET_LOCK( 'discourse_lock', 0 ) got_it" );
			if ( 1 === intval( $got_lock->got_it ) ) {

				$publish_private = apply_filters( 'wpdc_publish_private_post', false, $post_id );
				if ( 'publish' === get_post_status( $post_id ) || $publish_private ) {
					// Possible values are 0 (no Discourse comments), 'display-comments', or 'display-comments-link'.
					$comment_type             = $comment_type ? $comment_type : $this->get_comment_type_for_post( $post_id, 'sync_comments' );
					$comment_count            = 'display-comments' === $comment_type ? intval( $discourse_options['max-comments'] ) : 0;
					$min_trust_level          = intval( $discourse_options['min-trust-level'] );
					$min_score                = intval( $discourse_options['min-score'] );
					$min_replies              = intval( $discourse_options['min-replies'] );
					$bypass_trust_level_score = intval( $discourse_options['bypass-trust-level-score'] );

					$options = 'best=' . $comment_count . '&min_trust_level=' . $min_trust_level . '&min_score=' . $min_score;
					$options = $options . '&min_replies=' . $min_replies . '&bypass_trust_level_score=' . $bypass_trust_level_score;

					if ( isset( $discourse_options['only-show-moderator-liked'] ) && 1 === intval( $discourse_options['only-show-moderator-liked'] ) ) {
						$options = $options . '&only_moderator_liked=true';
					}

					$discourse_permalink = get_post_meta( $post_id, 'discourse_permalink', true );
					$topic_id            = get_post_meta( $post_id, 'discourse_topic_id', true );
					if ( ! $discourse_permalink ) {

						return 0;
					}
					$permalink = esc_url_raw( $discourse_permalink ) . '/wordpress.json?' . $options;
					$result    = $this->discourse_request( $permalink, array( 'raw' => true ) );

					if ( ! $this->validate( $result ) ) {
						$message = wp_remote_retrieve_response_message( $result );
						$code    = wp_remote_retrieve_response_code( $result );

						$log_args = array(
							'message'            => $message,
							'discourse_topic_id' => $topic_id,
							'wp_post_id'         => $post_id,
							'http_code'          => $code,
						);
						$this->logger->error( 'sync_comments.response_error', $log_args );
					} else {
						$raw_body = $result['body'];
						$json     = json_decode( $raw_body );

						if ( isset( $json->filtered_posts_count ) ) {
							$posts_count = $json->filtered_posts_count - 1;
							if ( $posts_count < 0 ) {
								$posts_count = 0;
							}

							update_post_meta( $post_id, 'discourse_comments_count', $posts_count );

							// Check if the site is on a recent version of Discourse that's returning the category_id (Aug 5, 2020.)
							// Allows comment type to be set for topics that are recategorized on Discourse when the display-public-comments-only comment option is selected.
							if ( property_exists( $json, 'category_id' ) ) {
								$category_id = $json->category_id;
								update_post_meta( $post_id, 'publish_post_category', intval( $category_id ) );
							}

							update_post_meta( $post_id, 'discourse_comments_raw', esc_sql( $raw_body ) );

							if ( isset( $topic_id ) ) {
								// Delete the cached html.
								delete_transient( "wpdc_comment_html_{$topic_id}" );
							}

							if ( ! empty( $this->options['verbose-comment-logs'] ) ) {
								$this->logger->info( 'sync_comments.success', array( 'post_id' => $post_id ) );
							}
						}
					}

					update_post_meta( $post_id, 'discourse_last_sync', $time );
					update_post_meta( $post_id, 'wpdc_sync_post_comments', 0 );
				}// End if().
			}// End if().

			$wpdb->get_results( "SELECT RELEASE_LOCK( 'discourse_lock' )" );
		}// End if().

		return null;
	}

	/**
	 * Displays either the Discourse comments or a link to the comments, depending on the value of $comment_type.
	 *
	 * Hooks into 'comments_template'.
	 *
	 * @param string $old The comments template returned by WordPress.
	 *
	 * @return string
	 */
	public function comments_template( $old ) {
		global $post;
		$post_id      = $post->ID;
		$current_user = wp_get_current_user();

		// Possible values are 0 (no Discourse comments), 'display-comments', or 'display-comments-link'.
		$comment_type = $this->get_comment_type_for_post( $post_id, 'comments_template' );
		// Discourse comments are not being used for the post and the hide-wordpress-comments option has been selected.
		$load_blank = empty( $comment_type ) && ! empty( $this->options['hide-wordpress-comments'] );
		// A switch that can be used to prevent loading the comments template for a user.
		$load_comments_template = apply_filters( 'wpdc_load_comments_template_for_user', true, $current_user, $post_id );

		if ( ! $load_comments_template || $load_blank ) {

			return WPDISCOURSE_PATH . 'templates/blank.php';
		}

		if ( empty( $comment_type ) ) {

			return $old;
		}

		$discourse_comments = null;
		switch ( $comment_type ) {
			case 'display-comments':
				if ( ! empty( $this->options['ajax-load'] ) ) {

					return WPDISCOURSE_PATH . 'templates/ajax-comments.php';
				}
				$discourse_comments = $this->comment_formatter->format( $post_id );

				break;
			case 'display-comments-link':
				$discourse_comments = $this->comment_formatter->comment_link( $post_id );

				break;
		}

		if ( empty( $this->options['show-existing-comments'] ) ) {
			echo wp_kses_post( $discourse_comments );

			return WPDISCOURSE_PATH . 'templates/blank.php';
		} else {
			echo wp_kses_post( $discourse_comments ) . '<div class="discourse-existing-comments-heading">' . wp_kses_post( $this->options['existing-comments-heading'] ) . '</div>';

			return $old;
		}
	}

	/**
	 * Returns the 'discourse_comments_count' for posts that use Discourse comments.
	 *
	 * Returns the sum of the Discourse comments and the WordPress comments for posts that have both.
	 *
	 * @param int $count The comment count supplied by WordPress.
	 * @param int $post_id The ID of the post.
	 *
	 * @return mixed
	 */
	public function get_comments_number( $count, $post_id ) {
		$discourse_post_id = get_post_meta( $post_id, 'discourse_post_id', true );
		if ( ! empty( $discourse_post_id ) ) {

			$single_page = is_single( $post_id ) || is_page( $post_id );
			$single_page = apply_filters( 'wpdc_single_page_comment_number_sync', $single_page, $post_id );

			if ( empty( $this->options['use-discourse-webhook'] ) ) {
				// Only automatically sync comments for individual posts, it's too inefficient to do this with an archive page.
				if ( $single_page ) {
					$this->sync_comments( $post_id );
				} else {
					// For archive pages, check $last_sync against $archive_page_sync_period.
					$archive_page_sync_period = intval( apply_filters( 'discourse_archive_page_sync_period', DAY_IN_SECONDS, $post_id ) );
					$last_sync                = intval( get_post_meta( $post_id, 'discourse_last_sync', true ) );

					if ( $last_sync + $archive_page_sync_period < time() ) {
						$this->sync_comments( $post_id );
					}
				}
			}

			$count = intval( get_post_meta( $post_id, 'discourse_comments_count', true ) );

			// If WordPress comments are also being used, add them to the comment count.
			if ( ! empty( $this->options['show-existing-comments'] ) ) {
				$current_post     = get_post( $post_id );
				$wp_comment_count = $current_post->comment_count;
				$count            = $count + $wp_comment_count;
			}
		}

		return apply_filters( 'wpdc_comments_count', $count, $post_id, $discourse_post_id );
	}
}
