<?php

/**
 * Helper class for shareable links for each team.
 */
class Ekc_Shareable_Links_Helper {

	public function store_shareable_links_content( $tournament_id, $url_prefix, $email_content, $sender_email ) {
		$tournament = new Ekc_Tournament();
		$tournament->set_tournament_id( $tournament_id );
		$tournament->set_shareable_link_url_prefix( $url_prefix );
		$tournament->set_shareable_link_email_text( $email_content );
		$tournament->set_shareable_link_sender_email( $sender_email );

		$db = new Ekc_Database_Access();
		$db->update_shareable_link_data( $tournament );
	}

	public function generate_all_shareable_links( $tournament_id ) {
		$db = new Ekc_Database_Access();
		$teams = $db->get_active_teams( $tournament_id );
		foreach ( $teams as $team ) {
			$this->generate_shareable_link( $team->get_team_id() ); 
		}
	}

	public function generate_shareable_link( $team_id ) {
		$db = new Ekc_Database_Access();
		$link_id = $this->create_shareable_link_id( $team_id );
		$db->update_shareable_link_id( $team_id, $link_id );
	}

	public function send_all_shareable_links_by_mail( $tournament_id ) {
		$db = new Ekc_Database_Access();
		$tournament = $db->get_tournament_by_id( $tournament_id );
		$teams = $db->get_active_teams( $tournament_id );
		
		foreach ( $teams as $team ) {
			$this->send_shareable_link( $tournament, $team ); 
		}
	}

	public function send_shareable_link_by_mail( $tournament_id, $team_id ) {
		$db = new Ekc_Database_Access();
		$tournament = $db->get_tournament_by_id( $tournament_id );
		$team = $db->get_team_by_id( $team_id );

		$this->send_shareable_link( $tournament, $team );
	}
	
	private function send_shareable_link( $tournament, $team ) {
		$url = $this->create_shareable_link_url( $tournament->get_shareable_link_url_prefix(), $team->get_shareable_link_id() );
		$email_content = $this->replace_placeholder( $tournament->get_shareable_link_email_text(), $team->get_name(), $url );
		$sender_email = $tournament->get_shareable_link_sender_email();

		if ( $team->get_email() && $team->get_shareable_link_id() ) {
			$this->send_mail( $team->get_team_id(), $team->get_email(), $tournament->get_name(), $email_content, $sender_email );
		}
	} 

	private function replace_placeholder( $email_content, $team_name, $url ) {
		$email_content_replaced = str_replace( '${team}', esc_html( $team_name ), $email_content );
		$email_content_replaced = str_replace( '${url}', esc_url( $url ), $email_content_replaced );
		return $email_content_replaced;
	}

	/**
	 * We use wp_mail() to send emails
	 * 1) if no smtp server is configured, wp_mail uses php mail() function
	 *    and relies on the hosting platform for correct configuration
	 * 2) WP Plugins such as 'WP Mail SMTP' allow to configure an smtp server
	 *    for wp_mail function. This is the recommended option.
	 *    https://wordpress.org/plugins/wp-mail-smtp/
	 */
	private function send_mail( $team_id, $recipient_email_address, $subject, $email_content, $sender_email ) {
		// use a global variable to pass the current team_id to the wp_mail_failed and wp_mail_succeeded actions
		global $ekc_team_id;
		$ekc_team_id = $team_id;
		
		$headers = array('Content-Type: text/html; charset=UTF-8');
		if ( $sender_email ) {
			$headers[] = 'From: ' . $sender_email;
		}
		$html_email_content = '<html>' . $email_content . '</html>';
		
		// ignore return value of wp_mail()
		// wp_mail() internally uses a PHPMailer instance with exceptions=true, so it cannot return false, but will throw an exception, which will be handled by wp_mail_failed
		wp_mail( $recipient_email_address, $subject, $html_email_content, $headers );
	}

	public function on_wp_mail_succeeded( $mail_data ) {
		$email_address = '';
		if ( array_key_exists( 'to', $mail_data ) ) {
			$email_address = $mail_data['to'][0];
		}

		$this->store_email_status( Ekc_Drop_Down_Helper::EMAIL_STATUS_SENT, $email_address, null );
	}

	public function on_wp_mail_failed( $wp_error ) {
		$email_address = '';
		$error_message = '';
		if ( $wp_error->has_errors() ) {
			$error_message = $wp_error->get_error_message();
			$mail_data = $wp_error->get_error_data();
			if ( array_key_exists( 'to', $mail_data ) ) {
				$email_address = $mail_data['to'][0];
			}
		}
		
		$this->store_email_status( Ekc_Drop_Down_Helper::EMAIL_STATUS_FAILED, $email_address, $error_message );
	}

	private function store_email_status( $email_status, $email_address, $status_message ) {
		// $phpmailer is defined by wp_mail(), $ekc_team_id is defined by Ekc_Shareable_Links_Helper.send_mail()
		global $phpmailer, $ekc_team_id;
		$db = new Ekc_Database_Access();
		$db->delete_scheduled_email_status_for_team( $ekc_team_id );
		$db->insert_email_status( $ekc_team_id, $phpmailer->getLastMessageID(), $email_status, $email_address, $status_message, null );
	}

	public function cancel_scheduled_email( $tournament_id, $team_id ) {
		$db = new Ekc_Database_Access();
		$db->delete_scheduled_email_status_for_team( $team_id );
	}

	public function schedule_emails( $tournament_id, $batch_size, $email_status ) {
		$db = new Ekc_Database_Access();

		// check for new teams
		$new_team_ids = array();
		if ( in_array( Ekc_Drop_Down_Helper::EMAIL_STATUS_NEW, $email_status, true ) ) {
			$new_team_ids = $db->get_team_ids_for_email_status_new( $tournament_id );
		}
		
		// check for existing email status (sent and failed)
		$sent_team_ids = array();
		if ( in_array( Ekc_Drop_Down_Helper::EMAIL_STATUS_SENT, $email_status, true ) ) {
			$sent_team_ids = $db->get_team_ids_for_email_status( $tournament_id, Ekc_Drop_Down_Helper::EMAIL_STATUS_SENT );
		}
		$failed_team_ids = array();
		if ( in_array( Ekc_Drop_Down_Helper::EMAIL_STATUS_FAILED, $email_status, true ) ) {
			$failed_team_ids = $db->get_team_ids_for_email_status( $tournament_id, Ekc_Drop_Down_Helper::EMAIL_STATUS_FAILED );
		}
		
		// scheduled teams will be re-scheduled
		$scheduled_team_ids = $db->get_team_ids_for_email_status( $tournament_id, Ekc_Drop_Down_Helper::EMAIL_STATUS_SCHEDULED );
		if ( !empty( $scheduled_team_ids ) ) {
			$db->delete_scheduled_email_status_for_tournament( $tournament_id );
		}

		// array_values ensures ascending numeric keys without gaps, which might result from array_unique, which preserves numeric keys 
		$all_team_ids = array_values( array_unique( array_merge( $scheduled_team_ids, $new_team_ids, $failed_team_ids, $sent_team_ids ) ) );

		$teams_count = count( $all_team_ids );
		$batches = intval( ceil( $teams_count / $batch_size ) );
		$team_index = 0;
		for ( $scheduled_batch = $batches; $scheduled_batch > 0; $scheduled_batch-- ) {
			for ( $i = 0; $i < $batch_size && $team_index < $teams_count; $i++ ) {
				$db->insert_email_status( $all_team_ids[$team_index], null, Ekc_Drop_Down_Helper::EMAIL_STATUS_SCHEDULED, null, null, $scheduled_batch );
				$team_index++;
			}
		}

		// schedule cron event (if not already scheduled)
		if ( ! wp_next_scheduled( 'ekc_check_scheduled_mails' ) ) {
			wp_schedule_event( time() + 300 /* start first batch in 5 minutes */, 'ekc_email_scheduler', 'ekc_check_scheduled_mails' );
		}
	}
	
	public function check_scheduled_mails() {
		$db = new Ekc_Database_Access();
		$batches_by_tournament = $db->get_next_scheduled_emails_batch();

		if ( !empty( $batches_by_tournament ) ) {
			// process current batch
			foreach ( $batches_by_tournament as $tournament_id => $team_ids ) {
				$tournament = $db->get_tournament_by_id( $tournament_id );
				foreach ( $team_ids as $team_id ) {
					$team = $db->get_team_by_id( intval( $team_id ) );
					$this->send_shareable_link( $tournament, $team );
				}
			}
		}
		else {
			// unschedule cron event if nothing to to - it will be re-scheduled if required
			$scheduled_timestamp = wp_next_scheduled( 'ekc_check_scheduled_mails' );
			if ( $scheduled_timestamp ) {
				wp_unschedule_event( $scheduled_timestamp, 'ekc_check_scheduled_mails' );
			}
		}
	}

	public function send_next_scheduled_batch( $tournament_id ) {
		$db = new Ekc_Database_Access();
		$batches_by_tournament = $db->get_next_scheduled_emails_batch();
		if ( !empty( $batches_by_tournament ) ) {
			// process next batch
			foreach ( $batches_by_tournament as $batch_tournament_id => $team_ids ) {
				if ( intval( $tournament_id ) === intval( $batch_tournament_id ) ) {
					$tournament = $db->get_tournament_by_id( $tournament_id );
					foreach ( $team_ids as $team_id ) {
						$team = $db->get_team_by_id( intval( $team_id ) );
						$this->send_shareable_link( $tournament, $team );
					}
					break;
				}
			}
		}
	}

	public function create_shareable_link_url( $url_prefix, $link_id ) {
		return $url_prefix . 'linkid=' . $link_id;
	}

	private function create_shareable_link_id( $team_id ) {
		return $team_id . '-' . $this->random_str(20);
	}

/**
 * Generate a random string, using a cryptographically secure 
 * pseudorandom number generator (random_int)
 * 
 * @param int $length      How many characters do we want?
 * @param string $keyspace A string of all possible characters
 *                         to select from
 * @return string
 */
 private function random_str(
    int $length = 64,
    string $keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
    ): string {
    if ($length < 1) {
        throw new \RangeException('Length must be a positive integer');
    }
    $pieces = [];
    $max = mb_strlen($keyspace, '8bit') - 1;
    for ($i = 0; $i < $length; ++$i) {
        $pieces []= $keyspace[random_int(0, $max)];
    }
    return implode('', $pieces);
  }
}

