<?php

namespace AutomateWoo;

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

/**
 * Integration class for Mailchimp.
 *
 * @class Integration_Mailchimp
 */
class Integration_Mailchimp extends Integration {

	/**
	 * ID of this integration.
	 *
	 * @var string
	 */
	public $integration_id = 'mailchimp';

	/**
	 * The Mailchimp API key.
	 *
	 * @var string
	 */
	private $api_key;

	/**
	 * The root API URL for Mailchimp.
	 *
	 * @var string
	 */
	private $api_root = 'https://<dc>.api.mailchimp.com/3.0';

	/**
	 * Constructor.
	 *
	 * @param string $api_key The Mailchimp API key.
	 */
	public function __construct( $api_key ) {
		list( $key, $data_center ) = explode( '-', $api_key );

		$this->api_key  = $key;
		$this->api_root = str_replace( '<dc>', $data_center, $this->api_root );
	}


	/**
	 * Test the current API key to confirm that AutomateWoo can communicate with the Mailchimp API
	 *
	 * @return bool
	 */
	public function test_integration(): bool {
		return $this->request( 'GET', '/' )->is_successful();
	}

	/**
	 * Check if the integration is enabled.
	 *
	 * @return bool
	 */
	public function is_enabled(): bool {
		return (bool) Options::mailchimp_enabled();
	}

	/**
	 * Make a request to the API.
	 *
	 * @param string $method   The request method.
	 * @param string $endpoint The request endpoint.
	 * @param array  $args     Additional request arguments.
	 *
	 * @return Remote_Request
	 */
	public function request( $method, $endpoint, $args = [] ) {
		$request_args = [
			'headers'   => [
				'Authorization' => 'Basic ' . base64_encode( 'anystring:' . $this->api_key ),
			],
			'timeout'   => 15,
			'method'    => $method,
			'sslverify' => false,
		];

		$url = $this->api_root . $endpoint;

		switch ( $method ) {
			case 'GET':
				// SEMGREP WARNING EXPLANATION
				// This is escaped with esc_url_raw, but semgrep only takes into consideration esc_url.
				$url = esc_url_raw( add_query_arg( array_map( 'urlencode', $args ), $url ) );
				break;

			default:
				$request_args['body'] = wp_json_encode( $args );
				break;
		}

		$request = new Remote_Request( $url, $request_args );
		$this->maybe_log_request_errors( $request );

		return $request;
	}

	/**
	 * Get Mailchimp lists.
	 *
	 * @return array
	 */
	public function get_lists() {
		$cache = Cache::get_transient( 'mailchimp_lists' );
		if ( $cache ) {
			return $cache;
		}

		$request = $this->request(
			'GET',
			'/lists',
			[
				'count' => 100,
			]
		);

		$clean_lists = [];

		if ( $request->is_successful() ) {
			$body = $request->get_body();

			if ( is_array( $body['lists'] ) ) {
				foreach ( $body['lists'] as $list ) {
					$clean_lists[ $list['id'] ] = $list['name'];
				}
			}
		}

		Cache::set_transient( 'mailchimp_lists', $clean_lists, 0.15 );

		return $clean_lists;
	}

	/**
	 * Get the fields for a particular list.
	 *
	 * @param string $list_id The list ID.
	 *
	 * @return array
	 */
	public function get_list_fields( $list_id ) {
		if ( ! $list_id ) {
			return [];
		}

		$cache_key = "mailchimp_list_fields_$list_id";
		$cache     = Cache::get_transient( $cache_key );
		if ( $cache ) {
			return (array) $cache;
		}

		$request = $this->request(
			'GET',
			"/lists/$list_id/merge-fields",
			[
				'count' => 100,
			]
		);

		if ( ! $request->is_successful() ) {
			return [];
		}

		$body   = $request->get_body();
		$fields = isset( $body['merge_fields'] ) ? $body['merge_fields'] : [];

		Cache::set_transient( $cache_key, $fields, 0.15 );

		return $fields;
	}

	/**
	 * Get interest categories for a particular list.
	 *
	 * @param string $list_id The list ID.
	 * @return array
	 */
	public function get_list_interest_categories( $list_id ) {
		if ( ! $list_id ) {
			return [];
		}

		$cache_key = "mc_list_interests_$list_id";
		$cache     = Cache::get_transient( $cache_key );
		if ( $cache ) {
			return (array) $cache;
		}

		$data    = [];
		$request = $this->request(
			'GET',
			"/lists/$list_id/interest-categories",
			[
				'count' => 100,
			]
		);

		if ( ! $request->is_successful() ) {
			return [];
		}

		$body        = $request->get_body();
		$categories  = isset( $body['categories'] ) ? (array) $body['categories'] : [];
		$has_failure = false;
		foreach ( $categories as $category ) {
			$interests = $this->get_interest_categories_interests( $list_id, $category['id'] );
			if ( null === $interests ) {
				$has_failure = true;
				$interests   = [];
			}
			$data[ $category['id'] ] = [
				'id'        => $category['id'],
				'title'     => $category['title'],
				'interests' => $interests,
			];
		}

		if ( ! $has_failure ) {
			Cache::set_transient( $cache_key, $data, 0.15 );
		}

		return $data;
	}

	/**
	 * Get interests for a particular category from the API.
	 *
	 * Protected method due to not being cached, use $this->get_list_interest_categories()
	 *
	 * Returns null on request failure so the caller can distinguish a failed fetch
	 * from a category that genuinely has no interests (empty array).
	 *
	 * @since 6.5.0
	 *
	 * @param string $list_id     The list ID.
	 * @param string $category_id The category ID.
	 *
	 * @return array|null Array of interests on success, null on request failure.
	 */
	protected function get_interest_categories_interests( $list_id, $category_id ) {
		$data    = [];
		$request = $this->request(
			'GET',
			"/lists/$list_id/interest-categories/$category_id/interests",
			[
				'count' => 100,
			]
		);

		if ( ! $request->is_successful() ) {
			return null;
		}

		$body      = $request->get_body();
		$interests = isset( $body['interests'] ) ? (array) $body['interests'] : [];
		foreach ( $interests as $interest ) {
			$data[ $interest['id'] ] = $interest['name'];
		}

		return $data;
	}

	/**
	 * Determine whether a contact is part of the given list.
	 *
	 * This does not reveal whether they are a subscriber. For that, see the is_subscribed_to_list() method.
	 *
	 * @param string $email   The email address.
	 * @param string $list_id The list ID.
	 *
	 * @return bool
	 */
	public function is_contact( $email, $list_id ) {
		$status = $this->get_subscriber_status_for_list( $email, $list_id );

		return '' !== $status && '404' !== $status;
	}

	/**
	 * Determine whether a contact is subscribed to the given list.
	 *
	 * This method should be used for determining if a customer is subscribed to marketing emails. For transactional
	 * emails,
	 *
	 * @param string $email   The email address.
	 * @param string $list_id The Mailchimp list ID.
	 *
	 * @return bool
	 */
	public function is_subscribed_to_list( $email, $list_id ) {
		$status = $this->get_subscriber_status_for_list( $email, $list_id );

		return 'subscribed' === $status;
	}

	/**
	 * Update interest groups for a contact.
	 *
	 * $interests should be an array with the interest ID as the key and
	 * true or false as the value, depending on whether adding or removing the group
	 *
	 * @param string $email     The contact's email address.
	 * @param string $list_id   The list ID.
	 * @param array  $interests Array of interests.
	 *
	 * @return Remote_Request
	 */
	public function update_contact_interest_groups( $email, $list_id, $interests ) {
		$subscriber_hash = md5( $email );

		$args = [
			'email_address' => $email,
			'status_if_new' => 'subscribed',
			'interests'     => $interests,
		];

		return $this->request( 'PUT', "/lists/$list_id/members/$subscriber_hash", $args );
	}

	/**
	 * Clear the cache for lists.
	 */
	public function clear_cache_data() {
		Cache::delete_transient( 'mailchimp_lists' );
	}

	/**
	 * Get the subscriber information for a particular list.
	 *
	 * @param string $email   The email address to check.
	 * @param string $list_id The list ID.
	 *
	 * @return string
	 */
	private function get_subscriber_status_for_list( $email, $list_id ) {
		// Check the cache first.
		$cache_key = "mailchimp_status_for_list_{$list_id}";
		if ( Temporary_Data::exists( $cache_key, $email ) ) {
			return Temporary_Data::get( $cache_key, $email );
		}

		$email_hash = md5( $email );

		// will return 404 if subscriber doesn't exists, so don't log errors for this request
		$this->log_errors = false;
		$request          = $this->request( 'GET', "/lists/{$list_id}/members/{$email_hash}", [] );
		$this->log_errors = true;

		// Bail and don't cache the HTTP error.
		if ( $request->is_http_error() ) {
			return '';
		}

		$body   = $request->get_body();
		$status = isset( $body['status'] ) ? (string) $body['status'] : '';

		Temporary_Data::set( $cache_key, $email, $status );

		return $status;
	}

	/**
	 * Get the tags for a particular member.
	 *
	 * @since 4.8.0
	 *
	 * @param string $email   The member's email address.
	 * @param string $list_id The list ID.
	 *
	 * @return array
	 */
	public function get_member_tags( $email, $list_id ) {
		$return  = [];
		$hash    = md5( $email );
		$request = $this->request(
			'GET',
			"/lists/{$list_id}/members/{$hash}/tags",
			[
				'count' => 100,
			]
		);

		if ( ! $request->is_successful() ) {
			return $return;
		}

		$body = $request->get_body();
		$tags = isset( $body['tags'] ) ? (array) $body['tags'] : [];
		foreach ( $tags as $tag ) {
			$return[ strtolower( $tag['name'] ) ] = $tag['name'];
		}

		return $return;
	}

	/**
	 * Update the tags for a particular member.
	 *
	 * The array of $tags should have the tag name as an index, and either true or false
	 * as a value. Tags with "true" as a value will be added to the member, and tags
	 * with "false" as a value will be removed from the member.
	 *
	 * Example: Add tag "top buyer" and remove "no sales":
	 *
	 * [
	 *     'top buyer' => true,
	 *     'no sales'  => false,
	 * ]
	 *
	 * @since 4.8.0
	 *
	 * @param string $email   The member's email address.
	 * @param string $list_id The list ID.
	 * @param array  $tags    Array of tags to update.
	 *
	 * @return Remote_Request The request
	 */
	public function update_member_tags( $email, $list_id, $tags ) {
		$hash = md5( $email );
		$args = [];
		foreach ( $tags as $tag => $active ) {
			$args[] = [
				'name'   => (string) $tag, // Must be a string.
				'status' => $active ? 'active' : 'inactive',
			];
		}

		return $this->request(
			'POST',
			"/lists/{$list_id}/members/{$hash}/tags",
			[
				'tags' => $args,
			]
		);
	}
}
