<?php

class SwpmAuth {

    public $protected;
    public $permitted;
    private $isLoggedIn;
    private $lastStatusMsg;
    private static $_this;
    public $userData;

    private function __construct() {
        //check if we need to display custom message on the login form
        $custom_msg = filter_input(INPUT_COOKIE, 'swpm-login-form-custom-msg', FILTER_SANITIZE_STRING);
        if (!empty($custom_msg)) {
            $this->lastStatusMsg = $custom_msg;
            //let's 'unset' the cookie
            setcookie('swpm-login-form-custom-msg', '', time() - 3600, COOKIEPATH, COOKIE_DOMAIN);
        }
        $this->isLoggedIn = false;
        $this->userData = null;
        $this->protected = SwpmProtection::get_instance();
    }

    private function init() {
        $valid = $this->validate();
        //SwpmLog::log_auth_debug("init:". ($valid? "valid": "invalid"), true);
        if (!$valid) {
            $this->authenticate();
        }
    }

    public static function get_instance() {
        if (empty(self::$_this)) {
            self::$_this = new SwpmAuth();
            self::$_this->init();
        }
        return self::$_this;
    }

    private function authenticate($user = null, $pass = null) {
        global $wpdb;
        $swpm_password = empty($pass) ? filter_input(INPUT_POST, 'swpm_password') : $pass;
        $swpm_user_name = empty($user) ? apply_filters('swpm_user_name', filter_input(INPUT_POST, 'swpm_user_name')) : $user;

        if (!empty($swpm_user_name) && !empty($swpm_password)) {
            //SWPM member login request.
            //Trigger action hook that can be used to check stuff before the login request is processed by the plugin.
            $args = array('username' => $swpm_user_name, 'password' => $swpm_password);
            do_action('swpm_before_login_request_is_processed', $args);

            //First, lets make sure this user is not already logged into the site as an "Admin" user. We don't want to override that admin login session.
            if (current_user_can('administrator')) {
                //This user is logged in as ADMIN then trying to do another login as a member. Stop the login request processing (we don't want to override your admin login session).
                $wp_profile_page = SIMPLE_WP_MEMBERSHIP_SITE_HOME_URL . '/wp-admin/profile.php';
                $error_msg = '';
                $error_msg .= '<p>' . SwpmUtils::_('Warning! Simple Membership plugin cannot process this login request to prevent you from getting logged out of WP Admin accidentally.') . '</p>';
                $error_msg .= '<p><a href="' . $wp_profile_page . '" target="_blank">' . SwpmUtils::_('Click here') . '</a>' . SwpmUtils::_(' to see the profile you are currently logged into in this browser.') . '</p>';
                $error_msg .= '<p>' . SwpmUtils::_('You are logged into the site as an ADMIN user in this browser. First, logout from WP Admin then you will be able to log in as a normal member.') . '</p>';
                $error_msg .= '<p>' . SwpmUtils::_('Alternatively, you can use a different browser (where you are not logged-in as ADMIN) to test the membership login.') . '</p>';
                $error_msg .= '<p>' . SwpmUtils::_('Your normal visitors or members will never see this message. This message is ONLY for ADMIN user.') . '</p>';
                wp_die($error_msg);
            }

            //If captcha is present and validation failed, it returns an error string. If validation succeeds, it returns an empty string.
            $captcha_validation_output = apply_filters('swpm_validate_login_form_submission', '');
            if (!empty($captcha_validation_output)) {
                $this->lastStatusMsg = SwpmUtils::_('Captcha validation failed on login form.');
                return;
            }

            if (is_email($swpm_user_name)) {//User is trying to log-in using an email address
                $email = sanitize_email($swpm_user_name);
                $query = $wpdb->prepare("SELECT user_name FROM " . $wpdb->prefix . "swpm_members_tbl WHERE email = %s", $email);
                $username = $wpdb->get_var($query);
                if ($username) {//Found a user record
                    $swpm_user_name = $username; //Grab the usrename value so it can be used in the authentication process.
                    SwpmLog::log_auth_debug("Authentication request using email address: " . $email . ', Found a user record with username: ' . $swpm_user_name, true);
                }
            }

            //Lets process the request. Check username and password
            $user = sanitize_user($swpm_user_name);
            $pass = trim($swpm_password);
            SwpmLog::log_auth_debug("Authentication request - Username: " . $swpm_user_name, true);

            $query = "SELECT * FROM " . $wpdb->prefix . "swpm_members_tbl WHERE user_name = %s";
            $userData = $wpdb->get_row($wpdb->prepare($query, $user));
            $this->userData = $userData;
            if (!$userData) {
                $this->isLoggedIn = false;
                $this->userData = null;
                $this->lastStatusMsg = SwpmUtils::_("User Not Found.");
                return false;
            }
            $check = $this->check_password($pass, $userData->password);
            if (!$check) {
                $this->isLoggedIn = false;
                $this->userData = null;
                $this->lastStatusMsg = SwpmUtils::_("Password Empty or Invalid.");
                return false;
            }
            if ($this->check_constraints()) {
                $rememberme = filter_input(INPUT_POST, 'rememberme');
                $remember = empty($rememberme) ? false : true;
                $this->set_cookie($remember);
                $this->isLoggedIn = true;
                $this->lastStatusMsg = "Logged In.";
                SwpmLog::log_auth_debug("Authentication successful for username: " . $user . ". Executing swpm_login action hook.", true);
                do_action('swpm_login', $user, $pass, $remember);
                return true;
            }
        }
        return false;
    }

    private function check_constraints() {
        if (empty($this->userData)) {
            return false;
        }
        global $wpdb;
        $enable_expired_login = SwpmSettings::get_instance()->get_value('enable-expired-account-login', '');

        //Update the last accessed date and IP address for this login attempt. $wpdb->update(table, data, where, format, where format)
        $last_accessed_date = current_time('mysql');
        $last_accessed_ip = SwpmUtils::get_user_ip_address();
        $wpdb->update($wpdb->prefix . 'swpm_members_tbl', array('last_accessed' => $last_accessed_date, 'last_accessed_from_ip' => $last_accessed_ip), array('member_id' => $this->userData->member_id), array('%s', '%s'), array('%d')
        );

        //Check the member's account status.
        $can_login = true;
        if ($this->userData->account_state == 'inactive' && empty($enable_expired_login)) {
            $this->lastStatusMsg = SwpmUtils::_('Account is inactive.');
            $can_login = false;
        } else if (($this->userData->account_state == 'expired') && empty($enable_expired_login)) {
            $this->lastStatusMsg = SwpmUtils::_('Account has expired.');
            $can_login = false;
        } else if ($this->userData->account_state == 'pending') {
            $this->lastStatusMsg = SwpmUtils::_('Account is pending.');
            $can_login = false;
        } else if ($this->userData->account_state == 'activation_required') {
            $resend_email_url = add_query_arg(array(
                'swpm_resend_activation_email' => '1',
                'swpm_member_id' => $this->userData->member_id,
                    ), get_home_url());
            $msg = sprintf(SwpmUtils::_('You need to activate your account. If you didn\'t receive an email then %s to resend the activation email.'), '<a href="' . $resend_email_url . '">' . SwpmUtils::_('click here') . '</a>');
            $this->lastStatusMsg = $msg;
            $can_login = false;
        }

        if (!$can_login) {
            $this->isLoggedIn = false;
            $this->userData = null;
            return false;
        }

        if (SwpmUtils::is_subscription_expired($this->userData)) {
            if ($this->userData->account_state == 'active') {
                $wpdb->update($wpdb->prefix . 'swpm_members_tbl', array('account_state' => 'expired'), array('member_id' => $this->userData->member_id), array('%s'), array('%d'));
            }
            if (empty($enable_expired_login)) {
                $this->lastStatusMsg = SwpmUtils::_('Account has expired.');
                $this->isLoggedIn = false;
                $this->userData = null;
                return false;
            }
        }

        $this->permitted = SwpmPermission::get_instance($this->userData->membership_level);
        $this->lastStatusMsg = SwpmUtils::_("You are logged in as:") . $this->userData->user_name;
        $this->isLoggedIn = true;
        return true;
    }

    private function check_password($plain_password, $hashed_pw) {
        global $wp_hasher;
        if (empty($plain_password)) {
            return false;
        }
        if (empty($wp_hasher)) {
            require_once( ABSPATH . 'wp-includes/class-phpass.php');
            $wp_hasher = new PasswordHash(8, TRUE);
        }
        return $wp_hasher->CheckPassword($plain_password, $hashed_pw);
    }

    public function match_password($password) {
        if (!$this->is_logged_in()) {
            return false;
        }
        return $this->check_password($password, $this->get('password'));
    }

    public function login_to_swpm_using_wp_user($user) {
        if ($this->isLoggedIn) {
            return false;
        }
        $email = $user->user_email;
        $member = SwpmMemberUtils::get_user_by_email($email);
        if (empty($member)) {
            //There is no swpm profile with this email.
            return false;
        }
        $this->userData = $member;
        $this->isLoggedIn = true;
        $this->set_cookie();
        SwpmLog::log_auth_debug('Member has been logged in using WP User object.', true);
        $this->check_constraints();
        return true;
    }

    public function login($user, $pass, $remember = '', $secure = '') {
        SwpmLog::log_auth_debug("SwpmAuth::login()", true);
        if ($this->isLoggedIn) {
            return;
        }
        if ($this->authenticate($user, $pass) && $this->validate()) {
            $this->set_cookie($remember, $secure);
        } else {
            $this->isLoggedIn = false;
            $this->userData = null;
        }
        return $this->lastStatusMsg;
    }

    public function logout() {
        if (!$this->isLoggedIn) {
            return;
        }
        setcookie(SIMPLE_WP_MEMBERSHIP_AUTH, ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN);
        setcookie(SIMPLE_WP_MEMBERSHIP_SEC_AUTH, ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN);
        $this->userData = null;
        $this->isLoggedIn = false;
        $this->lastStatusMsg = SwpmUtils::_("Logged Out Successfully.");
        do_action('swpm_logout');
    }

    private function set_cookie($remember = '', $secure = '') {
        if ($remember) {
            $expiration = time() + 1209600; //14 days
            $expire = $expiration + 43200; //12 hours grace period
        } else {
            $expiration = time() + 259200; //3 days.
            $expire = $expiration; //The minimum cookie expiration should be at least a few days.
        }

        $expire = apply_filters('swpm_auth_cookie_expiry_value', $expire);

        setcookie("swpm_in_use", "swpm_in_use", $expire, COOKIEPATH, COOKIE_DOMAIN);

        $expiration_timestamp = SwpmUtils::get_expiration_timestamp($this->userData);
        $enable_expired_login = SwpmSettings::get_instance()->get_value('enable-expired-account-login', '');
        // make sure cookie doesn't live beyond account expiration date.
        // but if expired account login is enabled then ignore if account is expired
        $expiration = empty($enable_expired_login) ? min($expiration, $expiration_timestamp) : $expiration;
        $pass_frag = substr($this->userData->password, 8, 4);
        $scheme = 'auth';
        if (!$secure) {
            $secure = is_ssl();
        }
        $key = SwpmAuth::b_hash($this->userData->user_name . $pass_frag . '|' . $expiration, $scheme);
        $hash = hash_hmac('md5', $this->userData->user_name . '|' . $expiration, $key);
        $auth_cookie = $this->userData->user_name . '|' . $expiration . '|' . $hash;
        $auth_cookie_name = $secure ? SIMPLE_WP_MEMBERSHIP_SEC_AUTH : SIMPLE_WP_MEMBERSHIP_AUTH;
        setcookie($auth_cookie_name, $auth_cookie, $expire, COOKIEPATH, COOKIE_DOMAIN, $secure, true);
    }

    private function validate() {
        $auth_cookie_name = is_ssl() ? SIMPLE_WP_MEMBERSHIP_SEC_AUTH : SIMPLE_WP_MEMBERSHIP_AUTH;
        if (!isset($_COOKIE[$auth_cookie_name]) || empty($_COOKIE[$auth_cookie_name])) {
            return false;
        }
        $cookie_elements = explode('|', $_COOKIE[$auth_cookie_name]);
        if (count($cookie_elements) != 3) {
            return false;
        }

        //SwpmLog::log_auth_debug("validate() - " . $_COOKIE[$auth_cookie_name], true);
        list($username, $expiration, $hmac) = $cookie_elements;
        $expired = $expiration;
        // Allow a grace period for POST and AJAX requests
        if (defined('DOING_AJAX') || 'POST' == $_SERVER['REQUEST_METHOD']) {
            $expired += HOUR_IN_SECONDS;
        }
        // Quick check to see if an honest cookie has expired
        if ($expired < time()) {
            $this->lastStatusMsg = SwpmUtils::_("Session Expired."); //do_action('auth_cookie_expired', $cookie_elements);
            SwpmLog::log_auth_debug("validate() - Session Expired", true);
            return false;
        }

        global $wpdb;
        $query = " SELECT * FROM " . $wpdb->prefix . "swpm_members_tbl WHERE user_name = %s";
        $user = $wpdb->get_row($wpdb->prepare($query, $username));
        if (empty($user)) {
            $this->lastStatusMsg = SwpmUtils::_("Invalid Username");
            return false;
        }

        $pass_frag = substr($user->password, 8, 4);
        $key = SwpmAuth::b_hash($username . $pass_frag . '|' . $expiration);
        $hash = hash_hmac('md5', $username . '|' . $expiration, $key);
        if ($hmac != $hash) {
            $this->lastStatusMsg = SwpmUtils::_("Please login again.");
            SwpmLog::log_auth_debug("validate() - Bad Hash", true);
            wp_logout(); //Force logout of WP user session to clear the bad hash.
            return false;
        }

        if ($expiration < time()) {
            $GLOBALS['login_grace_period'] = 1;
        }
        $this->userData = $user;
        return $this->check_constraints();
    }

    public static function b_hash($data, $scheme = 'auth') {
        $salt = wp_salt($scheme) . 'j4H!B3TA,J4nIn4.';
        return hash_hmac('md5', $data, $salt);
    }

    public function is_logged_in() {
        return $this->isLoggedIn;
    }

    public function get($key, $default = "") {
        if (isset($this->userData->$key)) {
            return $this->userData->$key;
        }
        if (isset($this->permitted->$key)) {
            return $this->permitted->$key;
        }
        if (!empty($this->permitted)) {
            return $this->permitted->get($key, $default);
        }
        return $default;
    }

    public function get_message() {
        return $this->lastStatusMsg;
    }

    public function get_expire_date() {
        if ($this->isLoggedIn) {
            return SwpmUtils::get_formatted_expiry_date($this->get('subscription_starts'), $this->get('subscription_period'), $this->get('subscription_duration_type'));
        }
        return "";
    }

    public function delete() {
        if (!$this->is_logged_in()) {
            return;
        }
        $user_name = $this->get('user_name');
        $user_id = $this->get('member_id');
        $subscr_id = $this->get('subscr_id');
        $email = $this->get('email');
        // let's check if Stripe subscription needs to be cancelled as well
        global $wpdb;
        $q = $wpdb->prepare('SELECT * 
        FROM  `' . $wpdb->prefix . 'swpm_payments_tbl` 
        WHERE email =  %s
        AND gateway =  "stripe"
        AND subscr_id = %s
        LIMIT 1', array($email, $subscr_id));

        $member = $wpdb->get_row($q, ARRAY_A);
        if (!is_null($member)) {
            //looks like we need to cancel Stripe subscription
            $pieces = explode('|', $subscr_id);
            if (!empty($pieces)) {
                $subscr_id = $pieces[0];
                $button_id = $pieces[1];
                SwpmLog::log_simple_debug("Attempting to cancel Stripe Subscription #" . $subscr_id, true);
                //check if button exists
                if (get_post($button_id)) {
                    $settings = SwpmSettings::get_instance();
                    $sandbox_enabled = $settings->get_value('enable-sandbox-testing');
                    if ($sandbox_enabled) {
                        SwpmLog::log_simple_debug("Sandbox payment mode is enabled. Using test API key details.", true);
                        $secret_key = get_post_meta($button_id, 'stripe_test_secret_key', true);
                        ; //Use sandbox API key
                    } else {
                        $secret_key = get_post_meta($button_id, 'stripe_live_secret_key', true);
                        ; //Use live API key
                    }
                    //Include the Stripe library.
                    SwpmMiscUtils::load_stripe_lib();
                    
                    \Stripe\Stripe::setApiKey($secret_key);
                    // Let's try to cancel subscription
                    try {
                        $sub = \Stripe\Subscription::retrieve($subscr_id);
                        $sub->cancel();
                    } catch (Exception $e) {
                        SwpmLog::log_simple_debug("Error occurred during Stripe Subscription cancellation. " . $e->getMessage(), false);
                        $body = $e->getJsonBody();
                        $error = $body['error'];
                        $error_string = print_r($error, true);
                        SwpmLog::log_simple_debug("Error details: " . $error_string, false);
                    }
                    if (!isset($error)) {
                        SwpmLog::log_simple_debug("Stripe Subscription has been cancelled.", true);
                    }
                }
            }
        }

        wp_clear_auth_cookie();
        $this->logout();
        SwpmMembers::delete_swpm_user_by_id($user_id);
        SwpmMembers::delete_wp_user($user_name);
    }

    public function reload_user_data() {
        if (!$this->is_logged_in()) {
            return;
        }
        global $wpdb;
        $query = "SELECT * FROM " . $wpdb->prefix . "swpm_members_tbl WHERE member_id = %d";
        $this->userData = $wpdb->get_row($wpdb->prepare($query, $this->userData->member_id));
    }

    public function is_expired_account() {
        if (!$this->is_logged_in()) {
            return null;
        }
        $account_status = $this->get('account_state');
        if ($account_status == 'expired' || $account_status == 'inactive') {
            //Expired or Inactive accounts are both considered to be expired.
            return true;
        }
        return false;
    }

}
