<?php
/**
 * AppSumo — codes LTD, redeem AJAX, essai Guardian 15j (Stripe).
 *
 * @package Avision
 */

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

if ( ! defined( 'AVI_APPSUMO_GUARDIAN_TRIAL_DAYS' ) ) {
	define( 'AVI_APPSUMO_GUARDIAN_TRIAL_DAYS', 15 );
}

/**
 * Clé secrète Stripe (S11 AppSumo + Elements) : constante dédiée puis legacy plugin.
 *
 * @return string
 */
function avi_appsumo_get_stripe_secret_key(): string {
	if ( defined( 'AVI_STRIPE_SECRET_KEY' ) && is_string( AVI_STRIPE_SECRET_KEY ) && trim( AVI_STRIPE_SECRET_KEY ) !== '' ) {
		return trim( AVI_STRIPE_SECRET_KEY );
	}
	if ( defined( 'AVISION_STRIPE_SECRET_KEY' ) && is_string( AVISION_STRIPE_SECRET_KEY ) && trim( AVISION_STRIPE_SECRET_KEY ) !== '' ) {
		return trim( AVISION_STRIPE_SECRET_KEY );
	}
	return trim( (string) get_option( 'avision_stripe_secret_key', '' ) );
}

/**
 * Clé publique Stripe.js (pk_…).
 *
 * @return string
 */
function avi_appsumo_get_stripe_publishable_key(): string {
	if ( defined( 'AVI_STRIPE_PUBLIC_KEY' ) && is_string( AVI_STRIPE_PUBLIC_KEY ) ) {
		$k = trim( AVI_STRIPE_PUBLIC_KEY );
		if ( strpos( $k, 'pk_' ) === 0 ) {
			return $k;
		}
	}
	$opt = trim( (string) get_option( 'avi_stripe_public_key', '' ) );
	if ( strpos( $opt, 'pk_' ) === 0 ) {
		return $opt;
	}
	if ( defined( 'AVISION_STRIPE_PUBLISHABLE_KEY' ) && is_string( AVISION_STRIPE_PUBLISHABLE_KEY ) ) {
		$k = trim( AVISION_STRIPE_PUBLISHABLE_KEY );
		if ( strpos( $k, 'pk_' ) === 0 ) {
			return $k;
		}
	}
	return '';
}

/**
 * URL dashboard après redeem sans essai Guardian.
 *
 * @return string
 */
function avi_appsumo_skip_guardian_trial_redirect_url(): string {
	return (string) apply_filters(
		'avi_appsumo_skip_guardian_trial_redirect_url',
		add_query_arg( 'appsumo_welcome', '1', trailingslashit( home_url( '/premium-dashboard/' ) ) )
	);
}

/**
 * URL dashboard après activation essai Guardian (Stripe).
 *
 * @return string
 */
function avi_appsumo_after_guardian_trial_redirect_url(): string {
	return (string) apply_filters(
		'avi_appsumo_after_guardian_trial_redirect_url',
		add_query_arg(
			array(
				'appsumo_welcome' => '1',
				'guardian'        => '1',
			),
			trailingslashit( home_url( '/premium-dashboard/' ) )
		)
	);
}

/**
 * URL agence après redeem sans essai Guardian (LTD AppSumo).
 *
 * @return string
 */
function avi_appsumo_skip_guardian_trial_redirect_agency_url(): string {
	return (string) apply_filters(
		'avi_appsumo_skip_guardian_trial_redirect_agency_url',
		add_query_arg( 'appsumo_welcome', '1', trailingslashit( home_url( '/agency-dashboard/' ) ) )
	);
}

/**
 * URL agence après activation essai Guardian (Stripe).
 *
 * @return string
 */
function avi_appsumo_after_guardian_trial_redirect_agency_url(): string {
	return (string) apply_filters(
		'avi_appsumo_after_guardian_trial_redirect_agency_url',
		add_query_arg(
			array(
				'appsumo_welcome' => '1',
				'guardian'        => '1',
			),
			trailingslashit( home_url( '/agency-dashboard/' ) )
		)
	);
}

/**
 * Migrations idempotentes : table codes + colonnes profil.
 *
 * @return void
 */
function avi_appsumo_migrate_db(): void {
	if ( ! isset( $GLOBALS['wpdb'] ) || ! is_object( $GLOBALS['wpdb'] ) ) {
		return;
	}
	global $wpdb;
	$done = get_option( 'avi_appsumo_schema_migrated', '' );
	if ( $done === '1.0' ) {
		return;
	}

	$table_codes = $wpdb->prefix . 'avi_appsumo_codes';
	$charset       = $wpdb->get_charset_collate();
	// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- nom de table contrôlé.
	$sql = "CREATE TABLE {$table_codes} (
		id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
		code VARCHAR(64) NOT NULL,
		plan_type VARCHAR(32) NOT NULL DEFAULT 'solo',
		status VARCHAR(20) NOT NULL DEFAULT 'unused',
		claimed_by BIGINT UNSIGNED NULL,
		claimed_at DATETIME NULL,
		created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
		PRIMARY KEY  (id),
		UNIQUE KEY uk_avi_appsumo_code (code),
		KEY idx_avi_appsumo_status (status)
	) {$charset};";
	require_once ABSPATH . 'wp-admin/includes/upgrade.php';
	dbDelta( $sql );

	$profiles = $wpdb->prefix . 'avi_saas_profiles';
	if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $profiles ) ) === $profiles ) {
		$cols = array(
			'ltd_code'                    => "ADD COLUMN `ltd_code` VARCHAR(64) NULL COMMENT 'Code AppSumo utilisé'",
			'ltd_guardian_trial_start'    => "ADD COLUMN `ltd_guardian_trial_start` DATETIME NULL COMMENT 'Début trial Guardian 15j'",
			'ltd_guardian_trial_expires'  => "ADD COLUMN `ltd_guardian_trial_expires` DATETIME NULL COMMENT 'Expiration trial Guardian'",
		);
		foreach ( $cols as $name => $ddl ) {
			if ( $wpdb->get_var( $wpdb->prepare( "SHOW COLUMNS FROM `{$profiles}` LIKE %s", $name ) ) !== $name ) {
				// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- fragment DDL contrôlé.
				$wpdb->query( "ALTER TABLE `{$profiles}` {$ddl}" );
			}
		}
	}

	update_option( 'avi_appsumo_schema_migrated', '1.0', false );
}

/**
 * Filtre quota agence : plafond LTD AppSumo (user meta avi_ltd_max_listings).
 *
 * @param int $quota Quota calculé par le thème / filtres existants.
 * @return int
 */
function avi_appsumo_filter_agency_quota( int $quota ): int {
	if ( ! function_exists( 'is_user_logged_in' ) || ! is_user_logged_in() ) {
		return $quota;
	}
	$uid = (int) get_current_user_id();
	$cap = get_user_meta( $uid, 'avi_ltd_max_listings', true );
	if ( $cap === '' || $cap === false ) {
		return $quota;
	}
	$cap = (int) $cap;
	if ( $cap <= 0 ) {
		return $quota;
	}
	return min( $quota, $cap );
}

/**
 * Crée une ligne agence + lie le profil (plan LTD agence) si besoin.
 *
 * @param int $wp_user_id        ID WordPress.
 * @param string $plan_type      agency_5|agency_20|unlimited.
 * @param int $profile_row_id    ID ligne `avi_saas_profiles.id`.
 * @return void
 */
function avi_appsumo_ensure_agency_for_ltd( int $wp_user_id, string $plan_type, int $profile_row_id ): void {
	global $wpdb;
	$wp_user_id      = (int) $wp_user_id;
	$profile_row_id  = (int) $profile_row_id;
	if ( $wp_user_id <= 0 || $profile_row_id <= 0 ) {
		return;
	}
	$ag_table = $wpdb->prefix . 'avi_saas_agencies';
	$pr_table  = $wpdb->prefix . 'avi_saas_profiles';
	if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $ag_table ) ) !== $ag_table ) {
		return;
	}
	$existing = (int) $wpdb->get_var(
		$wpdb->prepare(
			"SELECT id FROM `{$ag_table}` WHERE admin_user_id = %d LIMIT 1",
			$wp_user_id
		)
	);
	$agency_id = $existing;
	if ( $agency_id <= 0 ) {
		$ag_cols = $wpdb->get_col( "SHOW COLUMNS FROM `{$ag_table}`", 0 );
		$ag_cols = is_array( $ag_cols ) ? $ag_cols : array();
		$user     = get_userdata( $wp_user_id );
		$disp     = $user ? (string) $user->display_name : 'Agency';
		$max_map  = array(
			'agency_5'   => 5,
			'agency_20'  => 20,
			'unlimited'  => 9999,
		);
		$max_list = isset( $max_map[ $plan_type ] ) ? (int) $max_map[ $plan_type ] : 5;
		$row      = array();
		$fmt      = array();
		if ( in_array( 'uuid', $ag_cols, true ) && function_exists( 'wp_generate_uuid4' ) ) {
			$row['uuid'] = wp_generate_uuid4();
			$fmt[]       = '%s';
		}
		if ( in_array( 'name', $ag_cols, true ) ) {
			$row['name'] = $disp !== '' ? $disp . ' Agency' : 'Agency';
			$fmt[]       = '%s';
		}
		if ( in_array( 'admin_user_id', $ag_cols, true ) ) {
			$row['admin_user_id'] = $wp_user_id;
			$fmt[]                = '%d';
		}
		if ( in_array( 'created_at', $ag_cols, true ) ) {
			$row['created_at'] = current_time( 'mysql', true );
			$fmt[]             = '%s';
		}
		if ( in_array( 'max_listings', $ag_cols, true ) ) {
			$row['max_listings'] = $max_list;
			$fmt[]               = '%d';
		}
		if ( in_array( 'agency_tier', $ag_cols, true ) ) {
			$row['agency_tier'] = 'ltd';
			$fmt[]              = '%s';
		}
		if ( count( $row ) > 0 ) {
			$wpdb->insert( $ag_table, $row, $fmt );
			$agency_id = (int) $wpdb->insert_id;
		}
	}
	if ( $agency_id <= 0 ) {
		return;
	}
	$pr_cols = $wpdb->get_col( "SHOW COLUMNS FROM `{$pr_table}`", 0 );
	$pr_cols = is_array( $pr_cols ) ? $pr_cols : array();
	if ( in_array( 'agency_id', $pr_cols, true ) ) {
		$wpdb->update(
			$pr_table,
			array( 'agency_id' => $agency_id ),
			array( 'id' => $profile_row_id ),
			array( '%d' ),
			array( '%d' )
		);
	}
}

/**
 * Active un plan LTD AppSumo (profil SaaS + métas utilisateur).
 *
 * @param int    $wp_user_id     ID WordPress.
 * @param string $plan_type      solo|agency_5|agency_20|unlimited.
 * @param string $code           Code saisi (déjà validé).
 * @param string $account_type Soumission UI (`individual`|`agency`) ; le plan impose le routage final (filtre `avi_activate_ltd_routing_account_type`).
 * @return bool|\WP_Error
 */
function avi_activate_ltd_plan( int $wp_user_id, string $plan_type, string $code, string $account_type = 'individual' ) {
	global $wpdb;
	$wp_user_id = (int) $wp_user_id;
	if ( $wp_user_id <= 0 || $code === '' ) {
		return new \WP_Error( 'avi_appsumo_param', 'invalid' );
	}
	$allowed = array( 'solo', 'agency_5', 'agency_20', 'unlimited' );
	if ( ! in_array( $plan_type, $allowed, true ) ) {
		$plan_type = 'solo';
	}
	$is_agency_plan = in_array( $plan_type, array( 'agency_5', 'agency_20', 'unlimited' ), true );
	$account_type_submitted = sanitize_key( (string) $account_type );
	if ( ! in_array( $account_type_submitted, array( 'individual', 'agency' ), true ) ) {
		$account_type_submitted = 'individual';
	}
	// Le plan LTD impose le compte (un code solo ne peut pas devenir agence).
	$account_type = $is_agency_plan ? 'agency' : 'individual';
	$account_type = (string) apply_filters(
		'avi_activate_ltd_routing_account_type',
		$account_type,
		$wp_user_id,
		$plan_type,
		$account_type_submitted
	);

	$u = get_userdata( $wp_user_id );
	if ( ! $u || ! is_email( $u->user_email ) ) {
		return new \WP_Error( 'avi_appsumo_user', 'invalid_user' );
	}
	$email = strtolower( trim( (string) $u->user_email ) );

	update_user_meta( $wp_user_id, 'avi_payment_type', 'ltd' );

	if ( $plan_type === 'solo' ) {
		update_user_meta( $wp_user_id, 'avi_account_type', 'premium' );
		update_user_meta( $wp_user_id, 'avi_user_type', 'b2c' );
		delete_user_meta( $wp_user_id, 'avi_ltd_max_listings' );
	} else {
		update_user_meta( $wp_user_id, 'avi_account_type', 'agency' );
		update_user_meta( $wp_user_id, 'avi_user_type', 'b2b' );
		$cap = ( $plan_type === 'agency_5' ) ? 5 : ( ( $plan_type === 'agency_20' ) ? 20 : 9999 );
		update_user_meta( $wp_user_id, 'avi_ltd_max_listings', $cap );
		if ( function_exists( 'avision_force_b2b_saas_profile_row' ) ) {
			$vol = ( $plan_type === 'agency_5' ) ? 5 : ( ( $plan_type === 'agency_20' ) ? 20 : 500 );
			avision_force_b2b_saas_profile_row( $wp_user_id, $email, '', $vol );
		}
	}

	$table = $wpdb->prefix . 'avi_saas_profiles';
	if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ) !== $table ) {
		return new \WP_Error( 'avi_appsumo_table', 'no_profiles_table' );
	}

	$cols = $wpdb->get_col( "SHOW COLUMNS FROM `{$table}`", 0 );
	$cols = is_array( $cols ) ? $cols : array();
	$now  = current_time( 'mysql' );

	$update = array();
	if ( in_array( 'ltd_code', $cols, true ) ) {
		$update['ltd_code'] = $code;
	}
	if ( $plan_type === 'solo' ) {
		if ( in_array( 'radar_plan', $cols, true ) ) {
			$update['radar_plan'] = 'offert';
		}
		if ( in_array( 'max_keywords', $cols, true ) ) {
			$update['max_keywords'] = 20;
		}
		if ( in_array( 'guardian_active', $cols, true ) ) {
			$update['guardian_active'] = 0;
		}
		if ( in_array( 'guardian_scan_enabled', $cols, true ) ) {
			$update['guardian_scan_enabled'] = 0;
		}
		if ( in_array( 'user_type', $cols, true ) ) {
			$update['user_type'] = 'b2c';
		}
	} else {
		if ( in_array( 'user_type', $cols, true ) ) {
			$update['user_type'] = 'agency';
		}
		if ( in_array( 'radar_plan', $cols, true ) ) {
			$rp = ( $plan_type === 'agency_5' ) ? 'agency_tier1' : ( ( $plan_type === 'agency_20' ) ? 'agency_tier2' : 'agency_tier3' );
			$update['radar_plan'] = $rp;
		}
	}
	if ( in_array( 'email', $cols, true ) ) {
		$update['email'] = $email;
	}
	if ( in_array( 'updated_at', $cols, true ) ) {
		$update['updated_at'] = $now;
	}
	if ( in_array( 'payment_type', $cols, true ) ) {
		$update['payment_type'] = 'ltd';
	}
	if ( in_array( 'account_type', $cols, true ) ) {
		$update['account_type'] = $account_type === 'agency' ? 'agency' : 'individual';
	}

	$pid = 0;
	if ( in_array( 'wp_user_id', $cols, true ) ) {
		$pid = (int) $wpdb->get_var( $wpdb->prepare( "SELECT id FROM `{$table}` WHERE wp_user_id = %d ORDER BY id ASC LIMIT 1", $wp_user_id ) );
	}
	if ( $pid <= 0 && in_array( 'user_id', $cols, true ) ) {
		$pid = (int) $wpdb->get_var( $wpdb->prepare( "SELECT id FROM `{$table}` WHERE user_id = %d ORDER BY id ASC LIMIT 1", $wp_user_id ) );
	}
	if ( $pid <= 0 ) {
		$pid = (int) $wpdb->get_var( $wpdb->prepare( "SELECT id FROM `{$table}` WHERE LOWER(TRIM(COALESCE(email,''))) = %s ORDER BY id ASC LIMIT 1", $email ) );
	}

	if ( $pid > 0 && ! empty( $update ) ) {
		$wpdb->update( $table, $update, array( 'id' => $pid ) );
		if ( $account_type === 'agency' ) {
			avi_appsumo_ensure_agency_for_ltd( $wp_user_id, $plan_type, $pid );
		}
		return true;
	}

	// Création profil minimal B2C si solo et aucune ligne.
	if ( $plan_type === 'solo' ) {
		$ins  = array();
		$fmt  = array();
		if ( in_array( 'email', $cols, true ) ) {
			$ins['email'] = $email;
			$fmt[]        = '%s';
		}
		if ( in_array( 'business_name', $cols, true ) ) {
			$ins['business_name'] = 'AppSumo';
			$fmt[]                = '%s';
		}
		if ( in_array( 'city', $cols, true ) ) {
			$ins['city'] = '-';
			$fmt[]       = '%s';
		}
		if ( in_array( 'category', $cols, true ) ) {
			$ins['category'] = 'Client';
			$fmt[]           = '%s';
		}
		if ( in_array( 'wp_user_id', $cols, true ) ) {
			$ins['wp_user_id'] = $wp_user_id;
			$fmt[]             = '%d';
		}
		if ( in_array( 'user_id', $cols, true ) ) {
			$ins['user_id'] = $wp_user_id;
			$fmt[]          = '%d';
		}
		if ( in_array( 'uuid', $cols, true ) && function_exists( 'wp_generate_uuid4' ) ) {
			$ins['uuid'] = wp_generate_uuid4();
			$fmt[]       = '%s';
		}
		if ( in_array( 'radar_plan', $cols, true ) ) {
			$ins['radar_plan'] = 'offert';
			$fmt[]             = '%s';
		}
		if ( in_array( 'max_keywords', $cols, true ) ) {
			$ins['max_keywords'] = 20;
			$fmt[]               = '%d';
		}
		if ( in_array( 'user_type', $cols, true ) ) {
			$ins['user_type'] = 'b2c';
			$fmt[]            = '%s';
		}
		if ( in_array( 'ltd_code', $cols, true ) ) {
			$ins['ltd_code'] = $code;
			$fmt[]           = '%s';
		}
		if ( in_array( 'created_at', $cols, true ) ) {
			$ins['created_at'] = $now;
			$fmt[]             = '%s';
		}
		if ( in_array( 'updated_at', $cols, true ) ) {
			$ins['updated_at'] = $now;
			$fmt[]             = '%s';
		}
		if ( in_array( 'payment_type', $cols, true ) ) {
			$ins['payment_type'] = 'ltd';
			$fmt[]               = '%s';
		}
		if ( in_array( 'account_type', $cols, true ) ) {
			$ins['account_type'] = 'individual';
			$fmt[]               = '%s';
		}
		if ( count( $ins ) > 0 ) {
			$wpdb->insert( $table, $ins, $fmt );
		}
		return true;
	}

	// Agence : le profil peut avoir été créé uniquement sur wor1515_ par avision_force_b2b_saas_profile_row — considérer les méta LTD comme suffisantes.
	if ( $plan_type !== 'solo' ) {
		if ( $account_type === 'agency' ) {
			$pid2 = 0;
			if ( in_array( 'wp_user_id', $cols, true ) ) {
				$pid2 = (int) $wpdb->get_var( $wpdb->prepare( "SELECT id FROM `{$table}` WHERE wp_user_id = %d ORDER BY id ASC LIMIT 1", $wp_user_id ) );
			}
			if ( $pid2 <= 0 && in_array( 'user_id', $cols, true ) ) {
				$pid2 = (int) $wpdb->get_var( $wpdb->prepare( "SELECT id FROM `{$table}` WHERE user_id = %d ORDER BY id ASC LIMIT 1", $wp_user_id ) );
			}
			if ( $pid2 > 0 ) {
				avi_appsumo_ensure_agency_for_ltd( $wp_user_id, $plan_type, $pid2 );
			}
		}
		return true;
	}

	return new \WP_Error( 'avi_appsumo_profile', 'profile_missing' );
}

/**
 * POST Stripe API v1 (application/x-www-form-urlencoded).
 *
 * @param string               $path   ex. subscriptions
 * @param array<string, mixed> $fields
 * @return array{ok:bool, code:int, body:array|string}
 */
function avi_appsumo_stripe_api_post( string $path, array $fields ): array {
	$sk = avi_appsumo_get_stripe_secret_key();
	if ( $sk === '' ) {
		return array( 'ok' => false, 'code' => 0, 'body' => array( 'error' => array( 'message' => 'no_secret' ) ) );
	}
	$path = ltrim( $path, '/' );
	$res  = wp_remote_post(
		'https://api.stripe.com/v1/' . $path,
		array(
			'timeout' => 20,
			'headers' => array(
				'Authorization' => 'Bearer ' . $sk,
				'Content-Type'  => 'application/x-www-form-urlencoded',
			),
			'body'    => $fields,
		)
	);
	if ( is_wp_error( $res ) ) {
		return array( 'ok' => false, 'code' => 0, 'body' => array( 'error' => array( 'message' => $res->get_error_message() ) ) );
	}
	$code = (int) wp_remote_retrieve_response_code( $res );
	$raw  = wp_remote_retrieve_body( $res );
	$data = json_decode( $raw, true );
	if ( ! is_array( $data ) ) {
		$data = array( 'raw' => $raw );
	}
	return array(
		'ok'   => $code >= 200 && $code < 300,
		'code' => $code,
		'body' => $data,
	);
}

/**
 * Crée ou récupère l’ID client Stripe pour un utilisateur WordPress.
 *
 * @param int $wp_user_id ID WP.
 * @return string|\WP_Error ID cus_… ou erreur.
 */
function avi_appsumo_ensure_stripe_customer_id( int $wp_user_id ) {
	$wp_user_id = (int) $wp_user_id;
	if ( $wp_user_id <= 0 ) {
		return new \WP_Error( 'avi_appsumo_user', 'invalid' );
	}
	$user = get_userdata( $wp_user_id );
	if ( ! $user || ! is_email( $user->user_email ) ) {
		return new \WP_Error( 'avi_appsumo_user', 'invalid' );
	}
	$email = (string) $user->user_email;

	$cus_meta = get_user_meta( $wp_user_id, 'avi_stripe_customer_id', true );
	$cus      = is_string( $cus_meta ) ? trim( $cus_meta ) : '';
	if ( $cus !== '' && strpos( $cus, 'cus_' ) === 0 ) {
		return $cus;
	}

	$cres = avi_appsumo_stripe_api_post(
		'customers',
		array(
			'email'                       => $email,
			'metadata[user_id]'           => (string) $wp_user_id,
			'metadata[avi_appsumo_trial]' => '1',
		)
	);
	if ( ! $cres['ok'] || empty( $cres['body']['id'] ) ) {
		$msg = isset( $cres['body']['error']['message'] ) ? (string) $cres['body']['error']['message'] : 'customer_failed';
		return new \WP_Error( 'avi_appsumo_stripe', $msg );
	}
	$cus = (string) $cres['body']['id'];
	update_user_meta( $wp_user_id, 'avi_stripe_customer_id', $cus );
	return $cus;
}

/**
 * Crée un SetupIntent Stripe (usage hors session) pour enregistrer une CB.
 *
 * @param int $wp_user_id Utilisateur connecté (LTD AppSumo).
 * @return array{client_secret:string}|\WP_Error
 */
function avi_appsumo_create_setup_intent_for_user( int $wp_user_id ) {
	if ( get_user_meta( $wp_user_id, 'avi_payment_type', true ) !== 'ltd' ) {
		return new \WP_Error( 'avi_appsumo_ltd', 'not_ltd_account' );
	}
	$cus = avi_appsumo_ensure_stripe_customer_id( $wp_user_id );
	if ( is_wp_error( $cus ) ) {
		return $cus;
	}
	$sir = avi_appsumo_stripe_api_post(
		'setup_intents',
		array(
			'customer'            => $cus,
			'usage'               => 'off_session',
			'metadata[user_id]'   => (string) $wp_user_id,
			'metadata[avi_flow]'  => 'appsumo_guardian_trial',
		)
	);
	if ( ! $sir['ok'] || empty( $sir['body']['client_secret'] ) ) {
		$msg = isset( $sir['body']['error']['message'] ) ? (string) $sir['body']['error']['message'] : 'setup_intent_failed';
		return new \WP_Error( 'avi_appsumo_setup_intent', $msg );
	}
	return array( 'client_secret' => (string) $sir['body']['client_secret'] );
}

/**
 * Crée un abonnement Guardian avec essai 15 jours (Stripe) + MAJ profil.
 *
 * @param int    $wp_user_id        Utilisateur connecté.
 * @param string $payment_method_id pm_….
 * @return array{redirect:string}|\WP_Error
 */
function avi_appsumo_create_guardian_trial_subscription( int $wp_user_id, string $payment_method_id ) {
	global $wpdb;
	$wp_user_id         = (int) $wp_user_id;
	$payment_method_id  = preg_replace( '/[^a-zA-Z0-9_]/', '', (string) $payment_method_id );
	if ( $wp_user_id <= 0 || $payment_method_id === '' || strpos( $payment_method_id, 'pm_' ) !== 0 ) {
		return new \WP_Error( 'avi_appsumo_pm', 'invalid_payment_method' );
	}
	if ( get_user_meta( $wp_user_id, 'avi_payment_type', true ) !== 'ltd' ) {
		return new \WP_Error( 'avi_appsumo_ltd', 'not_ltd_account' );
	}
	$price_key = 'avi_stripe_price_guardian';
	$price_id  = trim( (string) get_option( $price_key, '' ) );
	if ( $price_id === '' || strpos( $price_id, 'price_XXXX' ) === 0 ) {
		return new \WP_Error( 'avi_appsumo_price', 'guardian_price_not_configured' );
	}

	$user = get_userdata( $wp_user_id );
	if ( ! $user || ! is_email( $user->user_email ) ) {
		return new \WP_Error( 'avi_appsumo_user', 'invalid' );
	}

	$cus = avi_appsumo_ensure_stripe_customer_id( $wp_user_id );
	if ( is_wp_error( $cus ) ) {
		return $cus;
	}

	$att = avi_appsumo_stripe_api_post(
		'payment_methods/' . rawurlencode( $payment_method_id ) . '/attach',
		array( 'customer' => $cus )
	);
	if ( ! $att['ok'] ) {
		$msg  = isset( $att['body']['error']['message'] ) ? (string) $att['body']['error']['message'] : 'attach_failed';
		$code = isset( $att['body']['error']['code'] ) ? (string) $att['body']['error']['code'] : '';
		if ( stripos( $msg, 'already been attached' ) === false && $code !== 'resource_already_exists' ) {
			return new \WP_Error( 'avi_appsumo_attach', $msg );
		}
	}

	$def = avi_appsumo_stripe_api_post(
		'customers/' . rawurlencode( $cus ),
		array(
			'invoice_settings[default_payment_method]' => $payment_method_id,
		)
	);
	if ( ! $def['ok'] ) {
		$msg = isset( $def['body']['error']['message'] ) ? (string) $def['body']['error']['message'] : 'customer_update_failed';
		return new \WP_Error( 'avi_appsumo_customer', $msg );
	}

	$trial_days = (int) apply_filters( 'avi_appsumo_guardian_trial_days', AVI_APPSUMO_GUARDIAN_TRIAL_DAYS );
	if ( $trial_days < 1 || $trial_days > 90 ) {
		$trial_days = AVI_APPSUMO_GUARDIAN_TRIAL_DAYS;
	}

	$sub_body = array(
		'customer'                    => $cus,
		'default_payment_method'      => $payment_method_id,
		'items[0][price]'             => $price_id,
		'trial_period_days'           => (string) $trial_days,
		'metadata[user_id]'           => (string) $wp_user_id,
		'metadata[avi_product]'       => 'guardian',
		'metadata[avi_appsumo_trial]' => '1',
		'metadata[source]'            => 'appsumo_trial',
	);
	$sres = avi_appsumo_stripe_api_post( 'subscriptions', $sub_body );
	if ( ! $sres['ok'] || empty( $sres['body']['id'] ) ) {
		$msg = isset( $sres['body']['error']['message'] ) ? (string) $sres['body']['error']['message'] : 'subscription_failed';
		return new \WP_Error( 'avi_appsumo_sub', $msg );
	}

	$table = $wpdb->prefix . 'avi_saas_profiles';
	if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ) !== $table ) {
		return new \WP_Error( 'avi_appsumo_table', 'profiles' );
	}
	$start = current_time( 'mysql', true );
	$end   = gmdate( 'Y-m-d H:i:s', time() + $trial_days * DAY_IN_SECONDS );

	$upd  = array(
		'guardian_active'            => 1,
		'guardian_scan_enabled'      => 1,
		'ltd_guardian_trial_start'   => $start,
		'ltd_guardian_trial_expires' => $end,
	);
	$cols = $wpdb->get_col( "SHOW COLUMNS FROM `{$table}`", 0 );
	$cols = is_array( $cols ) ? $cols : array();
	$upd  = array_intersect_key( $upd, array_flip( $cols ) );
	if ( empty( $upd ) ) {
		return new \WP_Error( 'avi_appsumo_cols', 'missing_guardian_columns' );
	}
	$fmt = array();
	foreach ( $upd as $k => $v ) {
		$fmt[] = is_int( $v ) ? '%d' : '%s';
	}
	$row_id = 0;
	if ( in_array( 'user_id', $cols, true ) ) {
		$row_id = (int) $wpdb->get_var( $wpdb->prepare( "SELECT id FROM `{$table}` WHERE user_id = %d ORDER BY id ASC LIMIT 1", $wp_user_id ) );
	}
	if ( $row_id <= 0 && in_array( 'wp_user_id', $cols, true ) ) {
		$row_id = (int) $wpdb->get_var( $wpdb->prepare( "SELECT id FROM `{$table}` WHERE wp_user_id = %d ORDER BY id ASC LIMIT 1", $wp_user_id ) );
	}
	if ( $row_id <= 0 ) {
		return new \WP_Error( 'avi_appsumo_profile', 'no_profile_row' );
	}
	$wpdb->update( $table, $upd, array( 'id' => $row_id ), $fmt, array( '%d' ) );

	// Email essai Guardian LTD (Brevo) — params wholesale pour paliers agence.
	if ( class_exists( '\Avision\Avision_Email_Manager' ) && function_exists( 'avi_get_wholesale_tier' ) ) {
		$agency_table = $wpdb->prefix . 'avi_saas_agencies';
		$agency_id    = null;
		if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $agency_table ) ) === $agency_table ) {
			$agency_id = $wpdb->get_var(
				$wpdb->prepare(
					"SELECT id FROM `{$agency_table}` WHERE admin_user_id = %d LIMIT 1",
					$wp_user_id
				)
			);
		}
		$listing_count = 1;
		if ( $agency_id && in_array( 'agency_id', $cols, true ) ) {
			$cnt = (int) $wpdb->get_var(
				$wpdb->prepare(
					"SELECT COUNT(*) FROM `{$table}` WHERE agency_id = %d",
					(int) $agency_id
				)
			);
			$listing_count = $cnt > 0 ? $cnt : 1;
		}
		$tier      = avi_get_wholesale_tier( $listing_count );
		$price_map = array( 1 => 26, 2 => 24, 3 => 19 );
		$price_per = isset( $price_map[ $tier ] ) ? (int) $price_map[ $tier ] : 29;
		$monthly_total = $price_per * $listing_count;
		\Avision\Avision_Email_Manager::send_avi_email(
			$wp_user_id,
			\Avision\Avision_Email_Manager::TYPE_LTD_TRIAL_START,
			array(
				'recipient_email'   => (string) $user->user_email,
				'user_name'         => (string) $user->display_name,
				'dashboard_url'     => trailingslashit( home_url( '/premium-dashboard/' ) ),
				'listing_count'     => $listing_count,
				'price_per_listing' => $price_per,
				'monthly_total'     => $monthly_total,
				'wholesale_tier'    => $tier,
			)
		);
	}

	return array(
		'redirect' => avi_appsumo_after_guardian_trial_redirect_url(),
	);
}

/**
 * Bannière « essai Guardian » (chemin B) — sortie HTML si conditions remplies.
 *
 * @return string
 */
function avi_appsumo_get_guardian_trial_banner_html(): string {
	if ( ! function_exists( 'is_user_logged_in' ) || ! is_user_logged_in() ) {
		return '';
	}
	$uid = (int) get_current_user_id();
	if ( get_user_meta( $uid, 'avi_payment_type', true ) !== 'ltd' ) {
		return '';
	}

	global $wpdb;
	$table = $wpdb->prefix . 'avi_saas_profiles';
	if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ) !== $table ) {
		return '';
	}
	$cols = $wpdb->get_col( "SHOW COLUMNS FROM `{$table}`", 0 );
	$cols = is_array( $cols ) ? $cols : array();
	$has_user_id = in_array( 'user_id', $cols, true );
	$has_wp_uid  = in_array( 'wp_user_id', $cols, true );
	if ( ! $has_user_id && ! $has_wp_uid ) {
		return '';
	}
	$where = $has_user_id ? 'user_id = %d' : 'wp_user_id = %d';
	$row = $wpdb->get_row(
		$wpdb->prepare(
			// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- WHERE whitelisted (colonnes).
			"SELECT created_at, ltd_guardian_trial_start FROM `{$table}` WHERE {$where} ORDER BY id ASC LIMIT 1",
			$uid
		),
		ARRAY_A
	);
	if ( ! is_array( $row ) ) {
		return '';
	}
	if ( ! empty( $row['ltd_guardian_trial_start'] ) ) {
		return '';
	}
	$created = isset( $row['created_at'] ) ? strtotime( (string) $row['created_at'] . ' UTC' ) : false;
	if ( ! $created ) {
		return '';
	}
	$deadline = $created + AVI_APPSUMO_GUARDIAN_TRIAL_DAYS * DAY_IN_SECONDS;
	if ( time() > $deadline ) {
		return '';
	}
	$days_left = max( 0, (int) floor( ( $deadline - time() ) / DAY_IN_SECONDS ) );
	$ajax      = admin_url( 'admin-ajax.php' );
	$nonce     = wp_create_nonce( 'avision_appsumo_nonce' );
	$trial_lang = get_user_meta( get_current_user_id(), 'avi_language', true ) ?: 'en';
	$trial_titles = array(
		'fr' => 'Essai Guardian gratuit (15 jours)',
		'en' => 'Free Guardian Trial (15 days)',
		'es' => 'Prueba Guardian gratuita (15 días)',
	);
	$trial_desc = array(
		'fr' => sprintf( 'Il vous reste environ %d jour(s) pour activer votre essai Guardian gratuit. Votre compte a été créé le %s.', $days_left, date_i18n( 'd/m/Y', $created ) ),
		'en' => sprintf( 'You have approximately %d day(s) left to activate your free Guardian trial. Your account was created on %s.', $days_left, date_i18n( 'M d, Y', $created ) ),
		'es' => sprintf( 'Le quedan aproximadamente %d día(s) para activar su prueba gratuita de Guardian. Su cuenta fue creada el %s.', $days_left, date_i18n( 'd/m/Y', $created ) ),
	);
	$trial_btn = array(
		'fr' => 'Activer l\'essai',
		'en' => 'Activate trial',
		'es' => 'Activar prueba',
	);
	$trial_help = array(
		'fr' => 'Entrez votre code de paiement Stripe (pm_…) pour activer.',
		'en' => 'Enter your Stripe payment code (pm_…) to activate.',
		'es' => 'Ingrese su código de pago Stripe (pm_…) para activar.',
	);
	ob_start();
	?>
	<div id="avi-appsumo-guardian-banner" class="avi-appsumo-guardian-banner"
		style="margin:16px 0;padding:14px 16px;background:#fff7ed;border:1px solid #fdba74;border-radius:10px;font-size:.95em;">
		<strong><?php echo esc_html( $trial_titles[ $trial_lang ] ?? $trial_titles['en'] ); ?></strong>
		<p style="margin:8px 0;"><?php echo esc_html( $trial_desc[ $trial_lang ] ?? $trial_desc['en'] ); ?></p>
		<p style="margin:0 0 8px;font-size:.85em;color:#888;">
			<?php echo esc_html( $trial_help[ $trial_lang ] ?? $trial_help['en'] ); ?>
		</p>
		<label for="avi-appsumo-pm-input" class="screen-reader-text">
			<?php echo esc_html( $trial_btn[ $trial_lang ] ?? 'Payment method' ); ?>
		</label>
		<input type="text" id="avi-appsumo-pm-input" placeholder="pm_…"
			style="width:260px;margin-right:8px;padding:6px 10px;border:1px solid #ddd;border-radius:6px;font-size:.9em;" />
		<button type="button" id="avi-appsumo-guardian-btn" class="button button-primary">
			<?php echo esc_html( $trial_btn[ $trial_lang ] ?? $trial_btn['en'] ); ?>
		</button>
		<span id="avi-appsumo-guardian-msg" style="margin-left:10px;font-size:.9em;"></span>
		<script>
		(function(){
			var btn = document.getElementById('avi-appsumo-guardian-btn');
			var inp = document.getElementById('avi-appsumo-pm-input');
			var msg = document.getElementById('avi-appsumo-guardian-msg');
			if (!btn || !inp) return;
			btn.addEventListener('click', function(){
				var pm = (inp.value || '').trim();
				if (!pm) { if (msg) msg.textContent = '<?php echo esc_js( __( 'Saisissez un pm_… valide.', 'avision-dashboard' ) ); ?>'; return; }
				btn.disabled = true;
				var fd = new FormData();
				fd.append('action', 'avision_appsumo_start_guardian_trial');
				fd.append('nonce', '<?php echo esc_js( $nonce ); ?>');
				fd.append('payment_method_id', pm);
				fetch('<?php echo esc_url( $ajax ); ?>', { method: 'POST', body: fd, credentials: 'same-origin' })
					.then(function(r){ return r.json(); })
					.then(function(d){
						if (d.success) {
							if (msg) msg.textContent = '<?php echo esc_js( __( 'Essai activé. Rechargez la page.', 'avision-dashboard' ) ); ?>';
							location.reload();
						} else {
							var c = (d.data && d.data.code) ? d.data.code : 'erreur';
							if (msg) msg.textContent = c;
						}
					})
					.catch(function(){ if (msg) msg.textContent = 'network'; })
					.finally(function(){ btn.disabled = false; });
			});
		})();
		</script>
	</div>
	<?php
	return (string) ob_get_clean();
}

/**
 * @return void
 */
function avi_appsumo_maybe_print_guardian_banner(): void {
	if ( ! function_exists( 'is_page' ) ) {
		return;
	}
	$on = is_page( 'premium-dashboard' ) || is_page( 'agency-dashboard' );
	if ( ! $on && function_exists( 'is_page_template' ) ) {
		$on = is_page_template( 'page-premium-dashboard.php' ) || is_page_template( 'page-agency-dashboard.php' );
	}
	if ( ! $on ) {
		return;
	}
	$html = avi_appsumo_get_guardian_trial_banner_html();
	if ( $html !== '' ) {
		// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- HTML contrôlé (traductions échappées dans le template).
		echo $html;
	}
}

/**
 * Remet un code en « unused » (rollback).
 *
 * @param string $code Code exact (casse DB).
 * @return void
 */
function avi_appsumo_revert_code_claim( string $code ): void {
	global $wpdb;
	$table = $wpdb->prefix . 'avi_appsumo_codes';
	if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ) !== $table ) {
		return;
	}
	$wpdb->update(
		$table,
		array(
			'status'     => 'unused',
			'claimed_by' => null,
			'claimed_at' => null,
		),
		array( 'code' => $code ),
		array( '%s', '%s', '%s' ),
		array( '%s' )
	);
}

/**
 * Traite l’activation AppSumo (email + code), après validation du nonce côté routeur.
 *
 * @param string $account_type_hint `individual`|`agency` (UI ; le plan du code impose le routage effectif).
 * @return array{user_id:int,redirect:string,message:string,plan_type?:string,account_type?:string,is_agency?:bool,redirect_premium?:string,redirect_agency?:string}|\WP_Error
 */
function avi_appsumo_process_redeem( string $code_raw, string $email_raw, string $account_type_hint = '' ) {
	global $wpdb;
	$code  = strtoupper( preg_replace( '/[^A-Z0-9\-_]/', '', (string) $code_raw ) );
	$email = sanitize_email( (string) $email_raw );
	if ( $code === '' || ! is_email( $email ) ) {
		return new \WP_Error( 'avi_appsumo_input', 'invalid_email_or_code' );
	}
	$table = $wpdb->prefix . 'avi_appsumo_codes';
	if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ) !== $table ) {
		return new \WP_Error( 'avi_appsumo_schema', 'table_missing' );
	}
	$row = $wpdb->get_row(
		$wpdb->prepare(
			"SELECT id, code, plan_type, status FROM `{$table}` WHERE code = %s LIMIT 1",
			$code
		),
		ARRAY_A
	);
	if ( ! is_array( $row ) ) {
		return new \WP_Error( 'avi_appsumo_code', 'code_invalid' );
	}
	if ( (string) $row['status'] !== 'unused' ) {
		return new \WP_Error( 'avi_appsumo_code', 'code_already_used' );
	}
	$plan_type = sanitize_key( (string) ( $row['plan_type'] ?? 'solo' ) );
	if ( ! in_array( $plan_type, array( 'solo', 'agency_5', 'agency_20', 'unlimited' ), true ) ) {
		$plan_type = 'solo';
	}
	$account_type_hint = sanitize_key( (string) $account_type_hint );
	if ( ! in_array( $account_type_hint, array( 'individual', 'agency' ), true ) ) {
		$account_type_hint = 'individual';
	}
	$is_agency_plan = in_array( $plan_type, array( 'agency_5', 'agency_20', 'unlimited' ), true );
	$routing_account_type = $is_agency_plan ? 'agency' : 'individual';

	$now    = current_time( 'mysql' );
	$locked = $wpdb->update(
		$table,
		array(
			'status'     => 'claimed',
			'claimed_at' => $now,
			'claimed_by' => 0,
		),
		array(
			'id'     => (int) $row['id'],
			'status' => 'unused',
		),
		array( '%s', '%s', '%d' ),
		array( '%d', '%s' )
	);
	if ( $locked === false || (int) $locked < 1 ) {
		return new \WP_Error( 'avi_appsumo_code', 'code_already_used' );
	}

	$user        = get_user_by( 'email', $email );
	$new_user    = false;
	$plain_pass  = '';
	$wp_user_id  = 0;
	if ( ! $user ) {
		$plain_pass = wp_generate_password( 24, true, true );
		$local      = strstr( $email, '@', true );
		$local      = is_string( $local ) ? $local : 'user';
		$base       = sanitize_user( $local, true );
		if ( $base === '' ) {
			$base = 'user';
		}
		$login = $base;
		$n     = 0;
		while ( username_exists( $login ) ) {
			$n++;
			$login = $base . '_' . $n;
		}
		$wp_user_id = wp_insert_user(
			array(
				'user_login' => $login,
				'user_pass'  => $plain_pass,
				'user_email' => $email,
				'role'       => 'subscriber',
			)
		);
		if ( is_wp_error( $wp_user_id ) ) {
			avi_appsumo_revert_code_claim( (string) $row['code'] );
			return $wp_user_id;
		}
		$new_user = true;
	} else {
		$wp_user_id = (int) $user->ID;
	}

	$wpdb->update( $table, array( 'claimed_by' => $wp_user_id ), array( 'id' => (int) $row['id'] ), array( '%d' ), array( '%d' ) );

	$act = avi_activate_ltd_plan( $wp_user_id, $plan_type, $code, $account_type_hint );
	if ( is_wp_error( $act ) ) {
		avi_appsumo_revert_code_claim( (string) $row['code'] );
		if ( $new_user && $wp_user_id > 0 && function_exists( 'wp_delete_user' ) ) {
			require_once ABSPATH . 'wp-admin/includes/user.php';
			wp_delete_user( $wp_user_id );
		}
		return $act;
	}

	if ( $new_user && $plain_pass !== '' ) {
		$u_mail = get_userdata( $wp_user_id );
		$login_disp = ( $u_mail && ! empty( $u_mail->user_login ) ) ? (string) $u_mail->user_login : '';
		$sent_brevo  = false;
		$dash_ltd    = $is_agency_plan
			? trailingslashit( home_url( '/agency-dashboard/' ) )
			: trailingslashit( home_url( '/premium-dashboard/' ) );
		if ( class_exists( '\Avision\Avision_Email_Manager' ) ) {
			$sent_brevo = \Avision\Avision_Email_Manager::send_avi_email(
				$wp_user_id,
				\Avision\Avision_Email_Manager::TYPE_LTD_WELCOME,
				array(
					'recipient_email' => $email,
					'user_name'       => $u_mail ? (string) $u_mail->display_name : '',
					'user_pass'       => $plain_pass,
					'dashboard_url'   => $dash_ltd,
					'login_url'       => trailingslashit( home_url( '/connexion/' ) ),
					'account_type'    => $is_agency_plan ? 'Agency' : 'Individual',
				)
			);
		}
		if ( ! $sent_brevo && function_exists( 'wp_mail' ) ) {
			wp_mail(
				$email,
				'[AVI] Accès AppSumo LTD activé',
				sprintf(
					"Bonjour,\n\nVotre accès LTD AVI est activé.\nIdentifiant : %s\nMot de passe temporaire : %s\n\nConnectez-vous puis changez ce mot de passe.\n",
					$login_disp,
					$plain_pass
				)
			);
		}
	}

	if ( function_exists( 'wp_set_auth_cookie' ) && function_exists( 'wp_set_current_user' ) ) {
		wp_set_current_user( $wp_user_id );
		wp_set_auth_cookie( $wp_user_id, true, is_ssl() );
	}

	$redirect_premium = add_query_arg( 'appsumo_welcome', '1', trailingslashit( home_url( '/premium-dashboard/' ) ) );
	$redirect_agency  = add_query_arg( 'appsumo_welcome', '1', trailingslashit( home_url( '/agency-dashboard/' ) ) );
	$redirect_default = $is_agency_plan ? $redirect_agency : $redirect_premium;
	$redirect         = (string) apply_filters(
		'avi_appsumo_redeem_redirect_url',
		$redirect_default,
		$wp_user_id,
		$plan_type,
		$routing_account_type
	);

	return array(
		'user_id'           => $wp_user_id,
		'redirect'          => $redirect,
		'redirect_premium'  => $redirect_premium,
		'redirect_agency'   => $redirect_agency,
		'plan_type'         => $plan_type,
		'account_type'      => $routing_account_type,
		'is_agency'        => $is_agency_plan,
		'message'           => __( 'Compte activé ! Redirection…', 'avision-dashboard' ),
	);
}

/**
 * Thèmes FSE (ex. Twenty Twenty-Five) : s’assurer que le skip link bloc utilise le chemin WP 6.4+
 * et ne déclenche pas the_block_template_skip_link() déprécié si le template est résolu tardivement.
 *
 * @return void
 */
function avi_appsumo_fix_block_template_skip_link(): void {
	if ( function_exists( 'wp_enqueue_block_template_skip_link' ) ) {
		wp_enqueue_block_template_skip_link();
	}
}

add_action( 'plugins_loaded', 'avi_appsumo_migrate_db', 6 );
add_filter( 'avision_agency_quota', 'avi_appsumo_filter_agency_quota', 25, 1 );
add_action( 'wp_footer', 'avi_appsumo_maybe_print_guardian_banner', 5 );
add_action( 'wp_enqueue_scripts', 'avi_appsumo_fix_block_template_skip_link', 100 );
