<?php

class WPF_Maropost {

	/**
	 * The CRM slug.
	 *
	 * @var string
	 */
	public $slug = 'maropost';

	/**
	 * The CRM name.
	 *
	 * @var string
	 */
	public $name = 'Maropost';

	/**
	 * Contains API params
	 */
	public $params;

	/**
	 * Contains the default list for API calls.
	 */
	public $list_id;

	/**
	 * Lets pluggable functions know which features are supported by the CRM
	 */
	public $supports = array();

	/**
	 * Lets us link directly to editing a contact record.
	 * Each contact has a unique id other than his account id.
	 *
	 * @var string
	 */
	public $edit_url = '';

	/**
	 * The API URL.
	 *
	 * @var string
	 */
	public $api_url;

	/**
	 * The API Key.
	 *
	 * @var string
	 */
	public $api_key;

	/**
	 * Get things started
	 *
	 * @since 3.15.1
	 */
	public function __construct() {

		// Set up admin options.
		if ( is_admin() ) {
			require_once __DIR__ . '/admin/class-admin.php';
			new WPF_Maropost_Admin( $this->slug, $this->name, $this );
		}

		add_filter( 'http_response', array( $this, 'handle_http_response' ), 100, 3 );
	}

	/**
	 * Sets up hooks specific to this CRM
	 *
	 * @since 3.15.1
	 */
	public function init() {

		add_filter( 'wpf_crm_post_data', array( $this, 'format_post_data' ) );
		add_filter( 'wpf_format_field_value', array( $this, 'format_field_value' ), 10, 4 );

		$this->get_params(); // initialize the API URL and key.

		// Link to the contact edit page.
		$this->edit_url = str_replace( 'api', 'app', $this->api_url ) . 'lists/' . $this->list_id . '/contacts/%d';
	}


	/**
	 * Formats POST data received from HTTP Posts into standard format
	 *
	 * @since 3.15.1
	 * @return array
	 */
	public function format_post_data( $post_data ) {

		if ( isset( $post_data['contact_id'] ) ) {
			return $post_data;
		}

		$maropost_payload = json_decode( file_get_contents( 'php://input' ) );

		$post_data['contact_id'] = sanitize_text_field( $maropost_payload->contact->id );

		return $post_data;
	}

	/**
	 * Formats user entered data to match Maropost field formats
	 *
	 * @since 3.15.1
	 * @since 3.46.8 Added phone number formatting.
	 *
	 * @param mixed  $value      The field value.
	 * @param string $field_type The field type.
	 * @param string $field      The CRM field name.
	 * @param array  $update_data The full array of data being sent to the CRM.
	 * @return mixed The formatted value.
	 */
	public function format_field_value( $value, $field_type, $field, $update_data = array() ) {

		if ( 'date' === $field_type ) {

			// Adjust formatting for date fields.
			$date = gmdate( 'm/d/Y', $value );

			return $date;

		} elseif ( 'phone' === $field && ! empty( $value ) ) {

			// Format phone numbers to E.164 format for Maropost.
			$country = 'US'; // Default to US.

			// Try to get country code from available fields in priority order.
			if ( ! empty( $update_data['country'] ) ) {
				$country = $update_data['country'];
			} elseif ( function_exists( 'wc_get_base_location' ) ) {
				// Use WooCommerce store base location as fallback.
				$base_location = wc_get_base_location();
				if ( ! empty( $base_location['country'] ) ) {
					$country = $base_location['country'];
				}
			}

			return ltrim( wpf_phone_number_to_e164( $value, $country ), '+' );

		} elseif ( 'checkbox' === $field_type ) {

			// Handle string '0' and other falsy values that should be false.
			if ( '0' === $value || 0 === $value || false === $value || '' === $value || null === $value || ( is_array( $value ) && empty( $value ) ) ) {
				return null;
			}

			if ( ! empty( $value ) ) {
				// If checkbox is selected.
				return true;
			}

			// Default to null for empty values.
			return null;

		} else {

			return $value;

		}
	}

	/**
	 * Check HTTP Response for errors and return WP_Error if found
	 *
	 * @since 3.15.1
	 * @return HTTP Response
	 */
	public function handle_http_response( $response, $args, $url ) {

		if ( strpos( $url, 'maropost' ) !== false ) {

			$code = wp_remote_retrieve_response_code( $response );

			$body_json = json_decode( wp_remote_retrieve_body( $response ) );

			if ( isset( $body_json->error ) ) {

				$response = new WP_Error( 'error', $body_json->error );

			} elseif ( $code == 401 ) {

				$response = new WP_Error( 'error', 'Invalid API key.' );

			} elseif ( $code == 403 ) {

				$response = new WP_Error( 'error', 'Forbidden.' );

			} elseif ( $code == 422 ) {

				if ( ! empty( $body_json ) ) {

					$response = new WP_Error( 'error', 'Unprocessable entity: ' . wpf_print_r( $body_json, true ) );

				} else {
					$response = new WP_Error( 'error', 'Unprocessable entity.' );
				}
			}
		}

		return $response;
	}

	/**
	 * Gets params for API calls
	 *
	 * @since 3.15.1
	 * @return  array Params
	 */
	public function get_params( $account_id = null, $api_key = null ) {

		// Get saved data from DB.
		if ( empty( $account_id ) || empty( $api_key ) ) {
			$account_id = wpf_get_option( 'account_id' );
			$api_key    = wpf_get_option( 'maropost_key' );
		}

		$this->api_key = $api_key;

		if ( 112216 === intval( $account_id ) ) {
			$this->api_url = "https://sandbox-web.maropost.com/accounts/{$account_id}/"; // sandbox account.
		} else {
			$this->api_url = "https://api.maropost.com/accounts/{$account_id}/";
		}

		// Get the list.
		$this->list_id = wpf_get_option( 'mp_list' );

		// If no list set, use the first list.
		if ( empty( $this->list_id ) ) {

			$all_lists = wpf_get_option( 'maropost_lists', array() );

			if ( ! empty( $all_lists ) ) {
				$this->list_id = array_keys( $all_lists )[0];
			}
		}

		$this->params = array(
			'user-agent' => 'WP Fusion; ' . home_url(),
			'timeout'    => 20,
			'headers'    => array(
				'Content-Type' => 'application/json',
				'Accept'       => 'application/json',
			),
		);

		return $this->params;
	}

	/**
	 * Initialize connection
	 *
	 * @since 3.15.1
	 * @return  bool
	 */
	public function connect( $account_id = null, $api_key = null, $test = false ) {

		if ( ! $this->params ) {
			$this->get_params( $account_id, $api_key );
		}

		if ( ! $test ) {
			return true;
		}

		$request  = $this->api_url . 'lists.json?auth_token=' . $this->api_key;
		$response = wp_remote_get( $request, $this->params );

		if ( is_wp_error( $response ) ) {

			return $response;
		}

		return true;
	}


	/**
	 * Performs initial sync once connection is configured
	 *
	 * @since 3.15.1
	 * @return bool
	 */
	public function sync() {

		if ( is_wp_error( $this->connect() ) ) {
			return false;
		}

		$this->sync_tags();
		$this->sync_crm_fields();
		$this->sync_lists();

		do_action( 'wpf_sync' );

		return true;
	}


	/**
	 * Gets all available tags and saves them to options
	 *
	 * @since 3.15.1
	 * @return array Lists
	 */
	public function sync_tags() {

		$available_tags = array();

		$request  = $this->api_url . 'tags.json?per=5000&auth_token=' . $this->api_key;
		$response = wp_remote_get( $request, $this->get_params() );

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

		$body_json = json_decode( $response['body'], true );

		if ( ! empty( $body_json ) ) {
			foreach ( $body_json as $tag ) {
				$available_tags[ $tag['name'] ] = $tag['name'];
			}
		}

		wp_fusion()->settings->set( 'available_tags', $available_tags );

		return $available_tags;
	}

	/**
	 * Loads all custom fields from CRM and merges with local list
	 *
	 * @since 3.15.1
	 * @return array CRM Fields
	 */
	public function sync_crm_fields() {

		// Load built in fields to get field types and subtypes
		require __DIR__ . '/admin/maropost-fields.php';

		$built_in_fields = array();

		foreach ( $maropost_fields as $data ) {
			$built_in_fields[ $data['crm_field'] ] = $data['crm_label'];
		}

		$custom_fields = array();
		$request       = $this->api_url . 'custom_fields.json?per=5000&auth_token=' . $this->api_key;
		$response      = wp_remote_get( $request, $this->get_params() );

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

		$body_json = json_decode( $response['body'], true );

		if ( ! empty( $body_json ) ) {

			foreach ( $body_json as $field_data ) {

				if ( ! empty( $field_data['display_name'] ) ) {
					$custom_fields[ $field_data['name'] ] = $field_data['display_name'];
				} else {
					$custom_fields[ $field_data['name'] ] = $field_data['name'];
				}
			}
		}

		asort( $custom_fields );

		$crm_fields = array(
			'Standard Fields' => $built_in_fields,
			'Custom Fields'   => $custom_fields,
		);

		wp_fusion()->settings->set( 'crm_fields', $crm_fields );

		return $crm_fields;
	}


	/**
	 * Loads lists from CRM and merges with local copy
	 *
	 * @since 3.15.1
	 * @return array Lists
	 */
	public function sync_lists() {

		$mp_lists = array();

		$request  = $this->api_url . 'lists.json?auth_token=' . $this->api_key;
		$response = wp_remote_get( $request, $this->get_params() );

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

		$body_json = json_decode( $response['body'], true );

		if ( empty( $body_json ) ) {
			return array();
		}

		foreach ( $body_json as $list ) {
			$mp_lists[ $list['id'] ] = $list['name'];

		}

		wp_fusion()->settings->set( 'maropost_lists', $mp_lists );

		return $mp_lists;
	}

	/**
	 * Gets contact ID for a user based on email address
	 *
	 * @since 3.15.1
	 * @return int Contact ID
	 */
	public function get_contact_id( $email_address ) {

		$request  = $this->api_url . 'contacts/email.json?contact%5Bemail%5D=' . rawurlencode( $email_address ) . '&auth_token=' . $this->api_key;
		$response = wp_remote_get( $request, $this->get_params() );

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

		$body_json = json_decode( $response['body'], true );

		if ( empty( $body_json ) || ! isset( $body_json['id'] ) ) {
			return false;
		}

		return $body_json['id'];
	}




	/**
	 * Gets all tags currently applied to the user, also update the list of available tags
	 *
	 * @since 3.15.1
	 */
	public function get_tags( $contact_id ) {

		$tags = array();

		$request  = $this->api_url . 'contacts/' . $contact_id . '.json?auth_token=' . $this->api_key;
		$response = wp_remote_get( $request, $this->get_params() );

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

		$body_json = json_decode( $response['body'], true );

		if ( empty( $body_json['tags'] ) ) {
			return $tags;
		}

		$tags = wp_list_pluck( $body_json['tags'], 'name' );

		return $tags;
	}

	/**
	 * Applies tags to a contact
	 *
	 * @since 3.15.1
	 * @return bool
	 */
	public function apply_tags( $tags, $contact_id ) {

		$email = wp_fusion()->crm->get_email_from_cid( $contact_id );

		$request          = $this->api_url . 'add_remove_tags.json?auth_token=' . $this->api_key;
		$params           = $this->get_params();
		$params['body']   = wp_json_encode(
			array(
				'tags' => array(
					'email'    => $email,
					'add_tags' => $tags,
				),
			)
		);
		$params['method'] = 'PUT';

		$response = wp_remote_request( $request, $params );

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

		return true;
	}


	/**
	 * Removes tags from a contact
	 *
	 * @since 3.15.1
	 * @return bool
	 */
	public function remove_tags( $tags, $contact_id ) {

		$email = wp_fusion()->crm->get_email_from_cid( $contact_id );

		$request          = $this->api_url . 'add_remove_tags.json?auth_token=' . $this->api_key;
		$params           = $this->get_params();
		$params['body']   = wp_json_encode(
			array(
				'tags' => array(
					'email'       => $email,
					'remove_tags' => $tags,
				),
			)
		);
		$params['method'] = 'PUT';

		$response = wp_remote_post( $request, $params );

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

		return true;
	}


	/**
	 * Adds a new contact
	 *
	 * @since 3.15.1
	 * @return int Contact ID
	 */
	public function add_contact( $data ) {

		$crm_fields = wpf_get_option( 'crm_fields', array() );

		$standard_data = array_intersect_key( $data, $crm_fields['Standard Fields'] );

		$field_data = array(
			'contact' => $standard_data,
		);

		if ( ! empty( $crm_fields['Custom Fields'] ) ) {
			$field_data['contact']['custom_field'] = array_intersect_key( $data, $crm_fields['Custom Fields'] );
		}

		$url            = $this->api_url . 'lists/' . $this->list_id . '/contacts.json?auth_token=' . $this->api_key;
		$params         = $this->get_params();
		$params['body'] = wp_json_encode( $field_data );

		$response = wp_remote_post( $url, $params );

		$body = json_decode( wp_remote_retrieve_body( $response ) );

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

		if ( empty( $body ) || ! isset( $body->id ) ) {
			return new WP_Error( 'error', 'Failed to add contact.' );
		}

		return $body->id;
	}



	/**
	 * Update contact
	 *
	 * @since 3.15.1
	 * @return bool
	 */
	public function update_contact( $contact_id, $data ) {

		$crm_fields = wpf_get_option( 'crm_fields' );
		$send       = false;

		$standard_fields = $crm_fields['Standard Fields'];
		$custom_fields   = $crm_fields['Custom Fields'];

		foreach ( $standard_fields as $key => $crm_field ) {
			foreach ( $custom_fields as $cf_key => $cf_field ) {
				if ( isset( $data[ $key ] ) || isset( $data[ $cf_key ] ) ) {
					$send = true;
					break 2;
				}
			}
		}

		$custom_data   = array_intersect_key( $data, $custom_fields );
		$standard_data = array_intersect_key( $data, $standard_fields );

		$field_data = array(
			'contact' => $standard_data,
		);

		if ( ! empty( $custom_data ) ) {
			$field_data['contact']['custom_field'] = $custom_data;
		}

		if ( $send ) {

			$params = $this->get_params();

			$url              = $this->api_url . 'contacts/' . $contact_id . '.json?auth_token=' . $this->api_key;
			$params['body']   = wp_json_encode( $field_data );
			$params['method'] = 'PUT';

			$response = wp_remote_post( $url, $params );

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

		return true;
	}

	/**
	 * Loads a contact and updates local user meta
	 *
	 * @since 3.15.1
	 * @return array User meta data that was returned
	 */
	public function load_contact( $contact_id ) {

		$url      = $this->api_url . 'contacts/' . $contact_id . '.json?auth_token=' . $this->api_key;
		$response = wp_remote_get( $url, $this->get_params() );

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

		if ( 404 === wp_remote_retrieve_response_code( $response ) ) {
			return new WP_Error( 'error', wp_remote_retrieve_body( $response ) );
		}

		$user_meta      = array();
		$contact_fields = wpf_get_option( 'contact_fields' );
		$body_json      = json_decode( $response['body'], true );

		if ( empty( $body_json ) ) {
			return new WP_Error( 'error', 'Failed to load contact.' );
		}

		foreach ( $body_json as $key => $field ) {
			foreach ( $contact_fields as $field_id => $field_data ) {
				if ( $field_data['active'] == true && $key == $field_data['crm_field'] ) {
					$user_meta[ $field_id ] = $field;
				}
			}
		}

		return $user_meta;
	}


	/**
	 * Gets a list of contact IDs based on list
	 *
	 * @since 3.15.1
	 * @return array Contact IDs returned
	 */
	public function load_contacts( $tag ) {

		$contact_ids = array();
		$proceed     = true;

		while ( $proceed ) {

			$url     = $this->api_url . 'lists/' . $this->list_id . '/contacts.json?auth_token=' . $this->api_key;
			$results = wp_remote_get( $url, $this->get_params() );

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

			$body_json = json_decode( $results['body'], true );

			foreach ( $body_json as $contact ) {
				$contact_ids[] = $contact['id'];
			}

			if ( count( $body_json ) < 50 ) {
				$proceed = false;
			}
		}

		return $contact_ids;
	}
}
