<?php

declare( strict_types = 1 );

namespace Automattic\WooCommerce\Internal\PushNotifications\Entities;

defined( 'ABSPATH' ) || exit;

use InvalidArgumentException;

/**
 * Object representation of a push token.
 *
 * @since 10.4.0
 */
class PushToken {
	/**
	 * WordPress post type for storing push tokens.
	 */
	const POST_TYPE = 'push_token';

	/**
	 * Platform identifier for iOS devices.
	 */
	const PLATFORM_IOS = 'ios';

	/**
	 * Platform identifier for Android devices.
	 */
	const PLATFORM_ANDROID = 'android';

	/**
	 * Platform identifier for web browsers.
	 */
	const PLATFORM_BROWSER = 'browser';

	/**
	 * Origin identifier for WooCommerce Android app.
	 */
	const ORIGIN_WOOCOMMERCE_ANDROID = 'com.woocommerce.android';

	/**
	 * Origin identifier for WooCommerce Android app development builds.
	 */
	const ORIGIN_WOOCOMMERCE_ANDROID_DEV = 'com.woocommerce.android:dev';

	/**
	 * Origin identifier for WooCommerce iOS app.
	 */
	const ORIGIN_WOOCOMMERCE_IOS = 'com.automattic.woocommerce';

	/**
	 * Origin identifier for WooCommerce iOS app development builds.
	 */
	const ORIGIN_WOOCOMMERCE_IOS_DEV = 'com.automattic.woocommerce:dev';

	/**
	 * List of valid platforms.
	 */
	const PLATFORMS = array(
		self::PLATFORM_IOS,
		self::PLATFORM_ANDROID,
		self::PLATFORM_BROWSER,
	);

	/**
	 * List of valid origins.
	 */
	const ORIGINS = array(
		self::ORIGIN_WOOCOMMERCE_ANDROID,
		self::ORIGIN_WOOCOMMERCE_ANDROID_DEV,
		self::ORIGIN_WOOCOMMERCE_IOS,
		self::ORIGIN_WOOCOMMERCE_IOS_DEV,
	);

	/**
	 * Maximum length for push notification tokens.
	 */
	const MAX_TOKEN_LENGTH = 4096;

	/**
	 * The id of the token post.
	 *
	 * @var int|null
	 */
	private ?int $id = null;

	/**
	 * The id of the user who owns the token.
	 *
	 * @var int|null
	 */
	private ?int $user_id = null;

	/**
	 * The token representing a device we can send a push notification to.
	 *
	 * @var string|null
	 */
	private ?string $token = null;

	/**
	 * The UUID of the device that generated the token.
	 *
	 * @var string|null
	 */
	private ?string $device_uuid = null;

	/**
	 * The platform the token was generated by.
	 *
	 * @var string|null
	 */
	private ?string $platform = null;

	/**
	 * The origin the token belongs to.
	 *
	 * @var string|null
	 */
	private ?string $origin = null;

	/**
	 * Creates a new PushToken instance with the specified properties.
	 *
	 * This is a utility method that provides a one-liner to create an instance
	 * with all the data you want to specify upfront. Using this method doesn't
	 * imply that the instance obtained is valid, complete, or usable in all
	 * contexts - validity is still determined by the internal validation logic
	 * of the class.
	 *
	 * @param int|null    $id          The ID of the token post.
	 * @param int|null    $user_id     The ID of the user who owns the token.
	 * @param string|null $token       The token representing a device we can send a push notification to.
	 * @param string|null $device_uuid The UUID of the device that generated the token.
	 * @param string|null $platform    The platform the token was generated by.
	 * @param string|null $origin      The origin the token belongs to.
	 * @throws InvalidArgumentException If any of the provided values fail validation.
	 * @return PushToken
	 *
	 * @since 10.4.0
	 */
	public static function get_new_instance(
		?int $id = null,
		?int $user_id = null,
		?string $token = null,
		?string $device_uuid = null,
		?string $platform = null,
		?string $origin = null
	): PushToken {
		$instance = new self();

		if ( null !== $id ) {
			$instance->set_id( $id );
		}

		if ( null !== $user_id ) {
			$instance->set_user_id( $user_id );
		}

		if ( null !== $token ) {
			$instance->set_token( $token );
		}

		if ( null !== $device_uuid ) {
			$instance->set_device_uuid( $device_uuid );
		}

		if ( null !== $platform ) {
			$instance->set_platform( $platform );
		}

		if ( null !== $origin ) {
			$instance->set_origin( $origin );
		}

		return $instance;
	}

	/**
	 * Sets the ID.
	 *
	 * @param int $id The id of the token post.
	 * @throws InvalidArgumentException If ID is <= 0.
	 *
	 * @since 10.4.0
	 */
	public function set_id( int $id ) {
		if ( $id <= 0 ) {
			throw new InvalidArgumentException( 'ID must be a positive integer.' );
		}

		$this->id = $id;
	}

	/**
	 * Sets the user ID.
	 *
	 * @param int $user_id The id of the user who owns the token.
	 * @throws InvalidArgumentException If ID is <= 0.
	 *
	 * @since 10.4.0
	 */
	public function set_user_id( int $user_id ) {
		if ( $user_id <= 0 ) {
			throw new InvalidArgumentException( 'User ID must be a positive integer.' );
		}

		$this->user_id = $user_id;
	}

	/**
	 * Sets the token.
	 *
	 * @param string $token The token representing a device we can send a push notification to.
	 * @throws InvalidArgumentException If token is empty or exceeds maximum length.
	 *
	 * @since 10.4.0
	 */
	public function set_token( string $token ) {
		$token = trim( $token );

		if ( '' === $token ) {
			throw new InvalidArgumentException( 'Token cannot be empty.' );
		}

		if ( strlen( $token ) > self::MAX_TOKEN_LENGTH ) {
			throw new InvalidArgumentException(
				// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
				sprintf( 'Token exceeds maximum length of %s.', self::MAX_TOKEN_LENGTH )
			);
		}

		$this->token = $token;
	}

	/**
	 * Sets the device UUID, normalize empty (non-null) values to null.
	 *
	 * @param string|null $device_uuid The UUID of the device that generated the token.
	 *
	 * @since 10.4.0
	 */
	public function set_device_uuid( ?string $device_uuid ) {
		if ( null !== $device_uuid ) {
			$device_uuid = trim( $device_uuid );
		}

		$this->device_uuid = ( '' === $device_uuid ) ? null : $device_uuid;
	}

	/**
	 * Sets the platform.
	 *
	 * @param string $platform The platform the token was generated by.
	 * @throws InvalidArgumentException If the platform is invalid.
	 *
	 * @since 10.4.0
	 */
	public function set_platform( string $platform ) {
		if ( ! in_array( $platform, self::PLATFORMS, true ) ) {
			throw new InvalidArgumentException( 'Platform for PushToken is invalid.' );
		}

		$this->platform = $platform;
	}

	/**
	 * Sets the origin.
	 *
	 * @param string $origin The origin of the token, e.g. the app it came from.
	 * @throws InvalidArgumentException If the origin is invalid.
	 *
	 * @since 10.4.0
	 */
	public function set_origin( string $origin ) {
		if ( ! in_array( $origin, self::ORIGINS, true ) ) {
			throw new InvalidArgumentException( 'Origin for PushToken is invalid.' );
		}

		$this->origin = $origin;
	}

	/**
	 * Gets the ID.
	 *
	 * @return int|null
	 *
	 * @since 10.4.0
	 */
	public function get_id(): ?int {
		return $this->id;
	}

	/**
	 * Gets the user ID.
	 *
	 * @return int|null
	 *
	 * @since 10.4.0
	 */
	public function get_user_id(): ?int {
		return $this->user_id;
	}

	/**
	 * Gets the token.
	 *
	 * @return string|null
	 *
	 * @since 10.4.0
	 */
	public function get_token(): ?string {
		return $this->token;
	}

	/**
	 * Gets the device UUID.
	 *
	 * @return string|null
	 *
	 * @since 10.4.0
	 */
	public function get_device_uuid(): ?string {
		return $this->device_uuid;
	}

	/**
	 * Gets the platform.
	 *
	 * @return string|null
	 *
	 * @since 10.4.0
	 */
	public function get_platform(): ?string {
		return $this->platform;
	}

	/**
	 * Gets the origin.
	 *
	 * @return string|null
	 *
	 * @since 10.4.0
	 */
	public function get_origin(): ?string {
		return $this->origin;
	}

	/**
	 * Determines whether this token can be created.
	 *
	 * @return bool
	 *
	 * @since 10.4.0
	 */
	public function can_be_created(): bool {
		return ! $this->get_id() && $this->has_required_parameters();
	}

	/**
	 * Determines whether this token can be updated.
	 *
	 * @return bool
	 *
	 * @since 10.4.0
	 */
	public function can_be_updated(): bool {
		return $this->get_id() && $this->has_required_parameters();
	}

	/**
	 * Determines whether this token can be read.
	 *
	 * @return bool
	 *
	 * @since 10.4.0
	 */
	public function can_be_read(): bool {
		return (bool) $this->get_id();
	}

	/**
	 * Determines whether this token can be deleted.
	 *
	 * @return bool
	 *
	 * @since 10.4.0
	 */
	public function can_be_deleted(): bool {
		return (bool) $this->get_id();
	}

	/**
	 * Determines whether all the required non-ID parameters are filled.
	 *
	 * @return bool
	 *
	 * @since 10.4.0
	 */
	private function has_required_parameters(): bool {
		return $this->get_user_id()
			&& $this->get_token()
			&& $this->get_platform()
			&& $this->get_origin()
			&& (
				$this->get_device_uuid()
				|| $this->get_platform() === self::PLATFORM_BROWSER
			);
	}
}
