<?php
/**
 * Map PMPro membership levels to Square subscription plan variations.
 *
 * @package SHC4WC
 */

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

/**
 * Resolves Square catalog plan variations from PMPro level billing data.
 */
class SHC4WC_PMPro_Plans {

	const LEVEL_META_VARIATION = '_shc4wc_pmpro_square_plan_variation_id';
	const OPTION_PLAN_MAP      = 'shc4wc_pmpro_square_plan_map';

	/**
	 * @param string $cycle_period PMPro cycle period.
	 * @return string Square cadence.
	 */
	public static function map_cadence( $cycle_period ) {
		$map = array(
			'Day'   => 'DAILY',
			'Week'  => 'WEEKLY',
			'Month' => 'MONTHLY',
			'Year'  => 'ANNUAL',
		);

		$cycle_period = sanitize_text_field( (string) $cycle_period );
		return isset( $map[ $cycle_period ] ) ? $map[ $cycle_period ] : 'MONTHLY';
	}

	/**
	 * @param object $level PMPro level object.
	 * @return string
	 */
	public static function level_cache_key( $level ) {
		if ( ! is_object( $level ) ) {
			return '';
		}

		return 'pmpro_' . md5(
			wp_json_encode(
				array(
					'id'             => isset( $level->id ) ? (int) $level->id : 0,
					'billing_amount' => isset( $level->billing_amount ) ? (float) $level->billing_amount : 0,
					'cycle_number'   => isset( $level->cycle_number ) ? (int) $level->cycle_number : 1,
					'cycle_period'   => isset( $level->cycle_period ) ? (string) $level->cycle_period : 'Month',
				)
			)
		);
	}

	/**
	 * Find a matching Square variation from the connected catalog.
	 *
	 * @param object $level PMPro level.
	 * @return string|null
	 */
	public static function find_matching_catalog_variation( $level ) {
		if ( ! is_object( $level ) || ! class_exists( 'SHC4WC_Subscriptions' ) ) {
			return null;
		}

		$target_amount  = isset( $level->billing_amount ) ? round( (float) $level->billing_amount, 2 ) : 0;
		$target_cadence = self::map_cadence( isset( $level->cycle_period ) ? $level->cycle_period : 'Month' );

		foreach ( SHC4WC_Subscriptions::get_plan_variations() as $variation ) {
			if ( round( (float) $variation['amount'], 2 ) === $target_amount && strtoupper( (string) $variation['cadence'] ) === $target_cadence ) {
				return (string) $variation['id'];
			}
		}

		return null;
	}

	/**
	 * Create a STATIC-priced Square subscription plan variation for a PMPro level.
	 *
	 * @param object $level    PMPro level.
	 * @param string $currency ISO currency.
	 * @return string|WP_Error Variation id.
	 */
	public static function create_static_plan_variation( $level, $currency ) {
		if ( ! is_object( $level ) ) {
			return new WP_Error( 'shc4wc_pmpro_invalid_level', __( 'Invalid membership level.', 'payments-connect-square' ) );
		}

		$amount = isset( $level->billing_amount ) ? (float) $level->billing_amount : 0;
		if ( $amount <= 0 ) {
			return new WP_Error( 'shc4wc_pmpro_invalid_amount', __( 'Recurring billing amount must be greater than zero.', 'payments-connect-square' ) );
		}

		$api          = new SHC4WC_API( SHC4WC_Plugin::get_settings() );
		$amount_minor = (int) round( $amount * 100 );
		$currency     = strtoupper( sanitize_text_field( (string) $currency ) );
		$name         = isset( $level->name ) ? (string) $level->name : 'PMPro Level ' . (int) $level->id;
		$cadence      = self::map_cadence( isset( $level->cycle_period ) ? $level->cycle_period : 'Month' );
		$cycle_number = max( 1, isset( $level->cycle_number ) ? (int) $level->cycle_number : 1 );
		$plan_temp_id = '#pmpro_plan_' . wp_generate_password( 8, false, false );
		$var_temp_id  = '#pmpro_var_' . wp_generate_password( 8, false, false );

		// Square requires plan + variation as separate catalog objects (linked by subscription_plan_id).
		$response = $api->request(
			'POST',
			'/v2/catalog/batch-upsert',
			array(
				'idempotency_key' => wp_generate_uuid4(),
				'batches'         => array(
					array(
						'objects' => array(
							array(
								'type'                   => 'SUBSCRIPTION_PLAN',
								'id'                     => $plan_temp_id,
								'subscription_plan_data' => array(
									'name' => sanitize_text_field( $name ),
								),
							),
							array(
								'type'                             => 'SUBSCRIPTION_PLAN_VARIATION',
								'id'                               => $var_temp_id,
								'subscription_plan_variation_data' => array(
									'name'                 => sanitize_text_field( $name ),
									'subscription_plan_id' => $plan_temp_id,
									'phases'               => array(
										array(
											'cadence' => $cadence,
											'periods' => $cycle_number,
											'ordinal' => 0,
											'pricing' => array(
												'type'  => 'STATIC',
												'price' => array(
													'amount'   => $amount_minor,
													'currency' => $currency,
												),
											),
										),
									),
								),
							),
						),
					),
				),
			)
		);

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

		$variation_id = self::resolve_catalog_id_from_response( $response, $var_temp_id, 'SUBSCRIPTION_PLAN_VARIATION' );
		if ( '' === $variation_id ) {
			return new WP_Error( 'shc4wc_pmpro_plan_missing', __( 'Square did not return a subscription plan variation id.', 'payments-connect-square' ), $response );
		}

		delete_transient( SHC4WC_Subscriptions::CACHE_KEY );
		return $variation_id;
	}

	/**
	 * Resolve a server catalog id from Square upsert/batch-upsert responses.
	 *
	 * @param array<string,mixed> $response   Square API response.
	 * @param string              $client_id  Temporary # client id.
	 * @param string              $object_type Expected catalog object type fallback.
	 * @return string
	 */
	private static function resolve_catalog_id_from_response( $response, $client_id, $object_type = '' ) {
		if ( ! is_array( $response ) ) {
			return '';
		}

		if ( ! empty( $response['id_mappings'] ) && is_array( $response['id_mappings'] ) ) {
			foreach ( $response['id_mappings'] as $mapping ) {
				if ( ! is_array( $mapping ) ) {
					continue;
				}
				if ( ! empty( $mapping['client_object_id'] ) && $client_id === $mapping['client_object_id'] && ! empty( $mapping['object_id'] ) ) {
					return sanitize_text_field( (string) $mapping['object_id'] );
				}
			}
		}

		$objects = array();
		if ( ! empty( $response['objects'] ) && is_array( $response['objects'] ) ) {
			$objects = $response['objects'];
		} elseif ( ! empty( $response['catalog_object'] ) && is_array( $response['catalog_object'] ) ) {
			$objects = array( $response['catalog_object'] );
		}

		foreach ( $objects as $object ) {
			if ( ! is_array( $object ) || empty( $object['id'] ) ) {
				continue;
			}
			if ( '' !== $object_type && ( empty( $object['type'] ) || $object_type !== $object['type'] ) ) {
				continue;
			}
			return sanitize_text_field( (string) $object['id'] );
		}

		return '';
	}

	/**
	 * Resolve the Square plan variation id for a recurring PMPro level.
	 *
	 * @param object $level    PMPro level.
	 * @param string $currency ISO currency.
	 * @return string|WP_Error
	 */
	public static function resolve_variation_for_level( $level, $currency ) {
		if ( ! is_object( $level ) || empty( $level->id ) ) {
			return new WP_Error( 'shc4wc_pmpro_invalid_level', __( 'Invalid membership level.', 'payments-connect-square' ) );
		}

		$manual = self::get_level_meta( (int) $level->id, self::LEVEL_META_VARIATION );
		if ( is_string( $manual ) && '' !== $manual ) {
			return sanitize_text_field( $manual );
		}

		/**
		 * Filter a Square subscription plan variation id for a PMPro level.
		 *
		 * @param string $variation_id Empty by default.
		 * @param object $level        PMPro level object.
		 */
		$filtered = apply_filters( 'shc4wc_pmpro_square_plan_variation_id', '', $level );
		if ( is_string( $filtered ) && '' !== $filtered ) {
			return sanitize_text_field( $filtered );
		}

		$cache_key = self::level_cache_key( $level );
		$map       = get_option( self::OPTION_PLAN_MAP, array() );
		$map       = is_array( $map ) ? $map : array();
		if ( ! empty( $map[ $cache_key ] ) ) {
			return sanitize_text_field( (string) $map[ $cache_key ] );
		}

		$matched = self::find_matching_catalog_variation( $level );
		if ( $matched ) {
			$map[ $cache_key ] = $matched;
			update_option( self::OPTION_PLAN_MAP, $map, false );
			return $matched;
		}

		$created = self::create_static_plan_variation( $level, $currency );
		if ( is_wp_error( $created ) ) {
			return $created;
		}

		$map[ $cache_key ] = $created;
		update_option( self::OPTION_PLAN_MAP, $map, false );

		return $created;
	}

	/**
	 * @param int    $level_id Level id.
	 * @param string $key      Meta key.
	 * @return string
	 */
	public static function get_level_meta( $level_id, $key ) {
		if ( function_exists( 'pmpro_getMembershipLevelMeta' ) ) {
			$value = pmpro_getMembershipLevelMeta( (int) $level_id, $key, true );
			return is_string( $value ) ? $value : '';
		}
		if ( function_exists( 'get_pmpro_membership_level_meta' ) ) {
			$value = get_pmpro_membership_level_meta( (int) $level_id, $key, true );
			return is_string( $value ) ? $value : '';
		}
		return '';
	}

	/**
	 * @param int    $level_id Level id.
	 * @param string $key      Meta key.
	 * @param string $value    Meta value.
	 * @return void
	 */
	public static function update_level_meta( $level_id, $key, $value ) {
		if ( function_exists( 'pmpro_updateMembershipLevelMeta' ) ) {
			pmpro_updateMembershipLevelMeta( (int) $level_id, $key, $value );
			return;
		}
		if ( function_exists( 'update_pmpro_membership_level_meta' ) ) {
			update_pmpro_membership_level_meta( (int) $level_id, $key, $value );
		}
	}

	/**
	 * @param int    $level_id Level id.
	 * @param string $key      Meta key.
	 * @return void
	 */
	public static function delete_level_meta( $level_id, $key ) {
		if ( function_exists( 'pmpro_deleteMembershipLevelMeta' ) ) {
			pmpro_deleteMembershipLevelMeta( (int) $level_id, $key );
			return;
		}
		if ( function_exists( 'delete_pmpro_membership_level_meta' ) ) {
			delete_pmpro_membership_level_meta( (int) $level_id, $key );
		}
	}
}
