<?php
/**
 * Column mapping service for import/export field mapping operations.
 *
 * @package RewardsWP
 */

namespace Awesomemotive\Rewardswp\Services;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

use WP_Error;

/**
 * Service for handling CSV column mapping functionality.
 *
 * @since 1.0.0
 */
class ColumnMappingService {

	/**
	 * Available target fields for mapping.
	 *
	 * @since 1.0.0
	 * @var array
	 */
	private array $target_fields = [];

	/**
	 * Constructor.
	 *
	 * Initializes target field definitions. Field labels and descriptions are
	 * translated here rather than in the property default because translation
	 * functions cannot be used in constant property initializers.
	 *
	 * @since 1.0.0
	 */
	public function __construct() {
		$this->target_fields = [
			// Member identification fields (precedence order).
			'member_id'     => [
				'label'       => __( 'Member ID', 'rewardswp' ),
				'type'        => 'int',
				'required'    => false,
				'precedence'  => 1,
				'group'       => 'member_identification',
				'description' => __( 'Existing member ID from RewardsWP', 'rewardswp' ),
			],
			'user_id'       => [
				'label'       => __( 'User ID', 'rewardswp' ),
				'type'        => 'int',
				'required'    => false,
				'precedence'  => 2,
				'group'       => 'member_identification',
				'description' => __( 'WordPress user ID', 'rewardswp' ),
			],
			'email'         => [
				'label'       => __( 'Email', 'rewardswp' ),
				'type'        => 'email',
				'required'    => false,
				'precedence'  => 3,
				'group'       => 'member_identification',
				'description' => __( 'Member email address', 'rewardswp' ),
			],
			'first_name'    => [
				'label'       => __( 'First Name', 'rewardswp' ),
				'type'        => 'string',
				'required'    => false,
				'group'       => 'member_identification',
				'description' => __( 'Member first name', 'rewardswp' ),
			],
			'last_name'     => [
				'label'       => __( 'Last Name', 'rewardswp' ),
				'type'        => 'string',
				'required'    => false,
				'group'       => 'member_identification',
				'description' => __( 'Member last name', 'rewardswp' ),
			],

			// Member optional fields.
			'referral_code' => [
				'label'       => __( 'Referral Code', 'rewardswp' ),
				'type'        => 'string',
				'required'    => false,
				'group'       => 'member_optional',
				'description' => __( 'Custom referral code', 'rewardswp' ),
			],
			'status'        => [
				'label'       => __( 'Member Status', 'rewardswp' ),
				'type'        => 'enum',
				'required'    => false,
				'group'       => 'member_optional',
				'description' => __( 'Member status (active, blocked, pending, excluded)', 'rewardswp' ),
				'default'     => 'active',
			],

			// Point fields.
			'points'        => [
				'label'       => __( 'Points', 'rewardswp' ),
				'type'        => 'float',
				'required'    => true,
				'group'       => 'points',
				'description' => __( 'Point amount (positive for earned, negative for spent)', 'rewardswp' ),
			],
			'type'          => [
				'label'       => __( 'Point Type', 'rewardswp' ),
				'type'        => 'enum',
				'required'    => false,
				'group'       => 'points',
				'description' => __( 'Point transaction type (earned or spent)', 'rewardswp' ),
				'default'     => 'earned',
			],
			'source_type'   => [
				'label'       => __( 'Source Type', 'rewardswp' ),
				'type'        => 'string',
				'required'    => false,
				'group'       => 'points',
				'description' => __( 'Source of the points transaction', 'rewardswp' ),
				'default'     => 'import',
			],
			'source_id'     => [
				'label'       => __( 'Source ID', 'rewardswp' ),
				'type'        => 'int',
				'required'    => false,
				'group'       => 'points',
				'description' => __( 'ID of the source object', 'rewardswp' ),
				'default'     => 0,
			],
			'status_points' => [
				'label'       => __( 'Points Status', 'rewardswp' ),
				'type'        => 'enum',
				'required'    => false,
				'group'       => 'points',
				'description' => __( 'Status of points transaction (pending, approved, expired, revoked)', 'rewardswp' ),
				'default'     => 'approved',
			],
			'created_at'    => [
				'label'       => __( 'Created At', 'rewardswp' ),
				'type'        => 'datetime',
				'required'    => false,
				'group'       => 'points',
				'description' => __( 'Transaction date (YYYY-MM-DD HH:MM:SS)', 'rewardswp' ),
			],
			'expires_at'    => [
				'label'       => __( 'Expires At', 'rewardswp' ),
				'type'        => 'datetime',
				'required'    => false,
				'group'       => 'points',
				'description' => __( 'Point expiration date (YYYY-MM-DD HH:MM:SS)', 'rewardswp' ),
			],
			'note_public'   => [
				'label'       => __( 'Public Note', 'rewardswp' ),
				'type'        => 'string',
				'required'    => false,
				'group'       => 'points',
				'description' => __( 'Note visible to member', 'rewardswp' ),
			],
			'note_internal' => [
				'label'       => __( 'Internal Note', 'rewardswp' ),
				'type'        => 'string',
				'required'    => false,
				'group'       => 'points',
				'description' => __( 'Internal note for admin use', 'rewardswp' ),
			],
		];
	}

	/**
	 * Auto-detection patterns for common column names.
	 *
	 * @since 1.0.0
	 * @var array
	 */
	private array $auto_detect_patterns = [
		'member_id'      => [
			'exact'    => [ 'member_id', 'memberid', 'member-id', 'id' ],
			'contains' => [ 'member' ],
		],
		'user_id'        => [
			'exact'    => [ 'user_id', 'wp_user_id', 'userid', 'user-id', 'wp-user-id', 'wpuserid' ],
			'contains' => [ 'user_id', 'wp_user' ],
		],
		'email'          => [
			'exact'    => [ 'email', 'email_address', 'user_email', 'e-mail', 'e_mail' ],
			'contains' => [ 'email', 'mail' ],
		],
		'first_name'     => [
			'exact'    => [ 'first_name', 'firstname', 'fname', 'first-name', 'given_name' ],
			'contains' => [ 'first', 'fname' ],
		],
		'last_name'      => [
			'exact'    => [ 'last_name', 'lastname', 'lname', 'last-name', 'family_name', 'surname' ],
			'contains' => [ 'last', 'lname', 'surname' ],
		],
		'referral_code'  => [
			'exact'    => [ 'referral_code', 'referralcode', 'referral-code', 'ref_code', 'code' ],
			'contains' => [ 'referral', 'ref_code' ],
		],
		'status'         => [
			'exact'    => [ 'status', 'member_status', 'user_status' ],
			'contains' => [ 'status' ],
		],
		'points_balance' => [
			'exact'    => [ 'points_balance', 'balance', 'current_points', 'total_points' ],
			'contains' => [ 'balance', 'total_points' ],
		],
		'points'         => [
			'exact'    => [ 'points', 'point_value', 'amount', 'value', 'point_amount' ],
			'contains' => [ 'points', 'amount', 'value' ],
		],
		'type'           => [
			'exact'    => [ 'type', 'point_type', 'transaction_type', 'points_type' ],
			'contains' => [ 'type' ],
		],
		'source_type'    => [
			'exact'    => [ 'source_type', 'sourcetype', 'source-type', 'origin', 'source' ],
			'contains' => [ 'source' ],
		],
		'source_id'      => [
			'exact'    => [ 'source_id', 'sourceid', 'source-id', 'origin_id' ],
			'contains' => [ 'source_id' ],
		],
		'status_points'  => [
			'exact'    => [ 'points_status', 'point_status', 'transaction_status', 'approval_status' ],
			'contains' => [ 'points_status', 'approval' ],
		],
		'expires_at'     => [
			'exact'    => [ 'expires_at', 'expiration', 'expire_date', 'expires', 'expiry' ],
			'contains' => [ 'expir', 'expiry' ],
		],
		'note_public'    => [
			'exact'    => [ 'note_public', 'public_note', 'note', 'description', 'comment' ],
			'contains' => [ 'public_note', 'note', 'description' ],
		],
		'note_internal'  => [
			'exact'    => [ 'note_internal', 'internal_note', 'admin_note', 'private_note' ],
			'contains' => [ 'internal', 'admin_note', 'private' ],
		],
		'created_at'     => [
			'exact'    => [ 'created_at', 'date_created', 'transaction_date', 'date', 'created' ],
			'contains' => [ 'created', 'date' ],
		],
	];

	/**
	 * Auto-detect column mappings based on CSV headers.
	 *
	 * @since 1.0.0
	 *
	 * @param array $headers CSV column headers.
	 *
	 * @return array Detected mappings with confidence scores.
	 */
	public function auto_detect_mappings( array $headers ): array {
		$mappings = [];

		foreach ( $headers as $header ) {
			$header_clean    = strtolower( trim( $header ) );
			$best_match      = null;
			$best_confidence = 0;

			foreach ( $this->auto_detect_patterns as $target_field => $patterns ) {
				$confidence = $this->calculate_match_confidence( $header_clean, $patterns );

				if ( $confidence > $best_confidence ) {
					$best_match      = $target_field;
					$best_confidence = $confidence;
				}
			}

			$mappings[ $header ] = [
				'target_field' => $best_match,
				'confidence'   => $best_confidence,
				'status'       => $this->get_confidence_status( $best_confidence ),
			];
		}

		return $mappings;
	}

	/**
	 * Calculate match confidence for a header against patterns.
	 *
	 * @since 1.0.0
	 *
	 * @param string $header   Clean header name.
	 * @param array  $patterns Pattern configuration.
	 *
	 * @return float Confidence score (0-1).
	 */
	private function calculate_match_confidence( string $header, array $patterns ): float {
		// Exact match = 100% confidence.
		if ( in_array( $header, $patterns['exact'] ?? [], true ) ) {
			return 1.0;
		}

		// Contains match = 70% confidence.
		foreach ( $patterns['contains'] ?? [] as $pattern ) {
			if ( strpos( $header, $pattern ) !== false ) {
				return 0.7;
			}
		}

		return 0.0;
	}

	/**
	 * Get confidence status based on score.
	 *
	 * @since 1.0.0
	 *
	 * @param float $confidence Confidence score.
	 *
	 * @return string Status: 'confident', 'uncertain', 'unmapped'.
	 */
	private function get_confidence_status( float $confidence ): string {
		if ( $confidence >= 0.9 ) {
			return 'confident';
		}

		if ( $confidence >= 0.5 ) {
			return 'uncertain';
		}

		return 'unmapped';
	}

	/**
	 * Get available target fields for mapping.
	 *
	 * @since 1.0.0
	 *
	 * @return array Target fields configuration.
	 */
	public function get_target_fields(): array {
		return $this->target_fields;
	}

	/**
	 * Get target fields grouped by category.
	 *
	 * @since 1.0.0
	 *
	 * @return array Grouped target fields.
	 */
	public function get_target_fields_grouped(): array {
		$grouped = [];

		foreach ( $this->target_fields as $field_key => $field_config ) {
			$group                           = $field_config['group'] ?? 'other';
			$grouped[ $group ][ $field_key ] = $field_config;
		}

		return $grouped;
	}

	/**
	 * Validate mapping configuration.
	 *
	 * @since 1.0.0
	 *
	 * @param array $mappings Column mappings.
	 *
	 * @return bool|\WP_Error True if valid, WP_Error if invalid.
	 */
	public function validate_mappings( array $mappings ) {
		$errors = [];

		// Simply check what fields are mapped - no complex logic.
		$mapped_fields = array_values( $mappings );

		// Remove empty values.
		$mapped_fields = array_filter(
			$mapped_fields,
			function ( $field ) {
				return ! empty( $field ) && 'skip' !== $field;
			}
		);

		// Check for required: points field.
		$has_points = in_array( 'points', $mapped_fields, true );

		// Check for required: at least one member identification field.
		$member_id_fields          = [ 'member_id', 'user_id', 'email' ];
		$has_member_identification = false;
		foreach ( $member_id_fields as $field ) {
			if ( in_array( $field, $mapped_fields, true ) ) {
				$has_member_identification = true;
				break;
			}
		}

		// Validate required fields.
		if ( ! $has_points ) {
			$errors[] = esc_html__( 'Points field is required and must be mapped.', 'rewardswp' );
		}

		if ( ! $has_member_identification ) {
			$errors[] = esc_html__( 'At least one member identification field (Member ID, User ID, or Email) must be mapped.', 'rewardswp' );
		}

		// Check for duplicate mappings.
		$mapped_targets = [];
		foreach ( $mappings as $csv_column => $target_field ) {
			if ( empty( $target_field ) || 'skip' === $target_field ) {
				continue;
			}

			// Check if target field exists.
			if ( ! isset( $this->target_fields[ $target_field ] ) ) {
				// Not escaped here — rendered via Alpine x-text, which escapes on output.
				$errors[] = sprintf(
					/* translators: %1$s: CSV column name, %2$s: target field */
					__( 'Invalid target field "%2$s" for column "%1$s".', 'rewardswp' ),
					$csv_column,
					$target_field
				);
				continue;
			}

			// Check for duplicate mappings.
			if ( in_array( $target_field, $mapped_targets, true ) ) {
				// Not escaped here — rendered via Alpine x-text, which escapes on output.
				$errors[] = sprintf(
					/* translators: %s: target field name */
					__( 'Target field "%s" is mapped to multiple columns.', 'rewardswp' ),
					$this->target_fields[ $target_field ]['label']
				);
				continue;
			}

			$mapped_targets[] = $target_field;
		}

		// Additional validation: if email is mapped, check if first_name is also mapped.
		if ( in_array( 'email', $mapped_targets, true ) && ! in_array( 'user_id', $mapped_targets, true ) && ! in_array( 'member_id', $mapped_targets, true ) ) {
			if ( ! in_array( 'first_name', $mapped_targets, true ) ) {
				$errors[] = esc_html__( 'When using email for member identification, first name is also required.', 'rewardswp' );
			}
		}

		// Skip contextual validation during import - it's only for UI guidance.
		// The contextual validation is helpful for mapping UI but shouldn't block import.

		if ( ! empty( $errors ) ) {
			return new WP_Error( 'mapping_validation_failed', implode( ' ', $errors ), $errors );
		}

		return true;
	}

	/**
	 * Apply mappings to transform CSV row data.
	 *
	 * @since 1.0.0
	 *
	 * @param array $row      CSV row data (header => value).
	 * @param array $mappings Column mappings (csv_column => target_field).
	 *
	 * @return array Transformed row data.
	 */
	public function apply_mappings( array $row, array $mappings ): array {
		$transformed = [];

		foreach ( $mappings as $csv_column => $target_field ) {
			if ( empty( $target_field ) || 'skip' === $target_field || ! isset( $row[ $csv_column ] ) ) {
				continue;
			}

			$value = $row[ $csv_column ];

			// Apply field-specific transformations.
			$transformed[ $target_field ] = $this->transform_field_value( $target_field, $value );
		}

		// Apply defaults for unmapped fields.
		foreach ( $this->target_fields as $field_key => $field_config ) {
			if ( ! isset( $transformed[ $field_key ] ) && isset( $field_config['default'] ) ) {
				$transformed[ $field_key ] = $field_config['default'];
			}
		}

		return $transformed;
	}

	/**
	 * Transform field value based on field type.
	 *
	 * @since 1.0.0
	 *
	 * @param string $field_key Target field key.
	 * @param mixed  $value     Raw value from CSV.
	 *
	 * @return mixed Transformed value.
	 */
	private function transform_field_value( string $field_key, $value ) {
		if ( ! isset( $this->target_fields[ $field_key ] ) ) {
			return $value;
		}

		$field_config = $this->target_fields[ $field_key ];
		$type         = $field_config['type'];

		switch ( $type ) {
			case 'int':
				return (int) $value;

			case 'float':
				return (float) $value;

			case 'email':
				return sanitize_email( $value );

			case 'datetime':
				// Try to parse the date.
				$timestamp = strtotime( $value );
				if ( false !== $timestamp ) {
					return gmdate( 'Y-m-d H:i:s', $timestamp );
				}
				return $value;

			case 'enum':
				// Handle special enum fields.
				if ( 'type' === $field_key ) {
					return in_array( strtolower( $value ), [ 'earned', 'spent' ], true ) ? strtolower( $value ) : 'earned';
				}
				if ( 'status' === $field_key ) {
					return in_array( strtolower( $value ), [ 'active', 'blocked', 'pending', 'excluded' ], true ) ? strtolower( $value ) : 'active';
				}
				if ( 'status_points' === $field_key ) {
					return in_array( strtolower( $value ), [ 'pending', 'approved', 'expired', 'revoked' ], true ) ? strtolower( $value ) : 'approved';
				}
				return $value;

			default:
				return sanitize_text_field( $value );
		}
	}

	/**
	 * Get mapping options for SelectComponentView.
	 *
	 * @since 1.0.0
	 *
	 * @return array Options array formatted for SelectComponentView.
	 */
	public function get_mapping_options(): array {
		$options = [
			''     => esc_html__( '-- Select Field --', 'rewardswp' ),
			'skip' => esc_html__( '-- Skip Column --', 'rewardswp' ),
		];

		$grouped = $this->get_target_fields_grouped();

		foreach ( $grouped as $group_key => $fields ) {
			$group_label = $this->get_group_label( $group_key );

			foreach ( $fields as $field_key => $field_config ) {
				$label = $field_config['label'];

				$options[ $field_key ] = sprintf(
					'[%s] %s',
					$group_label,
					$label
				);
			}
		}

		return $options;
	}

	/**
	 * Get group label for display.
	 *
	 * @since 1.0.0
	 *
	 * @param string $group_key Group key.
	 *
	 * @return string Group label.
	 */
	private function get_group_label( string $group_key ): string {
		$labels = [
			'member_identification' => esc_html__( 'Member', 'rewardswp' ),
			'member_optional'       => esc_html__( 'Member', 'rewardswp' ),
			'points'                => esc_html__( 'Points', 'rewardswp' ),
		];

		return $labels[ $group_key ] ?? ucfirst( str_replace( '_', ' ', $group_key ) );
	}
}
