<?php

namespace WP_Statistics\Service\Admin\Posts;

use WP_Statistics\Components\DateRange;
use WP_STATISTICS\DB;
use WP_STATISTICS\Helper;
use WP_STATISTICS\Menus;
use WP_Statistics\MiniChart\WP_Statistics_Mini_Chart_Settings;
use WP_Statistics\Models\ViewsModel;
use WP_Statistics\Models\VisitorsModel;
use WP_STATISTICS\Option;
use WP_Statistics\Service\Admin\MiniChart\MiniChartHelper;
use WP_STATISTICS\TimeZone;
use WP_Statistics\Traits\ObjectCacheTrait;
use WP_Statistics\Utils\Request;

/**
 * This class will add, render, modify sort and modify order by hits column in posts and taxonomies list pages.
 */
class HitColumnHandler
{
    use ObjectCacheTrait;

    /**
     * Mini-chart helper class.
     *
     * @var MiniChartHelper
     */
    private $miniChartHelper;

    private $initialPostDate;

    /**
     * Hits column name.
     *
     * This will change in taxonomies lists.
     *
     * @var string
     */
    private $columnName = 'wp-statistics-post-hits';

    /**
     * Class constructor.
     *
     * @param bool $isTerm Is this instance handling a taxonomies list?
     */
    public function __construct($isTerm = false)
    {
        if ($isTerm) {
            $this->columnName = 'wp-statistics-tax-hits';
        }

        $this->initialPostDate = Helper::getInitialPostDate();

        $this->miniChartHelper = new MiniChartHelper();
    }

    /**
     * Adds a custom column to posts/taxonomies lists for hits statistics.
     *
     * @param array $columns Columns array.
     * @param string $postType Post type.
     *
     * @return array
     *
     * @hooked action: `manage_{$type}_posts_columns` - 10
     * @hooked action: `manage_edit-{$tax}_columns` - 10
     */
    public function addHitColumn($columns, $postType = '')
    {
        if (!empty($postType) && empty(is_post_type_viewable($postType))) {
            return $columns;
        }

        // Handle WooCommerce sortable UI
        if (isset($columns['handle'])) {
            $cols = [];
            foreach ($columns as $key => $value) {
                if ($key == 'handle') {
                    $cols[$this->columnName] = $this->miniChartHelper->getLabel();
                }
                $cols[$key] = $value;
            }
            return $cols;
        }

        $columns[$this->columnName] = $this->miniChartHelper->getLabel();

        return $columns;
    }

    /**
     * Renders hits column in post/pages lists.
     *
     * @param string $columnName
     * @param int $postId
     *
     * @return void
     *
     * @hooked action: `manage_{$type}_posts_columns` - 10
     */
    public function renderHitColumn($columnName, $postId)
    {
        // Exit early if the column is not the one we're interested in
        if ($columnName !== $this->columnName) {
            return;
        }

        // Initialize class attributes only once (since all posts in the list have the same post type)
        if (!$this->isCacheSet('postType')) {
            $this->setCache('postType', get_post_type($postId));
        }

        $hitCount = $this->calculateHitCount($postId);
        echo $this->getHitColumnContent($hitCount, $postId);
    }

    /**
     * Renders hits column in taxonomies lists.
     *
     * @param string Column HTML output.
     * @param string $columnName
     * @param int $termId
     *
     * @return string HTML output.
     *
     * @hooked filter: `manage_{$tax}_custom_column` - 10
     */
    public function renderTaxHitColumn($output, $columnName, $termId)
    {
        // Exit early if the column is not the one we're interested in
        if ($columnName !== $this->columnName) {
            return $output;
        }

        $term = get_term($termId);

        // Initialize class attributes only once (since all terms in the list have the same taxonomy)
        if (!$this->isCacheSet('postType')) {
            $this->setCache('postType', (($term instanceof \WP_Term) && ($term->taxonomy === 'category' || $term->taxonomy === 'post_tag')) ? $term->taxonomy : 'tax_' . $term->taxonomy);
        }

        $hitCount = $this->calculateHitCount($termId, $term);
        return $this->getHitColumnContent($hitCount, $termId, true);
    }

    /**
     * Adds hits column to sortable columns.
     *
     * @param array $columns
     *
     * @return array
     *
     * @hooked filter: `manage_edit-{$type}_sortable_columns` - 10
     * @hooked filter: `manage_edit-{$tax}_sortable_columns` - 10
     */
    public function modifySortableColumns($columns)
    {
        $columns[$this->columnName] = 'hits';

        return $columns;
    }

    /**
     * Modifies query clauses when posts are sorted by hits column.
     *
     * @param array $clauses Clauses for the query.
     * @param \WP_Query $wpQuery Current posts query.
     *
     * @return array Updated clauses for the query.
     *
     * @hooked filter: `posts_clauses` - 10
     */
    public function handlePostOrderByHits($clauses, $wpQuery)
    {
        if (!is_admin()) {
            return;
        }

        // If order-by.
        if (!isset($wpQuery->query_vars['orderby']) || !isset($wpQuery->query_vars['order']) || $wpQuery->query_vars['orderby'] != 'hits') {
            return $clauses;
        }

        global $wpdb;

        // Get global Variable
        $order = $wpQuery->query_vars['order'];

        // Add date condition if needed
        $dateCondition = '';
        if ($this->miniChartHelper->getCountDisplay() === 'date_range') {
            $dateCondition = 'BETWEEN "' . TimeZone::getTimeAgo(intval(Option::getByAddon('date_range', 'mini_chart', '14'))) . '" AND "' . date('Y-m-d') . '"';
        }

        // Select Field
        if ($this->miniChartHelper->getChartMetric() === 'visitors') {
            if (!empty($dateCondition)) {
                $dateCondition = "AND `visitor_relationships`.`date` $dateCondition";
            }

            $clauses['fields'] .= ', (SELECT COUNT(DISTINCT `visitor_id`) FROM ' . DB::table('visitor_relationships') . ' AS `visitor_relationships` LEFT JOIN ' . DB::table('pages') . ' AS `pages` ON `visitor_relationships`.`page_id` = `pages`.`page_id` WHERE (`pages`.`type` IN ("page", "post", "product") OR `pages`.`type` LIKE "post_type_%") AND ' . $wpdb->posts . '.`ID` = `pages`.`id` ' . $dateCondition . ') AS `post_hits_sortable` ';
        } else {
            $historicalSubQuery = '';
            if (!empty($dateCondition)) {
                $dateCondition = "AND `pages`.`date` $dateCondition";
            } else {
                // Consider historical for total views
                $historicalSubQuery = ' + IFNULL((SELECT SUM(`historical`.`value`) FROM ' . DB::table('historical') . ' AS `historical` WHERE `historical`.`page_id` = ' . $wpdb->posts . '.`ID` AND `historical`.`uri` LIKE CONCAT("%/", ' . $wpdb->posts . '.`post_name`, "/")), 0)';
            }

            $clauses['fields'] .= ', ((SELECT SUM(`pages`.`count`) FROM ' . DB::table('pages') . ' AS `pages` WHERE (`pages`.`type` IN ("page", "post", "product") OR `pages`.`type` LIKE "post_type_%") AND ' . $wpdb->posts . '.`ID` = `pages`.`id` ' . $dateCondition . ')' . $historicalSubQuery . ') AS `post_hits_sortable` ';
        }

        // Order by `post_hits_sortable`
        $clauses['orderby'] = " COALESCE(`post_hits_sortable`, 0) $order";

        return $clauses;
    }

    /**
     * Modifies query clauses when terms are sorted by hits column.
     *
     * @param array $clauses Clauses for the query.
     * @param array $taxonomies Taxonomy names.
     * @param array $args Term query arguments.
     *
     * @return array Updated clauses for the query.
     *
     * @hooked filter: `terms_clauses` - 10
     */
    public function handleTaxOrderByHits($clauses, $taxonomies, $args)
    {
        if (!is_admin()) {
            return;
        }

        // If order-by.
        if (!isset($args['orderby']) || $args['orderby'] !== 'hits') {
            return $clauses;
        }

        // Add date condition if needed
        $dateCondition = '';
        if ($this->miniChartHelper->getCountDisplay() === 'date_range') {
            $dateCondition = 'BETWEEN "' . TimeZone::getTimeAgo(intval(Option::getByAddon('date_range', 'mini_chart', '14'))) . '" AND "' . date('Y-m-d') . '"';
        }

        // Select Field
        if ($this->miniChartHelper->getChartMetric() === 'visitors') {
            if (!empty($dateCondition)) {
                $dateCondition = "AND `visitor_relationships`.`date` $dateCondition";
            }

            $clauses['fields'] .= ', (SELECT COUNT(DISTINCT `visitor_id`) FROM ' . DB::table('visitor_relationships') . ' AS `visitor_relationships` LEFT JOIN ' . DB::table('pages') . ' AS `pages` ON `visitor_relationships`.`page_id` = `pages`.`page_id` WHERE `pages`.`type` IN ("category", "post_tag", "tax") AND `t`.`term_id` = `pages`.`id` ' . $dateCondition . ') AS `tax_hits_sortable` ';
        } else {
            $historicalSubQuery = '';
            if (!empty($dateCondition)) {
                $dateCondition = "AND `pages`.`date` $dateCondition";
            } else {
                // Consider historical for total views
                $historicalSubQuery = ' + IFNULL((SELECT SUM(`historical`.`value`) FROM ' . DB::table('historical') . ' AS `historical` WHERE `historical`.`page_id` = `t`.`term_id` AND `historical`.`uri` LIKE CONCAT("%/", `t`.`slug`, "/")), 0)';
            }

            $clauses['fields'] .= ', ((SELECT SUM(`pages`.`count`) FROM ' . DB::table('pages') . ' AS `pages` WHERE `pages`.`type` IN ("category", "post_tag", "tax") AND `t`.`term_id` = `pages`.`id` ' . $dateCondition . ')' . $historicalSubQuery . ') AS `tax_hits_sortable` ';
        }

        // Order by `tax_hits_sortable`
        $clauses['orderby'] = " ORDER BY coalesce(`tax_hits_sortable`, 0)";

        return $clauses;
    }

    /**
     * Calculates hits count based on Mini-chart add-on's options.
     *
     * @param int $objectId Post or term ID.
     * @param \WP_Term $term If not empty, this method will calculate terms hits stats instead of posts hits stats.
     *
     * @return int|null
     */
    private function calculateHitCount($objectId, $term = null)
    {
        // Don't calculate stats if `count_display` is disabled
        if ($this->miniChartHelper->getCountDisplay() === 'disabled') {
            return null;
        }

        $hitArgs = [
            'resource_type' => $this->getCache('postType'),
            'ignore_date'   => true
        ];

        // Change `resource_type` parameter if it's a term
        if (!empty($term)) {
            $hitArgs['resource_type'] = $this->getCache('postType');
        }

        if ($this->miniChartHelper->getCountDisplay() === 'date_range') {
            $hitArgs['date'] = [
                'from' => TimeZone::getTimeAgo(intval(Option::getByAddon('date_range', 'mini_chart', '14'))),
                'to'   => date('Y-m-d'),
            ];
        }

        // Cache hitArgs
        $this->setCache('hitArgs', $hitArgs);

        $hitCount = 0;
        if ($this->miniChartHelper->getChartMetric() === 'visitors') {
            $visitorsModel = new VisitorsModel();
            $hitCount      = $visitorsModel->countVisitors(array_merge($hitArgs, ['resource_id' => $objectId]));
        } else {
            $viewsModel = new ViewsModel();
            $hitCount   = $viewsModel->countViewsFromPagesOnly(array_merge($hitArgs, ['post_id' => $objectId]));

            // Consider historical if `count_display` is equal to 'total'
            if ($this->miniChartHelper->getCountDisplay() === 'total') {
                $uri = empty($term) ? get_permalink($objectId) : get_term_link(intval($term->term_id), $term->taxonomy);
                $uri = !is_wp_error($uri) ? wp_make_link_relative($uri) : '';

                $hitCount = $viewsModel->countViewsFromPagesOnly(array_merge($hitArgs, ['post_id' => $objectId, 'uri' => $uri, 'historical' => true]));
            }
        }

        return $hitCount;
    }

    /**
     * Returns the content of hits column.
     *
     * @param int $hitCount
     * @param int $objectId Post or term ID.
     * @param bool $isTerm Is this column being rendered in taxonomies list?
     *
     * @return string HTML markup.
     */
    private function getHitColumnContent($hitCount, $objectId, $isTerm = false)
    {
        // Remove only the first occurrence of "post_type_" from `postType` attribute
        $actualPostType = $this->getCache('postType');
        if (strpos($actualPostType, 'post_type_') === 0) {
            $actualPostType = substr($actualPostType, strlen('post_type_'));
        }

        if (!$this->isCacheSet('isCurrentPostTypeSelected')) {
            // Check if current post type is selected in Mini-chart add-on's options
            $miniChartSettings = class_exists(WP_Statistics_Mini_Chart_Settings::class) ? get_option(WP_Statistics_Mini_Chart_Settings::get_instance()->setting_name) : '';
            $this->setCache('isCurrentPostTypeSelected', !empty($miniChartSettings) && !empty($miniChartSettings["active_mini_chart_{$actualPostType}"]));
        }

        $result = '';
        if (!$this->miniChartHelper->isMiniChartActive() || ($isTerm || $this->getCache('isCurrentPostTypeSelected'))) {
            $hookName = !$isTerm ? "wp_statistics_before_hit_column_{$actualPostType}" : 'wp_statistics_before_hit_column';

            // If Mini-chart add-on is not active, this line will display the "Unlock!" button
            // If Mini-chart add-on is active and current post type is selected in settings (or it's a term), the chart will be displayed via the filter
            $result .= apply_filters($hookName, $this->getPreviewChartUnlockHtml(), $objectId, $this->getCache('postType'));
        }

        // Display hit count only if it's a valid number
        if (is_numeric($hitCount)) {
            $date = $this->getCache('hitArgs')['date'] ?? DateRange::get('30days');

            $analyticsUrl = Menus::admin_url('content-analytics', [
                'post_id' => $objectId,
                'type'    => 'single',
                'from'    => $date['from'],
                'to'      => $date['to']
            ]);

            if ($isTerm) {
                $analyticsUrl = Menus::admin_url('category-analytics', [
                    'term_id' => $objectId,
                    'type'    => 'single',
                    'from'    => $date['from'],
                    'to'      => $date['to']
                ]);
            }

            // Add hit number below the chart
            $result .= sprintf(
                '<div class="%s"><span class="%s">%s</span> <a href="%s" class="wps-admin-column__link %s">%s</a></div>',
                $this->miniChartHelper->getCountDisplay() === 'disabled' ? 'wps-hide' : '',
                $this->miniChartHelper->isMiniChartActive() ? '' : 'wps-hide',
                $this->miniChartHelper->getLabel(),
                esc_url($analyticsUrl),
                $this->miniChartHelper->isMiniChartActive() ? '' : 'wps-admin-column__unlock-count',
                esc_html(number_format($hitCount))
            );
        }

        return $result;
    }

    /**
     * Returns HTML markup for a static "Unlock!" button that will be displayed when Mini-chart add-on is not active.
     *
     * @return string
     */
    private function getPreviewChartUnlockHtml()
    {
        return sprintf(
            '<div class="wps-admin-column__unlock">
                        <a href="%s">
                            <span class="wps-admin-column__unlock__text">%s</span>
                            <span class="wps-admin-column__unlock__img"></span>
                        </a>
                    </div>',
            esc_url(admin_url('admin.php?page=wps_plugins_page&type=locked-mini-chart')),
            __('Unlock', 'wp-statistics')
        );
    }
}
