<?php
/**
 * ConnectionManager.php
 *
 * Manages email connections, fallback mechanisms, and testing states.
 *
 * @package SureMails\Inc\Emails\Handler
 */

namespace SureMails\Inc;

use PHPMailer\PHPMailer\PHPMailer;
use SureMails\Inc\Controller\Logger;
use SureMails\Inc\Traits\Instance;

/**
 * Class ConnectionManager
 *
 * Manages email connections, including fallback connections and testing states.
 */
class ConnectionManager {

	use Instance;

	/**
	 * If it's the default connection.
	 *
	 * @var bool
	 */
	public $is_default = false;

	/**
	 * If email simulation is enabled. If enabled, the email will not be sent. Instead, it will be logged. Default is false.
	 *
	 * @var bool
	 */
	public $email_simulation = false;

	/**
	 * If no connection found from a from_email then this will be set to true to get from_email connections same a from_email of default connection.
	 * The default connection will be the first connection in the fallback sequence of from_email connections.
	 *
	 * @var bool
	 */
	public $swap_default_connection = false;

	/**
	 * Global PHPMailer instance.
	 *
	 * @var PHPMailer|null
	 */
	private static ?PHPMailer $phpmailer = null;

	/**
	 * All available connections.
	 *
	 * @var array{connections?: array<string, array<string, string|int|bool>>, default_connection?: array{type: string, email: string, id: string, connection_title: string}, log_emails?: string, delete_email_logs_after?: string, email_simulation?: string}
	 */
	private $connections = [];

	/**
	 * Array of connections filtered by from_email.
	 *
	 * @var array<int, array<string, string|int|bool>>
	 */
	private $from_email_connections = [];

	/**
	 * The currently selected connection data.
	 *
	 * @var array<string, string|int|bool>|null
	 */
	private $current_connection = null;

	/**
	 * Indicates if the current email send is using a fallback connection.
	 *
	 * @var bool
	 */
	private $is_fallback = false;

	/**
	 * Indicates if the current email send is a test email.
	 *
	 * @var bool
	 */
	private $is_testing = false;

	/**
	 * The email address to send from.
	 *
	 * @var string|null
	 */
	private $from_email = null;

	/**
	 * The index of the current connection in the fallback sequence.
	 *
	 * @var int|null
	 */
	private $current_index = null;

	/**
	 * If it's the last connection in the fallback sequence.
	 *
	 * @var bool
	 */
	private $is_last = false;

	/**
	 * If it's a resend.
	 *
	 * @var bool
	 */
	private $is_resend = false;

	/**
	 * If it's a retry.
	 *
	 * @var bool
	 */
	private $is_retried = false;

	/**
	 * If it's the first connection.
	 *
	 * @var bool
	 */
	private $is_first = false;

	/**
	 * Private constructor to enforce Singleton pattern.
	 *
	 * Initializes connection data and sets up hooks for refreshing connections.
	 */
	private function __construct() {
		$this->connections      = Settings::instance()->get_settings();
		$this->email_simulation = Settings::instance()->get_email_simulation_status();
	}

	/**
	 * Sets the current connection data.
	 *
	 * @param array<string, string|int|bool> $connection The connection data to set.
	 * @return void
	 */
	public function set_connection( array $connection ) {
		$this->current_connection = $connection;
	}

	/**
	 * Retrieves the current connection data.
	 *
	 * @return array<string, string|int|bool>|null The current connection data or null if not set.
	 */
	public function get_connection() {
		return $this->current_connection;
	}

	/**
	 * Sets the entire connections data.
	 *
	 * @param array{connections?: array<string, array<string, string|int|bool>>, default_connection?: array{type: string, email: string, id: string, connection_title: string}, log_emails?: string, delete_email_logs_after?: string, email_simulation?: string} $connections The array of all connection data.
	 * @return void
	 */
	public function set_connections( array $connections ) {
		$this->connections = $connections;
	}

	/**
	 * Retrieves all connections data.
	 *
	 * @return array{connections?: array<string, array<string, string|int|bool>>, default_connection?: array{type: string, email: string, id: string, connection_title: string}, log_emails?: string, delete_email_logs_after?: string, email_simulation?: string}|null The array of all connections or null if not set.
	 */
	public function get_connections() {
		return $this->connections;
	}

	/**
	 * Sets the fallback status.
	 *
	 * @param bool $is_fallback Indicates if using fallback connection.
	 * @return void
	 */
	public function set_is_fallback( bool $is_fallback ) {
		$this->is_fallback = $is_fallback;
	}

	/**
	 * Retrieves the fallback status.
	 *
	 * @return bool The fallback status.
	 */
	public function get_is_fallback() {
		return $this->is_fallback;
	}

	/**
	 * Sets the from_email address.
	 *
	 * @param string $from_email The email address to send from.
	 * @return void
	 */
	public function set_from_email( string $from_email ) {
		$this->from_email = strtolower( $from_email );
	}

	/**
	 * Retrieves the from_email address.
	 *
	 * @return string|null The from_email address or null if not set.
	 */
	public function get_from_email() {
		return $this->from_email;
	}
	/**
	 * Sets the testing status.
	 *
	 * @param bool $is_testing Indicates if sending a test email.
	 * @return void
	 */
	public function set_is_testing( bool $is_testing ) {
		$this->is_testing = $is_testing;
	}

	/**
	 * Retrieves the testing status.
	 *
	 * @return bool The testing status.
	 */
	public function get_is_testing() {
		return $this->is_testing;
	}

	/**
	 * Sets the last status.
	 *
	 * @param bool $is_retried Indicates if the email is retried.
	 * @return void
	 */
	public function set_is_retry( bool $is_retried ) {
		$this->is_retried = $is_retried;
	}

	/**
	 * Retrieves the last status.
	 *
	 * @return bool The last status.
	 */
	public function get_is_retried() {
		return $this->is_retried;
	}

	/**
	 * Retrieves the last status.
	 *
	 * @return bool The last status.
	 */
	public function get_is_last() {
		return $this->is_last;
	}

	/**
	 * Sets the is first connection.
	 *
	 * @return bool
	 */
	public function get_is_first() {
		return $this->is_first;
	}

	/**
	 * Set if the email is a resend.
	 *
	 * @param bool $is_resend Indicates if the email is a resend.
	 * @return void
	 */
	public function set_is_resend( bool $is_resend ) {
		$this->is_resend = $is_resend;
	}

	/**
	 * Retrieves the resend status.
	 *
	 * @return bool The resend status.
	 */
	public function get_is_resend() {
		return $this->is_resend;
	}

	/**
	 * Initializes the PHPMailer instance if not already set.
	 *
	 * @return PHPMailer The PHPMailer instance.
	 */
	public function get_phpmailer() {
		if ( ! self::$phpmailer ) {
			require_once ABSPATH . WPINC . '/PHPMailer/PHPMailer.php';
			require_once ABSPATH . WPINC . '/PHPMailer/SMTP.php';
			require_once ABSPATH . WPINC . '/PHPMailer/Exception.php';

			self::$phpmailer             = new PHPMailer( true );
			self::$phpmailer::$validator = static function ( $email ) {
				return (bool) is_email( $email );
			};
		}

		return self::$phpmailer;
	}

	/**
	 * Resets the PHPMailer instance to its default state.
	 *
	 * @return void
	 */
	public function reset_phpmailer() {
		self::$phpmailer = null;
	}

	/**
	 * Adjusts the current PHPMailer instance with connection-specific settings.
	 *
	 * @param array<string, string|int|bool> $connection The connection settings.
	 * @return void
	 */
	public function configure_phpmailer( array $connection ) {
		$phpmailer = $this->get_phpmailer();

		$from_email = (string) ( $connection['from_email'] ?? '' );
		$from_name  = (string) ( $connection['from_name'] ?? '' );

		if ( ! empty( $from_email ) ) {
			$phpmailer->setFrom( $from_email, $from_name );
		}
	}

	/**
	 * Retrieves the next connection based on priority.
	 *
	 * @return array<string, string|int|bool>|null The next connection details or null if no more connections are available.
	 */
	public function get_next_connection() {
		if ( $this->current_index === null ) {
			$this->current_index = 0;
			$this->is_first      = true;
		} else {
			$this->current_index++;
			$this->is_first = false;
		}

		$next_index             = $this->current_index;
		$from_email_connections = $this->from_email_connections;

		if ( $next_index + 1 >= count( $from_email_connections ) ) {
			$this->is_last = true;
		}
		if ( $next_index >= count( $from_email_connections ) ) {

			$this->is_last       = true;
			$this->current_index = null;
			return null;
		}

		return $from_email_connections[ $next_index ];
	}

	/**
	 * Retrieves the fallback connection based on priority.
	 *
	 * @return array<string, string|int|bool>|null The fallback connection details or null if not found.
	 */
	public function get_priority_based_fallback_connection() {
		$connections = $this->connections;
		if ( empty( $connections ) ) {
			return null;
		}
		$connections = $connections['connections'] ?? null;
		if ( ! $connections ) {
			return null;
		}

		$from_email_connections = $this->get_from_email_connections();

		if ( empty( $from_email_connections ) ) {
			return null;
		}

		return $this->get_next_connection();
	}

	/**
	 * Resets the ConnectionManager state to default.
	 *
	 * @return void
	 */
	public function reset() {
		$this->current_index           = null;
		$this->is_fallback             = false;
		$this->is_testing              = false;
		$this->from_email_connections  = [];
		$this->current_connection      = null;
		$this->from_email              = null;
		$this->is_last                 = false;
		$this->is_first                = false;
		$this->is_default              = false;
		$this->is_resend               = false;
		$this->is_retried              = false;
		$this->swap_default_connection = false;
		Logger::instance()->set_id( null );
	}

	/**
	 * Retrieves the default connection based on settings.
	 *
	 * @param bool $set_checks bool If set to true, it will set the is_default property.
	 * @return array<string, string|int|bool>|null The default connection details or null if not found.
	 */
	public function get_default_connection( $set_checks = true ) {
		$settings    = $this->get_connections();
		$connections = $settings['connections'] ?? null;

		if ( ! $connections ) {
			return null;
		}

		$default_connection_id = $settings['default_connection']['id'] ?? null;
		$default_connection    = $connections[ $default_connection_id ] ?? null;
		if ( $default_connection && $set_checks ) {
			$this->is_default = true;
		}
		return $default_connection;
	}

	/**
	 * Retrieves and sorts connections filtered by the current from_email.
	 *
	 * @return array<int, array<string, string|int|bool>>|null The array of filtered and sorted connections or null if not found.
	 */
	private function get_from_email_connections() {
		if ( $this->get_from_email() === null ) {
			return null;
		}

		$connections = $this->connections['connections'] ?? null;

		if ( ! $connections ) {
			return null;
		}

		$from_email_connections = [];

		foreach ( $connections as $connection ) {
			if ( strtolower( (string) $connection['from_email'] ) === $this->from_email ) {
				$send = $connection;

				$from_email_connections[] = $send;
			}
		}

		// Sort the $from_email_connections array by 'priority' in ascending order.
		usort(
			$from_email_connections,
			static function ( $a, $b ) {
				return $a['priority'] <=> $b['priority'];
			}
		);

		// If no connection found from a from_email then this will be set to true to get from_email connections same a from_email of default connection.
		// The default connection will be the first connection in the fallback sequence of from_email connections. This is to ensure that the default connection is always tried first. It will try with default connection then it will try with other from_email connections based on priority.
		if ( $this->swap_default_connection ) {
			$default_connection_id = $this->connections['default_connection']['id'] ?? null;
			if ( $default_connection_id ) {
				foreach ( $from_email_connections as $key => $from_email_connection ) {
					if ( $from_email_connection['id'] === $default_connection_id ) {
						$default_connection = $from_email_connections[ $key ];
						unset( $from_email_connections[ $key ] );
						array_unshift( $from_email_connections, $default_connection );
					}
				}
			}
		}

		$this->from_email_connections = $from_email_connections;

		return $from_email_connections;
	}
}
