<?PHP
/**
 * Contains a class for checking the validity of mail addresses and their
 * hostnames, cached through the central noda DB.
 *
 * @author Joshua Ramon Enslin <joshua@museum-digital.de>
 */
declare(strict_types = 1);

/**
 * Contains static functions for checking the validity of mail addresses.
 */
final class NodaMailChecker {
    /**
     * Splits a mail address and returns the hostname.
     *
     * @param string $mail_address Mail address.
     *
     * @return string
     */
    public static function getDomainFromMailAddress(string $mail_address):string {

        $mail_address = MD_STD_IN::sanitize_email($mail_address);

        $addressParts = explode('@', $mail_address);
        if (empty($addressParts[1])) {
            throw new MDInvalidEmail("Failed to get mail domain");
        }
        $domain = $addressParts[1];

        if (str_contains($domain, '.') === false) {
            throw new MDInvalidEmail("Failed to find a dot in the mail domain");
        }

        return $domain;

    }

    /**
     * Checks if a mail hostname is accessible.
     *
     * @param string $mail_domain Hostname of the mail address.
     *
     * @return string
     */
    public static function validateMailDomainAccessibility(string $mail_domain):string {

        $mxRecords = [];
        getmxrr($mail_domain, $mxRecords);
        $mxRecords = array_diff($mxRecords, ['0.0.0.0', '', null]);

        if (empty($mxRecords)) return "";

        return MD_STD_IN::sanitize_text(implode(',', $mxRecords));

    }

    /**
     * Checks if a mail hostname is accessible.
     *
     * @param MDMysqli $mysqli      DB connection.
     * @param string   $mail_domain Hostname of the mail address.
     *
     * @return boolean
     */
    public static function validateMailDomainAccessibilityCached(MDMysqli $mysqli, string $mail_domain):bool {

        $result = $mysqli->query_by_stmt("SELECT `valid`, `hostname`
            FROM `" . DATABASENAME_NODA . "`.`misc_mail_hostname_validity`
            WHERE `hostname` = ?", "s", $mail_domain);

        if ($cur = $result->fetch_row()) {
            $result->close();
            return (bool)$cur[0];
        }
        $result->close();

        $target_hostnames = self::validateMailDomainAccessibility($mail_domain);
        $validity = !empty($target_hostnames);

        $insertStmt = $mysqli->do_prepare("INSERT
            INTO `" . DATABASENAME_NODA . "`.`misc_mail_hostname_validity`
            (`hostname`, `valid`, `resolved_hostnames`)
            VALUES
            (?, ?, ?)");

        $insertStmt->bind_param("sis", $mail_domain, $validity, $target_hostnames);
        $insertStmt->execute();

        $insertStmt->close();

        return $validity;

    }

    /**
     * Checks a mail address for the validity of its hostname.
     *
     * @param MDMysqli $mysqli       DB connection.
     * @param string   $mail_address Hostname of the mail address.
     *
     * @return boolean
     */
    public static function validateMailByDomainAccessibilityCached(MDMysqli $mysqli, string $mail_address):bool {

        $domain = self::getDomainFromMailAddress($mail_address);
        return self::validateMailDomainAccessibilityCached($mysqli, $domain);

    }

    /**
     * Validates mail address, throwing an exception if the mail address is not valid.
     *
     * @param MDMysqli $mysqli       DB connection.
     * @param string   $mail_address Address.
     *
     * @return string
     */
    public static function validateMail(MDMysqli $mysqli, string $mail_address):string {

        if (empty($mail_address)) return $mail_address;

        $mail_address = MD_STD_IN::sanitize_email($mail_address);

        if (self::validateMailByDomainAccessibilityCached($mysqli, $mail_address) === false) {
            throw new MDInvalidEmail("The host name of the entered mail address is invalid");
        }

        return $mail_address;

    }
}
