<?php
namespace BLR\Backline\Modules\Contacts;

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

final class NewsletterDoubleOptIn {
	public const META_STATUS = 'blr_contact_newsletter_status';
	public const META_CONFIRM_TOKEN_HASH = 'blr_contact_newsletter_confirm_token_hash';
	public const META_CONFIRM_EXPIRES_GMT = 'blr_contact_newsletter_confirm_expires_gmt';
	public const META_REQUESTED_GMT = 'blr_contact_newsletter_requested_gmt';
	public const META_CONFIRMED_GMT = 'blr_contact_newsletter_confirmed_gmt';
	public const META_SIGNUP_GMT = 'blr_contact_newsletter_signup_gmt';

	public const STATUS_PENDING = 'pending';
	public const STATUS_SUBSCRIBED = 'subscribed';
	public const STATUS_UNSUBSCRIBED = 'unsubscribed';

	private const DEFAULT_CONFIRMATION_WINDOW_SECONDS = 172800;

	public static function status_options(): array {
		return [
			self::STATUS_PENDING,
			self::STATUS_SUBSCRIBED,
			self::STATUS_UNSUBSCRIBED,
		];
	}

	public static function normalize_status(string $status): string {
		$status = sanitize_key($status);

		if (in_array($status, self::status_options(), true)) {
			return $status;
		}

		return '';
	}

	public static function status_label(string $status): string {
		$status = self::normalize_status($status);

		switch ($status) {
			case self::STATUS_PENDING:
				return __('Pending confirmation', 'blr-backline');
			case self::STATUS_SUBSCRIBED:
				return __('Subscribed', 'blr-backline');
			case self::STATUS_UNSUBSCRIBED:
				return __('Not subscribed', 'blr-backline');
			default:
				return __('Not subscribed', 'blr-backline');
		}
	}

	public static function get_status(int $contact_id): string {
		if ($contact_id <= 0) {
			return '';
		}

		$status = self::normalize_status((string) get_post_meta($contact_id, self::META_STATUS, true));
		if ($status !== '') {
			return $status;
		}

		$legacy_signup = (string) get_post_meta($contact_id, 'blr_contact_newsletter_signup', true);
		if ($legacy_signup === '1') {
			return self::STATUS_SUBSCRIBED;
		}

		if ($legacy_signup === '0') {
			return self::STATUS_UNSUBSCRIBED;
		}

		return '';
	}

	public static function issue_confirmation_token(int $contact_id): array {
		if ($contact_id <= 0) {
			return [];
		}

		$token = wp_generate_password(64, false, false);
		$requested_gmt = current_time('mysql', true);
		$expires_gmt = gmdate('Y-m-d H:i:s', time() + self::confirmation_window_seconds());

		self::apply_status(
			$contact_id,
			self::STATUS_PENDING,
			[
				'requested_gmt' => $requested_gmt,
				'token_hash' => wp_hash_password($token),
				'token_expires_gmt' => $expires_gmt,
			]
		);

		return [
			'token' => $token,
			'requested_gmt' => $requested_gmt,
			'expires_gmt' => $expires_gmt,
		];
	}

	public static function validate_confirmation_token(int $contact_id, string $token): string {
		if ($contact_id <= 0 || $token === '') {
			return 'invalid';
		}

		$status = self::get_status($contact_id);
		if ($status === self::STATUS_SUBSCRIBED) {
			return 'already_confirmed';
		}

		$stored_hash = (string) get_post_meta($contact_id, self::META_CONFIRM_TOKEN_HASH, true);
		if ($stored_hash === '') {
			return 'invalid';
		}

		if (!wp_check_password($token, $stored_hash, $contact_id)) {
			return 'invalid';
		}

		$expires_gmt = (string) get_post_meta($contact_id, self::META_CONFIRM_EXPIRES_GMT, true);
		if ($expires_gmt !== '' && strtotime($expires_gmt . ' UTC') < time()) {
			return 'expired';
		}

		return 'valid';
	}

	public static function confirm_subscription(int $contact_id): void {
		if ($contact_id <= 0) {
			return;
		}

		self::apply_status(
			$contact_id,
			self::STATUS_SUBSCRIBED,
			[
				'confirmed_gmt' => current_time('mysql', true),
				'clear_confirmation_token' => true,
			]
		);
	}

	public static function clear_confirmation_token(int $contact_id): void {
		if ($contact_id <= 0) {
			return;
		}

		delete_post_meta($contact_id, self::META_CONFIRM_TOKEN_HASH);
		delete_post_meta($contact_id, self::META_CONFIRM_EXPIRES_GMT);
	}

	public static function apply_status(int $contact_id, string $status, array $args = []): void {
		if ($contact_id <= 0) {
			return;
		}

		$status = self::normalize_status($status);
		if ($status === '') {
			$status = self::STATUS_UNSUBSCRIBED;
		}

		update_post_meta($contact_id, self::META_STATUS, $status);

		if ($status === self::STATUS_PENDING) {
			$requested_gmt = isset($args['requested_gmt']) ? (string) $args['requested_gmt'] : current_time('mysql', true);
			update_post_meta($contact_id, self::META_REQUESTED_GMT, $requested_gmt);
			delete_post_meta($contact_id, self::META_CONFIRMED_GMT);
		}

		if ($status === self::STATUS_SUBSCRIBED) {
			$confirmed_gmt = isset($args['confirmed_gmt']) && (string) $args['confirmed_gmt'] !== ''
				? (string) $args['confirmed_gmt']
				: current_time('mysql', true);
			update_post_meta($contact_id, self::META_CONFIRMED_GMT, $confirmed_gmt);
			update_post_meta($contact_id, self::META_SIGNUP_GMT, $confirmed_gmt);
		}

		if ($status === self::STATUS_UNSUBSCRIBED) {
			delete_post_meta($contact_id, self::META_CONFIRMED_GMT);
		}

		if (!empty($args['token_hash'])) {
			update_post_meta($contact_id, self::META_CONFIRM_TOKEN_HASH, (string) $args['token_hash']);
		}

		if (!empty($args['token_expires_gmt'])) {
			update_post_meta($contact_id, self::META_CONFIRM_EXPIRES_GMT, (string) $args['token_expires_gmt']);
		}

		if (!empty($args['clear_confirmation_token']) || $status !== self::STATUS_PENDING) {
			self::clear_confirmation_token($contact_id);
		}

		self::sync_legacy_signup_meta($contact_id, $status, $args);
	}

	public static function subscribed_meta_query(): array {
		return [
			'relation' => 'OR',
			[
				'key' => self::META_STATUS,
				'value' => self::STATUS_SUBSCRIBED,
				'compare' => '=',
			],
			[
				'relation' => 'AND',
				[
					'key' => self::META_STATUS,
					'compare' => 'NOT EXISTS',
				],
				[
					'key' => 'blr_contact_newsletter_signup',
					'value' => '1',
					'compare' => '=',
				],
			],
		];
	}

	public static function pending_meta_query(): array {
		return [
			'key' => self::META_STATUS,
			'value' => self::STATUS_PENDING,
			'compare' => '=',
		];
	}

	public static function not_subscribed_meta_query(): array {
		return [
			'relation' => 'OR',
			[
				'key' => self::META_STATUS,
				'value' => self::STATUS_UNSUBSCRIBED,
				'compare' => '=',
			],
			[
				'relation' => 'AND',
				[
					'relation' => 'OR',
					[
						'key' => self::META_STATUS,
						'value' => '',
						'compare' => '=',
					],
					[
						'key' => self::META_STATUS,
						'compare' => 'NOT EXISTS',
					],
				],
				[
					'relation' => 'OR',
					[
						'key' => 'blr_contact_newsletter_signup',
						'value' => '0',
						'compare' => '=',
					],
					[
						'key' => 'blr_contact_newsletter_signup',
						'value' => '',
						'compare' => '=',
					],
					[
						'key' => 'blr_contact_newsletter_signup',
						'compare' => 'NOT EXISTS',
					],
				],
			],
		];
	}

	private static function sync_legacy_signup_meta(int $contact_id, string $status, array $args = []): void {
		$legacy_value = $status === self::STATUS_SUBSCRIBED ? '1' : '0';
		update_post_meta($contact_id, 'blr_contact_newsletter_signup', $legacy_value);

		if ($legacy_value === '1') {
			$signup_gmt = isset($args['confirmed_gmt']) && (string) $args['confirmed_gmt'] !== ''
				? (string) $args['confirmed_gmt']
				: (string) get_post_meta($contact_id, self::META_SIGNUP_GMT, true);
			if ($signup_gmt === '') {
				$signup_gmt = current_time('mysql', true);
			}
			update_post_meta($contact_id, 'blr_contact_newsletter_signup_gmt', $signup_gmt);
		} else {
			delete_post_meta($contact_id, 'blr_contact_newsletter_signup_gmt');
		}
	}

	private static function confirmation_window_seconds(): int {
		$seconds = (int) apply_filters('blr_backline_fan_signup_confirmation_window_seconds', self::DEFAULT_CONFIRMATION_WINDOW_SECONDS);

		return max(3600, $seconds);
	}
}
