<?php
/**
 * Fraud Reports Tab
 *
 * @package     AffiliateWP
 * @subpackage  Admin/Reports
 * @copyright   Copyright (c) 2025, Awesome Motive Inc
 * @license     http://opensource.org/licenses/gpl-2.0.php GNU Public License
 * @since       2.28.0
 */

namespace AffWP\Fraud\Admin\Reports;

use AffWP\Admin\Reports;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Implements the 'Fraud' tab for the Reports screen.
 *
 * Provides visibility into fraud detection activity including:
 * - Summary cards for rejected/flagged referrals and affiliates
 * - Trends graph showing fraud activity over time
 *
 * @since 2.28.0
 *
 * @see \AffWP\Admin\Reports\Tab
 */
class Tab extends Reports\Tab {

	/**
	 * Sets up the Fraud tab for Reports.
	 *
	 * @since 2.28.0
	 */
	public function __construct() {
		$this->tab_id   = 'fraud';
		$this->label    = __( 'Anti-Fraud', 'affiliate-wp' );
		$this->priority = 0;

		// Include and set up the fraud graph.
		require_once AFFILIATEWP_PLUGIN_DIR . 'includes/admin/reports/class-fraud-graph.php';
		$this->graph = new \Affiliate_WP_Fraud_Graph();

		$this->set_up_additional_filters();

		parent::__construct();
	}

	/**
	 * Registers the 'Money Saved' tile.
	 *
	 * Shows total commission value of rejected referrals.
	 *
	 * @since 2.28.0
	 */
	public function money_saved_tile() {
		global $wpdb;

		$referrals_table = affiliate_wp()->referrals->table_name;

		// Build affiliate filter.
		$affiliate_where = '';
		if ( $this->affiliate_id ) {
			$affiliate_where = $wpdb->prepare( ' AND affiliate_id = %d', $this->affiliate_id );
		}

		// Get total amount of rejected referrals (all time).
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe.
		$total_saved = (float) $wpdb->get_var(
			"SELECT COALESCE(SUM(amount), 0) FROM {$referrals_table}
			WHERE status = 'rejected'
			{$affiliate_where}"
		);

		// Get count for comparison.
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe.
		$rejected_count = (int) $wpdb->get_var(
			"SELECT COUNT(*) FROM {$referrals_table}
			WHERE status = 'rejected'
			{$affiliate_where}"
		);

		$this->register_tile(
			'money_saved',
			array(
				'label'           => __( 'Money Saved (All Time)', 'affiliate-wp' ),
				'context'         => 'primary',
				'type'            => 'amount',
				'data'            => $total_saved,
				'comparison_data' => sprintf(
					/* translators: %d: Number of rejected referrals */
					__( '%d rejected referrals.', 'affiliate-wp' ),
					$rejected_count
				),
				'tooltip'         => __( 'Total commission amount saved by rejecting fraudulent referrals.', 'affiliate-wp' ),
			)
		);
	}

	/**
	 * Registers the 'Fraud Rate' tile.
	 *
	 * Shows percentage of referrals that are fraudulent.
	 *
	 * @since 2.28.0
	 */
	public function fraud_rate_tile() {
		global $wpdb;

		$referrals_table = affiliate_wp()->referrals->table_name;

		// Build affiliate filter.
		$affiliate_where = '';
		if ( $this->affiliate_id ) {
			$affiliate_where = $wpdb->prepare( ' AND affiliate_id = %d', $this->affiliate_id );
		}

		// Build date filter.
		$date_where = $wpdb->prepare(
			' AND date >= %s AND date <= %s',
			$this->date_query['start'] . ' 00:00:00',
			$this->date_query['end'] . ' 23:59:59'
		);

		// Get total referrals count for the period.
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Variables are safe.
		$total_referrals = (int) $wpdb->get_var(
			"SELECT COUNT(*) FROM {$referrals_table}
			WHERE 1=1
			{$affiliate_where}
			{$date_where}"
		);

		// Get rejected + flagged count for the period.
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Variables are safe.
		$fraud_count = (int) $wpdb->get_var(
			"SELECT COUNT(*) FROM {$referrals_table}
			WHERE (status = 'rejected' OR (status = 'pending' AND flag != '' AND flag IS NOT NULL))
			{$affiliate_where}
			{$date_where}"
		);

		// Calculate fraud rate.
		$fraud_rate = $total_referrals > 0 ? ( $fraud_count / $total_referrals ) * 100 : 0;

		// Determine if rate is good or bad.
		$rate_status = '';
		if ( $total_referrals > 0 ) {
			if ( $fraud_rate < 1 ) {
				$rate_status = __( 'Excellent', 'affiliate-wp' );
			} elseif ( $fraud_rate < 5 ) {
				$rate_status = __( 'Normal', 'affiliate-wp' );
			} else {
				$rate_status = __( 'High - investigate', 'affiliate-wp' );
			}
		}

		$this->register_tile(
			'fraud_rate',
			array(
				'label'           => sprintf(
					/* translators: Date filter label */
					__( 'Fraud Rate (%s)', 'affiliate-wp' ),
					$this->get_date_comparison_label( __( 'Custom', 'affiliate-wp' ) )
				),
				'context'         => 'secondary',
				'type'            => 'percentage',
				'data'            => $fraud_rate,
				'comparison_data' => sprintf(
					/* translators: 1: Fraud count, 2: Total referrals, 3: Rate status */
					__( '%1$d of %2$d referrals. %3$s', 'affiliate-wp' ),
					$fraud_count,
					$total_referrals,
					$rate_status ? '<strong>' . $rate_status . '</strong>' : ''
				),
				'tooltip'         => __( 'Percentage of referrals that were rejected or flagged for fraud. Below 1% is excellent, 1-5% is normal, above 5% needs investigation.', 'affiliate-wp' ),
			)
		);
	}

	/**
	 * Registers the 'Most Rejected Affiliate' tile.
	 *
	 * Shows the affiliate with the most rejected referrals.
	 *
	 * @since 2.28.0
	 */
	public function top_offender_tile() {
		global $wpdb;

		$referrals_table = affiliate_wp()->referrals->table_name;

		// Get affiliate with most rejected referrals.
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe.
		$top_offender = $wpdb->get_row(
			"SELECT affiliate_id, COUNT(*) as rejected_count, SUM(amount) as total_amount
			FROM {$referrals_table}
			WHERE status = 'rejected'
			GROUP BY affiliate_id
			ORDER BY rejected_count DESC
			LIMIT 1"
		);

		if ( $top_offender && $top_offender->rejected_count > 0 ) {
			$affiliate_name = affwp_get_affiliate_name( $top_offender->affiliate_id );
			if ( empty( $affiliate_name ) ) {
				$affiliate_name = affwp_get_affiliate_username( $top_offender->affiliate_id );
			}

			$affiliate_link = affwp_admin_url(
				'referrals',
				array(
					'affiliate_id' => $top_offender->affiliate_id,
					'status'       => 'rejected',
				)
			);

			$this->register_tile(
				'top_offender',
				array(
					'label'           => __( 'Most Rejected Affiliate (All Time)', 'affiliate-wp' ),
					'context'         => 'tertiary',
					'type'            => 'url',
					'data'            => sprintf(
						'<a href="%s">%s</a>',
						esc_url( $affiliate_link ),
						esc_html( $affiliate_name )
					),
					'comparison_data' => sprintf(
						/* translators: 1: Number of rejected referrals, 2: Total amount */
						__( '%1$d rejected (%2$s lost)', 'affiliate-wp' ),
						$top_offender->rejected_count,
						affwp_currency_filter( affwp_format_amount( $top_offender->total_amount ) )
					),
					'tooltip'         => __( 'Affiliate with the highest number of rejected referrals. Click to view their rejected referrals.', 'affiliate-wp' ),
				)
			);
		} else {
			$this->register_tile(
				'top_offender',
				array(
					'label'           => __( 'Most Rejected Affiliate (All Time)', 'affiliate-wp' ),
					'context'         => 'tertiary',
					'type'            => '',
					'data'            => __( 'None', 'affiliate-wp' ),
					'comparison_data' => __( 'No rejected referrals found.', 'affiliate-wp' ),
					'tooltip'         => __( 'Affiliate with the highest number of rejected referrals.', 'affiliate-wp' ),
				)
			);
		}
	}

	/**
	 * Registers the 'Rejected Referrals' tile.
	 *
	 * Shows total rejected referrals for the selected date range.
	 *
	 * @since 2.28.0
	 */
	public function rejected_referrals_tile() {
		$args = array(
			'status' => 'rejected',
			'date'   => $this->date_query,
			'number' => -1,
			'fields' => 'ids',
		);

		if ( $this->affiliate_id ) {
			$args['affiliate_id'] = $this->affiliate_id;
		}

		$rejected_count = affiliate_wp()->referrals->count( $args );

		// Get all-time count for comparison.
		$all_time_args = array(
			'status' => 'rejected',
			'number' => -1,
			'fields' => 'ids',
		);

		if ( $this->affiliate_id ) {
			$all_time_args['affiliate_id'] = $this->affiliate_id;
		}

		$all_time_count = affiliate_wp()->referrals->count( $all_time_args );

		$this->register_tile(
			'rejected_referrals',
			array(
				'label'           => sprintf(
					/* translators: Date filter label */
					__( 'Rejected Referrals (%s)', 'affiliate-wp' ),
					$this->get_date_comparison_label( __( 'Custom', 'affiliate-wp' ) )
				),
				'context'         => 'primary',
				'type'            => 'number',
				'data'            => $rejected_count,
				'comparison_data' => sprintf(
					/* translators: %d: All-time rejected count */
					__( 'All time: %d.', 'affiliate-wp' ),
					$all_time_count
				),
				'tooltip'         => __( 'Referrals that were rejected due to fraud detection or manual review.', 'affiliate-wp' ),
			)
		);
	}

	/**
	 * Registers the 'Flagged Referrals' tile.
	 *
	 * Shows pending referrals with fraud flags awaiting review.
	 *
	 * @since 2.28.0
	 */
	public function flagged_referrals_tile() {
		global $wpdb;

		$referrals_table = affiliate_wp()->referrals->table_name;

		// Build affiliate filter.
		$affiliate_where = '';
		if ( $this->affiliate_id ) {
			$affiliate_where = $wpdb->prepare( ' AND affiliate_id = %d', $this->affiliate_id );
		}

		// Count pending referrals with flags and get total amount.
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe.
		$flagged_data = $wpdb->get_row(
			"SELECT COUNT(*) as count, COALESCE(SUM(amount), 0) as total_amount
			FROM {$referrals_table}
			WHERE status = 'pending'
			AND flag != ''
			AND flag IS NOT NULL
			{$affiliate_where}"
		);

		$flagged_count  = (int) $flagged_data->count;
		$amount_at_risk = (float) $flagged_data->total_amount;

		$this->register_tile(
			'flagged_referrals',
			array(
				'label'           => __( 'Flagged Awaiting Review (All Time)', 'affiliate-wp' ),
				'context'         => 'secondary',
				'type'            => 'number',
				'data'            => $flagged_count,
				'comparison_data' => sprintf(
					/* translators: %s: Amount at risk */
					__( '%s at risk.', 'affiliate-wp' ),
					affwp_currency_filter( affwp_format_amount( $amount_at_risk ) )
				),
				'tooltip'         => __( 'Pending referrals with fraud flags that need manual review. The amount shown is the total commission at risk if these are approved.', 'affiliate-wp' ),
			)
		);
	}

	/**
	 * Registers the 'Flagged Affiliates' tile.
	 *
	 * Shows affiliates flagged for IP velocity violations.
	 *
	 * @since 2.28.0
	 */
	public function flagged_affiliates_tile() {
		global $wpdb;

		$affiliate_meta_table = affiliate_wp()->affiliate_meta->table_name;
		$affiliates_table     = affiliate_wp()->affiliates->table_name;

		// Count affiliates with IP velocity flags.
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe.
		$flagged_count = (int) $wpdb->get_var(
			"SELECT COUNT(DISTINCT affiliate_id) FROM {$affiliate_meta_table}
			WHERE meta_key = 'ip_velocity_flag'
			AND meta_value = '1'"
		);

		// Get pending affiliates count (might need review).
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe.
		$pending_count = (int) $wpdb->get_var(
			"SELECT COUNT(*) FROM {$affiliates_table}
			WHERE status = 'pending'"
		);

		$comparison_text = '';
		if ( $pending_count > 0 ) {
			$comparison_text = sprintf(
				/* translators: %d: Number of pending affiliates */
				__( '%d pending review.', 'affiliate-wp' ),
				$pending_count
			);
		} else {
			$comparison_text = __( 'No pending affiliates.', 'affiliate-wp' );
		}

		$this->register_tile(
			'flagged_affiliates',
			array(
				'label'           => __( 'Flagged Affiliates - IP Velocity (All Time)', 'affiliate-wp' ),
				'context'         => 'tertiary',
				'type'            => 'number',
				'data'            => $flagged_count,
				'comparison_data' => $comparison_text,
				'tooltip'         => __( 'Affiliates flagged for multiple registrations from the same IP address. This may indicate fake accounts.', 'affiliate-wp' ),
			)
		);
	}

	/**
	 * Registers the Fraud tab tiles.
	 *
	 * @since 2.28.0
	 */
	public function register_tiles() {
		// Row 1: High-level insights.
		$this->money_saved_tile();
		$this->fraud_rate_tile();
		$this->top_offender_tile();

		// Row 2: Actionable data.
		$this->rejected_referrals_tile();
		$this->flagged_referrals_tile();
		$this->flagged_affiliates_tile();
	}

	/**
	 * Handles displaying the 'Trends' graph.
	 *
	 * @since 2.28.0
	 */
	public function display_trends() {
		$this->graph->set( 'show_controls', false );
		$this->graph->set( 'x_mode', 'time' );
		$this->graph->set( 'affiliate_id', $this->affiliate_id );
		$this->graph->display();
	}

	/**
	 * Gets a human-readable label for a fraud flag.
	 *
	 * @since 2.28.0
	 *
	 * @param string $flag The flag key.
	 * @return string Human-readable label.
	 */
	private function get_flag_label( $flag ) {
		$labels = array(
			'self_referral'   => __( 'Self-Referral', 'affiliate-wp' ),
			'ppc_traffic'     => __( 'PPC Traffic', 'affiliate-wp' ),
			'conversion_rate' => __( 'Conversion Rate', 'affiliate-wp' ),
			'referring_site'  => __( 'Referring Site', 'affiliate-wp' ),
		);

		return isset( $labels[ $flag ] ) ? $labels[ $flag ] : $flag;
	}
}
