<?php

/**
 * @property string $full_name
 */
class OsCustomerModel extends OsModel {
	var $id,
		$uuid,
		$first_name,
		$last_name,
		$password,
		$email,
		$phone,
		$account_nonse,
		$status,
		$activation_key,
		$google_user_id,
		$facebook_user_id,
		$avatar_image_id,
		$is_guest,
		$notes,
		$admin_notes,
		$wordpress_user_id,
		$meta_class = 'OsCustomerMetaModel',
		$updated_at,
		$created_at;

	function __construct( $id = false ) {
		$this->table_name = LATEPOINT_TABLE_CUSTOMERS;
		$this->nice_names = array(
			'first_name' => __( 'Customer First Name', 'latepoint' ),
			'email'      => __( 'Email Address', 'latepoint' ),
			'phone'      => __( 'Phone Number', 'latepoint' ),
			'last_name'  => __( 'Customer Last Name', 'latepoint' ),
		);

		parent::__construct( $id );
	}

	public function generate_data_vars(): array {
		return [
			'id'         => $this->id,
			'first_name' => $this->first_name,
			'last_name'  => $this->last_name,
			'full_name'  => $this->full_name,
			'email'      => $this->email,
			'phone'      => $this->phone,
		];
	}

	public function get_meta_by_key( $meta_key, $default = false ) {
		if ( $this->is_new_record() ) {
			return $default;
		}

		$meta = new OsCustomerMetaModel();

		return $meta->get_by_key( $meta_key, $this->id, $default );
	}

	/**
	 * @param int $limit
	 * @param bool $filter_allowed_records
	 *
	 * @return OsOrderModel[]
	 */
	public function get_orders( int $limit = 0, bool $filter_allowed_records = false ): array {
		$orders = new OsOrderModel();
		if ( $limit ) {
			$orders = $orders->set_limit( $limit );
		}
		if ( $filter_allowed_records ) {
			$orders->filter_allowed_records();
		}

		return $orders->where( [ 'customer_id' => $this->id ] )->order_by( 'created_at desc' )->get_results_as_models();
	}

	public function get_initials() {
		return mb_substr( $this->first_name, 0, 1 ) . mb_substr( $this->last_name, 0, 1 );
	}

	public function delete_meta_by_key( $meta_key ) {
		if ( $this->is_new_record() ) {
			return true;
		}

		$meta = new OsCustomerMetaModel();

		return $meta->delete_by_key( $meta_key, $this->id );
	}

	public function save_meta_by_key( $meta_key, $meta_value ) {
		if ( $this->is_new_record() ) {
			return false;
		}

		$meta = new OsCustomerMetaModel();

		return $meta->save_by_key( $meta_key, $meta_value, $this->id );
	}

	public function set_timezone_name( $timezone_name = false ) {
		if ( ! $timezone_name ) {
			$timezone_name = OsTimeHelper::get_timezone_name_from_session();
		}
		$this->save_meta_by_key( 'timezone_name', $timezone_name );
	}

	public function get_selected_timezone_name() {
		if ( OsSettingsHelper::is_on( 'steps_show_timezone_selector' ) ) {
			return $this->get_meta_by_key( 'timezone_name', OsTimeHelper::get_timezone_name_from_session() );
		} else {
			return OsTimeHelper::get_wp_timezone_name();
		}
	}

	public function get_selected_timezone_obj() {
		$timezone_obj = new DateTimeZone( $this->get_selected_timezone_name() );

		return $timezone_obj;
	}


	public function delete( $id = false ) {
		if ( ! $id && isset( $this->id ) ) {
			$id = $this->id;
		}
		$bookings           = new OsBookingModel();
		$bookings_to_delete = $bookings->where( [ 'customer_id' => $id ] )->get_results_as_models();
		if ( $bookings_to_delete ) {
			foreach ( $bookings_to_delete as $booking ) {
				$booking->delete();
			}
		}
		$orders           = new OsOrderModel();
		$orders_to_delete = $orders->where( [ 'customer_id' => $id ] )->get_results_as_models();
		if ( $orders_to_delete ) {
			foreach ( $orders_to_delete as $order ) {
				$order->delete();
			}
		}
		$transactions           = new OsTransactionModel();
		$transactions_to_delete = $transactions->where( [ 'customer_id' => $id ] )->get_results_as_models();
		if ( $transactions_to_delete ) {
			foreach ( $transactions_to_delete as $transaction ) {
				$transaction->delete();
			}
		}
		$customer_metas           = new OsCustomerMetaModel();
		$customer_metas_to_delete = $customer_metas->where( [ 'object_id' => $id ] )->get_results_as_models();
		if ( $customer_metas_to_delete ) {
			foreach ( $customer_metas_to_delete as $customer_meta ) {
				$customer_meta->delete();
			}
		}

		return parent::delete( $id );
	}


	public function get_bookings( $limit = false, $filter_allowed_records = false ) {
		$bookings = new OsBookingModel();
		if ( $limit ) {
			$bookings = $bookings->set_limit( $limit );
		}
		if ( $filter_allowed_records ) {
			$bookings->filter_allowed_records();
		}

		return $bookings->where( [ 'customer_id' => $this->id ] )->get_results_as_models();
	}

	public function get_past_bookings( $limit = false, $filter_allowed_records = false ) {
		$bookings = new OsBookingModel();
		if ( $limit ) {
			$bookings = $bookings->set_limit( $limit );
		}
		if ( $filter_allowed_records ) {
			$bookings->filter_allowed_records();
		}

		return $bookings->should_not_be_cancelled()->where(
			array(
				'customer_id' => $this->id,
				'OR'          => array(
					'start_date <' => OsTimeHelper::today_date( 'Y-m-d' ),
					'AND'          => array(
						'start_date'   => OsTimeHelper::today_date( 'Y-m-d' ),
						'start_time <' => OsTimeHelper::get_current_minutes(),
					),
				),
			)
		)->get_results_as_models();
	}

	/**
	 * @return OsBundleModel[]
	 */
	public function get_not_scheduled_bundles(): array {
		$non_scheduled_bundles = [];
		$orders                = new OsOrderModel();
		$orders                = $orders->where( [ 'customer_id' => $this->id ] )->get_results_as_models();
		if ( $orders ) {
			foreach ( $orders as $order ) {
				$bundles = $order->get_bundles_from_order_items();
				if ( $bundles ) {
					foreach ( $bundles as $order_item_id => $bundle ) {
						$bundle_services = $bundle->get_services();
						foreach ( $bundle_services as $bundle_service ) {
							$bookings                 = new OsBookingModel();
							$total_scheduled_bookings = $bookings->where(
								[
									'order_item_id' => $order_item_id,
									'service_id'    => $bundle_service->id,
								]
							)->should_not_be_cancelled()->count();
							if ( $total_scheduled_bookings < $bundle_service->join_attributes['quantity'] ) {
								$non_scheduled_bundles[ $order_item_id ] = $bundle;
								break;
							}
						}
					}
				}
			}
		}

		return $non_scheduled_bundles;
	}

	public function get_cancelled_bookings( $limit = false, $filter_allowed_records = false ) {
		$bookings = new OsBookingModel();
		if ( $limit ) {
			$bookings = $bookings->set_limit( $limit );
		}
		if ( $filter_allowed_records ) {
			$bookings->filter_allowed_records();
		}

		return $bookings->should_be_cancelled()->order_by( 'start_date, start_time asc' )->where( [ 'customer_id' => $this->id ] )->get_results_as_models();
	}

	public function get_future_bookings( $limit = false, $filter_allowed_records = false ) {
		$bookings = new OsBookingModel();
		if ( $limit ) {
			$bookings = $bookings->set_limit( $limit );
		}
		if ( $filter_allowed_records ) {
			$bookings->filter_allowed_records();
		}

		return $bookings->should_not_be_cancelled()->order_by( 'start_date, start_time asc' )->where( [ 'customer_id' => $this->id ] )->should_be_in_future()->get_results_as_models();
	}


	public function get_future_bookings_count( $filter_allowed_records = false ) {
		$bookings = new OsBookingModel();
		if ( $filter_allowed_records ) {
			$bookings->filter_allowed_records();
		}

		return $bookings->should_not_be_cancelled()->where(
			array(
				'customer_id' => $this->id,
				'OR'          => array(
					'start_date >' => OsTimeHelper::today_date( 'Y-m-d' ),
					'AND'          => array(
						'start_date'   => OsTimeHelper::today_date( 'Y-m-d' ),
						'start_time >' => OsTimeHelper::get_current_minutes(),
					),
				),
			)
		)->count();
	}

	public function get_total_bookings_count( $filter_allowed_records = false ) {
		$bookings = new OsBookingModel();
		if ( $filter_allowed_records ) {
			$bookings->filter_allowed_records();
		}

		return $bookings->select( 'count(id) as total_bookings' )->where( array( 'customer_id' => $this->id ) )->count();
	}


	public function filter_allowed_records(): OsModel {
		if ( ! OsRolesHelper::are_all_records_allowed() ) {
			$this->select( LATEPOINT_TABLE_CUSTOMERS . '.*' )->join( LATEPOINT_TABLE_BOOKINGS, [ 'customer_id' => LATEPOINT_TABLE_CUSTOMERS . '.id' ] )->group_by( LATEPOINT_TABLE_CUSTOMERS . '.id' );
			if ( ! OsRolesHelper::are_all_records_allowed( 'agent' ) ) {
				$this->filter_where_conditions( [ LATEPOINT_TABLE_BOOKINGS . '.agent_id' => OsRolesHelper::get_allowed_records( 'agent' ) ] );
			}
			if ( ! OsRolesHelper::are_all_records_allowed( 'location' ) ) {
				$this->filter_where_conditions( [ LATEPOINT_TABLE_BOOKINGS . '.location_id' => OsRolesHelper::get_allowed_records( 'location' ) ] );
			}
			if ( ! OsRolesHelper::are_all_records_allowed( 'service' ) ) {
				$this->filter_where_conditions( [ LATEPOINT_TABLE_BOOKINGS . '.service_id' => OsRolesHelper::get_allowed_records( 'service' ) ] );
			}
		}

		return $this;
	}

	public function primary_contact_type() {
		$contact_type = OsAuthHelper::get_selected_customer_authentication_field_type();
		switch ( $contact_type ) {
			case 'phone':
				return $this->phone;
			break;
			default:
				return $this->email;
			break;
		}
	}

	public function update_password( $password ) {
		// update connected wp user password
		if ( OsAuthHelper::can_wp_users_login_as_customers() && $this->wordpress_user_id ) {
			// Only reset the WP password for non-privileged accounts to prevent takeover of admin/editor users
			if ( ! OsCustomerHelper::is_wp_user_safe_for_customer_link( (int) $this->wordpress_user_id ) ) {
				return $this->update_attributes(
					[
						'password' => wp_hash_password( $password ),
						'is_guest' => false,
					]
				);
			}

			$is_logged_in      = OsWpUserHelper::get_current_user_id() == $this->wordpress_user_id;
			$logged_in_wp_user = $is_logged_in ? OsWpUserHelper::get_current_user() : false;

			// user is getting logged out after changing a password - we need to check if the user that we are changing password for is currently logged in, if it is - make sure to log them back in after password change
			wp_set_password( $password, $this->wordpress_user_id );
			if ( $is_logged_in && $logged_in_wp_user ) {
				OsAuthHelper::login_wp_user( $logged_in_wp_user );
			}
		}

		return $this->update_attributes(
			[
				'password' => wp_hash_password( $password ),
				'is_guest' => false,
			]
		);
	}

	protected function get_full_name() {
		return trim( join( ' ', array( $this->first_name, $this->last_name ) ) );
	}

	protected function get_default_status() {
		return 'pending_verification';
	}

	protected function before_create() {
		if ( ! isset( $this->is_guest ) ) {
			$this->is_guest = true;
		}
		if ( empty( $this->status ) ) {
			$this->status = $this->get_default_status();
		}
		if ( empty( $this->password ) ) {
			$this->password = wp_hash_password( bin2hex( openssl_random_pseudo_bytes( 8 ) ) );
		}
		if ( empty( $this->activation_key ) ) {
			$this->activation_key = sha1( wp_rand( 10000, 99999 ) . time() . $this->email );
		}
		if ( empty( $this->account_nonse ) ) {
			$this->account_nonse = sha1( wp_rand( 10000, 99999 ) . time() . $this->activation_key );
		}
	}


	public function get_avatar_url() {
		return OsCustomerHelper::get_avatar_url( $this );
	}

	public function get_avatar_image() {
		return OsCustomerHelper::get_avatar_image( $this );
	}

	// if this was a guest account without a set password and social login was not used, you can login just by email
	public function can_login_without_password() {
		return ( $this->is_guest && empty( $this->google_user_id ) && empty( $this->facebook_user_id ) );
	}

	public function prepare_data_before_it_is_set( $data ) {
		if ( isset( $data['first_name'] ) ) {
			$data['first_name'] = sanitize_text_field( $data['first_name'] );
		}
		if ( isset( $data['last_name'] ) ) {
			$data['last_name'] = sanitize_text_field( $data['last_name'] );
		}
		if ( isset( $data['phone'] ) ) {
			$data['phone'] = OsUtilHelper::sanitize_phone_number( $data['phone'] );
		}
		if ( isset( $data['notes'] ) ) {
			$data['notes'] = sanitize_textarea_field( $data['notes'] );
		}

		return $data;
	}


	protected function before_save() {
		if ( empty( $this->uuid ) ) {
			$this->uuid = OsUtilHelper::generate_uuid();
		}
	}

	public function get_uuid(): string {
		if ( ! $this->is_new_record() && empty( $this->uuid ) ) {
			$this->update_attributes( [ 'uuid' => OsUtilHelper::generate_uuid() ] );
		}
		return $this->uuid ?? '';
	}

	protected function allowed_params( $role = 'admin' ) {
		switch ( $role ) {
			case 'admin':
				$allowed_params = [
					'id',
					'uuid',
					'first_name',
					'last_name',
					'email',
					'phone',
					'avatar_image_id',
					'is_guest',
					'notes',
					'admin_notes',
					'wordpress_user_id',
					'password',
				];
				break;
			case 'customer':
			case 'public':
				$allowed_params = [
					'first_name',
					'last_name',
					'email',
					'phone',
					'avatar_image_id',
					'notes',
					'password',
				];
				break;
		}

		return $allowed_params;
	}

	protected function params_to_save( $role = 'admin' ) {
		$params_to_save = array(
			'id',
			'uuid',
			'first_name',
			'last_name',
			'email',
			'phone',
			'password',
			'activation_key',
			'account_nonse',
			'avatar_image_id',
			'status',
			'is_guest',
			'notes',
			'admin_notes',
			'wordpress_user_id',
			'google_user_id',
			'facebook_user_id',
		);

		return $params_to_save;
	}

	/**
	 * Validates and constructs a set of validation rules for properties.
	 *
	 * The method determines the validation rules to be applied to customer properties
	 * based on the context such as whether alternative validation is enabled, or based on
	 * default fields and specific settings. It also provides flexibility via filters to
	 * modify the resulting validation rules.
	 *
	 * @param bool $alternative_validation Indicates whether to use an alternative set of validation rules. Defaults to false.
	 *
	 * @return array An associative array where keys are property names and values are arrays of validation rules.
	 */
	protected function properties_to_validate( $alternative_validation = false ) {
		// if alternative validation is enabled - use a different scope of rules (useful when you don't need to run all validations for example on social login)
		if ( $alternative_validation ) {
			$validations = array(
				'email' => array( 'presence', 'email', 'uniqueness' ),
			);
		} else {
			$validations = array(
				'first_name' => array( 'presence' ),
				'last_name'  => array( 'presence' ),
				'email'      => array( 'presence', 'email' ),
			);

			$default_fields = OsSettingsHelper::get_default_fields_for_customer();
			foreach ( $default_fields as $name => $field ) {
				if ( $field['required'] && $field['active'] ) {
					$validations[ $name ][] = 'presence';
					$validations[ $name ]   = array_unique( $validations[ $name ] );
				} else {
					if ( isset( $validations[ $name ] ) ) {
						$validations[ $name ] = array_diff( $validations[ $name ], [ 'presence' ] );
					}
				}
			}
			if ( OsAuthHelper::is_customer_auth_enabled() ) {
				// auth enabled
				$auth_field = OsAuthHelper::get_selected_customer_authentication_field_type();
				if ( $auth_field == 'email' ) {
					$validations['email'][] = 'uniqueness';
				}
				if ( $auth_field == 'phone' ) {
					$validations['phone'][] = 'uniqueness';
				}
			} else {
				// auth disabled
				$merge_data = OsSettingsHelper::get_settings_value( 'default_contact_merge_behavior', 'email' );
				if ( $merge_data == 'email' ) {
					$validations['email'][] = 'uniqueness';
				}
				if ( $merge_data == 'phone' ) {
					$validations['phone'][] = 'uniqueness';
				}
			}
			$validations = apply_filters( 'latepoint_customer_model_validations', $validations );
		}

		return $validations;
	}
}
