<?php

/*
 * This file is part of Chevereto.
 *
 * (c) Rodolfo Berrios <rodolfo@chevereto.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use Chevereto\Config\Config;
use Chevereto\Legacy\Classes\Confirmation;
use Chevereto\Legacy\Classes\DB;
use Chevereto\Legacy\Classes\Login;
use Chevereto\Legacy\Classes\RequestLog;
use Chevereto\Legacy\Classes\Settings;
use Chevereto\Legacy\Classes\TwoFactor;
use Chevereto\Legacy\Classes\User;
use Chevereto\Legacy\G\Handler;
use function Chevereto\Legacy\captcha_check;
use function Chevereto\Legacy\check_hashed_token;
use function Chevereto\Legacy\decodeID;
use function Chevereto\Legacy\G\datetime;
use function Chevereto\Legacy\G\datetime_diff;
use function Chevereto\Legacy\G\datetimegmt;
use function Chevereto\Legacy\G\get_public_url;
use function Chevereto\Legacy\G\redirect;
use function Chevereto\Legacy\generate_hashed_token;
use function Chevereto\Legacy\get_email_body_str;
use function Chevereto\Legacy\getSetting;
use function Chevereto\Legacy\hashed_token_info;
use function Chevereto\Legacy\must_use_captcha;
use function Chevereto\Legacy\send_mail;
use function Chevereto\Vars\get;
use function Chevereto\Vars\post;
use function Chevereto\Vars\session;
use function Chevereto\Vars\sessionVar;

return function (Handler $handler) {
    $POST = post();
    $handler->setTemplate('404');
    $route = $handler->requestArray()[0];
    $doing = $handler->request()[0] ?? false;
    if (! $doing
        || ! in_array($doing, ['activate', 'password-reset', 'change-email-confirm', 'two-factor'], true)
        && $handler->isRequestLevel(3)
    ) {
        $handler->issueError(404);

        return;
    }
    if (! Settings::get('enable_signups')
        && in_array($doing, ['awaiting-confirmation', 'activate'], true)
    ) {
        $handler->issueError(403);

        return;
    }
    $logged_user = Login::getUser();
    $loggedStatus = $logged_user['status'] ?? '';
    if (Login::isLoggedUser() && $doing !== 'email-needed' && $loggedStatus === 'awaiting-email') {
        redirect('account/email-needed', 302);
    }

    switch ($doing) {
        case 'email-needed':
            if (Login::isLoggedUser()) {
                if ($loggedStatus !== 'awaiting-email') {
                    redirect($logged_user['url'], 302);
                }
            } else {
                redirect('login', 302);
            }

            break;
        case 'resend-activation':
        case 'activate':
            if (Login::isLoggedUser() && $loggedStatus !== 'awaiting-confirmation') {
                redirect($logged_user['url'] ?? '', 302);
            }

            break;
        case 'two-factor':
            if (! Login::isLoggedUser()) {
                redirect('login', 302);
            }
            if (! TwoFactor::hasFor($logged_user['id'])) {
                redirect('settings/security', 302);
            }
            if (! sessionVar()->has('challenge_two_factor')) {
                redirect($logged_user['url'] ?? '', 302);
            }

            break;
    }
    $captcha_needed = false;
    $request_to_db = [
        'activate' => 'account-activate',
        'change-email-confirm' => 'account-change-email',
        'email-needed' => 'account-email-needed',
        'password-forgot' => 'account-password-forgot',
        'password-reset' => 'account-password-forgot',
        'resend-activation' => 'account-activate',
        'two-factor' => 'account-two-factor',
    ];
    $request_db_field = $request_to_db[$doing] ?? '';
    $pre_doctitles = [
        'awaiting-confirmation' => _s('Awaiting confirmation'),
        'email-changed' => _s('Email changed'),
        'email-needed' => _s('Add your email address'),
        'password-forgot' => _s('Forgot password?'),
        'password-reset' => _s('Reset password'),
        'resend-activation' => _s('Resend account activation'),
        'two-factor' => _s('Two-factor authentication'),
    ];
    $keysToCheck = $request_to_db;
    unset($keysToCheck['change-email-confirm']);
    $keysToCheck = array_keys($keysToCheck);
    if (in_array($doing, $keysToCheck, true)) {
        $request_log = RequestLog::getCounts($request_db_field, 'fail');
        $captcha_needed = (getSetting('captcha') ?? false)
            ? must_use_captcha($request_log['day'])
            : false;
    }
    $is_process_done = false;
    $is_error = false;
    $error_message = null;
    $input_errors = [];
    if ($captcha_needed && ! empty($POST)) {
        $captcha = captcha_check();
        if (! $captcha->is_valid) {
            $is_error = true;
            $error_message = _s('%s says you are a robot', 'CAPTCHA');
        }
    }
    $handler->setTemplate($route . '/' . $doing);
    switch ($doing) {
        case 'password-forgot':
        case 'resend-activation':
            if ($doing === 'password-forgot' && $loggedStatus === 'valid' || $doing === 'resend-activation' && $loggedStatus === 'awaiting-confirmation') {
                $POST['user-subject'] = $logged_user['username'];
                $is_error = false;
            }
            if ($POST !== [] && ! $is_error) {
                $subject_type = filter_var($POST['user-subject'] ?? '', FILTER_VALIDATE_EMAIL)
                    ? 'email'
                    : 'username';
                if (trim($POST['user-subject']) === '') {
                    $is_error = true;
                    $input_errors['user-subject'] = _s('Invalid Username/Email');
                }
                if (! $is_error) {
                    $user = User::getSingle($POST['user-subject'], $subject_type);
                    if ($user !== []) {
                        if (! filter_var($user['email'], FILTER_VALIDATE_EMAIL)) {
                            $error_message = _s("User doesn't have an email.");
                            $is_error = true;
                        }
                        if ($doing === 'password-forgot') {
                            switch ($user['status']) {
                                case 'banned':
                                    $handler->issueError(403);

                                    return;

                                case 'awaiting-email':
                                case 'awaiting-confirmation':
                                    $is_error = true;
                                    $error_message = _s('Account needs to be activated to use this feature');

                                    break;
                            }
                        } else { //'resend-activation'
                            switch ($user['status']) {
                                case 'valid':
                                    $is_error = true;
                                    $error_message = _s('Account already activated');

                                    break;
                                case 'banned':
                                    $is_error = true;
                                    $error_message = _s('Account banned');

                                    break;
                            }
                        }
                        if ($handler->template() === '403') {
                            RequestLog::insert([
                                'type' => $request_db_field,
                                'result' => 'fail',
                                'user_id' => $user['id'],
                            ]);

                            return;
                        }
                    } else {
                        $is_error = true;
                        $input_errors['user-subject'] = _s('Invalid Username/Email');
                    }
                    if (! $is_error) {
                        $confirmation_db = Confirmation::get([
                            'user_id' => $user['id'],
                            'type' => $request_db_field,
                            'status' => 'active',
                        ], [
                            'field' => 'date',
                            'order' => 'desc',
                        ], 1);
                        if ($confirmation_db !== false) {
                            $minute_diff = $confirmation_db['confirmation_date_gmt']
                                ? datetime_diff($confirmation_db['confirmation_date_gmt'], null, 'm')
                                : 15 + 1;
                            if ($minute_diff < 15) { // Mimic for the already submitted
                                $is_error = true;
                                $is_process_done = false;
                                $activation_email = $user['email'];
                                if ($subject_type === 'username') { // We won't disclose this email address
                                    $activation_email = preg_replace('/(?<=.).(?=.*@)/u', '*', $activation_email);
                                    $explode = explode('@', $activation_email);
                                    while (strlen($explode[0]) < 4) {
                                        $explode[0] .= '*';
                                    }
                                    $activation_email = implode('@', $explode);
                                }
                                $handler::setVar('resend_activation_email', $activation_email);
                                $error_message = _s('Allow up to 15 minutes for the email. You can try again later.');
                            } else {
                                Confirmation::delete([
                                    'user_id' => $user['id'],
                                    'type' => $request_db_field,
                                ]);
                            }
                        }
                    }
                    if (! $is_error) {
                        $hashed_token = generate_hashed_token((int) $user['id']);
                        $array_values = [
                            'type' => $request_db_field,
                            'date' => datetime(),
                            'date_gmt' => datetimegmt(),
                            'token_hash' => $hashed_token['hash'],
                        ];
                        if (! isset($user['confirmation_id'])) {
                            $array_values['user_id'] = $user['id'];
                            $confirmation_db_query = Confirmation::insert($array_values);
                        } else {
                            $confirmation_db_query = Confirmation::update($user['confirmation_id'], $array_values);
                        }
                        if ($confirmation_db_query) {
                            $recovery_link = get_public_url(
                                'account/'
                                . ($doing === 'password-forgot' ? 'password-reset' : 'activate')
                                . '/?token='
                                . base64_encode($hashed_token['public_token_format'])
                            );
                            global $theme_mail;
                            $theme_mail = [
                                'user' => $user,
                                'link' => $recovery_link,
                            ];
                            if ($doing === 'password-forgot') {
                                $mail['subject'] = _s(
                                    'Reset your password at %s',
                                    getSetting('website_name')
                                );
                            } else {
                                $mail['subject'] = _s(
                                    'Confirmation required at %s',
                                    getSetting('website_name')
                                );
                            }
                            $mail['message'] = get_email_body_str('mails/account-' . ($doing === 'password-forgot' ? 'password-reset' : 'confirm'));
                            if (send_mail($user['email'], $mail['subject'], $mail['message'])) {
                                $is_process_done = true;
                            }
                            if ($doing === 'resend-activation') {
                                Login::setSignup([
                                    'status' => 'awaiting-confirmation',
                                    'email' => $user['email'],
                                ]);
                                redirect('account/awaiting-confirmation', 302);
                            }
                            $handler::setVar('password_forgot_email', $user['email']);
                        } else {
                            throw new Exception("Can't insert confirmation in DB", 400);
                        }
                    }
                }
                if ($is_error) {
                    RequestLog::insert([
                        'result' => 'fail',
                        'type' => $request_db_field,
                        'user_id' => $user['id'] ?? null,
                    ]);
                    if ((getSetting('captcha') ?? false)
                        && isset($request_log)
                        && must_use_captcha($request_log['day'] + 1)) {
                        $captcha_needed = true;
                    }
                    if (! $error_message) {
                        $error_message = _s('Invalid Username/Email');
                    }
                }
            }

            break;
        case 'awaiting-confirmation':
            if (! Login::hasSignup()) {
                $handler->issueError(403);

                return;
            }
            if (Login::getSignup()['status'] !== 'awaiting-confirmation') {
                $handler->issueError(403);

                return;
            }
            $signup_email = Login::isLoggedUser()
                ? $logged_user['email']
                : Login::getSignup()['email'];
            $handler::setVar('signup_email', $signup_email);

            break;
        case 'password-reset':
        case 'activate':
        case 'change-email-confirm':
            $token = get()['token'] ?? '';
            $token = base64_decode($token, true) ?: '';
            $get_token_array = $token !== ''
                ? explode(':', $token)
                : [];
            if (isset($request_log)
                && $request_log['day'] > Config::limit()->invalidRequestsPerDay()) {
                $get_token_array = [];
            }

            if ($get_token_array === [] || count($get_token_array) !== 4) {
                RequestLog::insert([
                    'type' => $request_db_field,
                    'result' => 'fail',
                    'user_id' => null,
                ]);
                $handler->issueError(403);

                return;
            }
            $user_id = decodeID($get_token_array[0]);
            $token_info = hashed_token_info($token);
            $confirmation_db = Confirmation::get([
                'user_id' => $token_info['id'],
                'type' => $request_db_field,
            ]);
            if ($confirmation_db === false) {
                $handler->issueError(403);

                return;
            }
            $hash_match = check_hashed_token($confirmation_db['confirmation_token_hash'], $token);
            if (datetime_diff($confirmation_db['confirmation_date_gmt'], null, 'h') > 48) {
                Confirmation::delete([
                    'id' => $confirmation_db['confirmation_id'],
                ]);
                $confirmation_db = false;
            }
            if (! $hash_match || ! $confirmation_db) {
                RequestLog::insert([
                    'type' => $request_db_field,
                    'result' => 'fail',
                    'user_id' => $user_id,
                ]);
                $handler->issueError(403);

                return;
            }
            switch ($doing) {
                case 'activate':
                    User::update($confirmation_db['confirmation_user_id'], [
                        'status' => 'valid',
                    ]);
                    Confirmation::delete([
                        'id' => $confirmation_db['confirmation_id'],
                    ]);
                    $logged_user = Login::login($confirmation_db['confirmation_user_id']);
                    Login::insertCookie('cookie', $logged_user['id']);
                    global $theme_mail;
                    $theme_mail = [
                        'user' => $logged_user,
                    ];
                    $mail['subject'] = _s('Welcome to %s', getSetting('website_name'));
                    $mail['message'] = get_email_body_str('mails/account-welcome');
                    if (send_mail($logged_user['email'], $mail['subject'], $mail['message'])) {
                        $is_process_done = true;
                    }
                    Login::unsetSignup();
                    $redirectTo = $logged_user !== []
                        ? User::getUrl($logged_user)
                        : '';
                    redirect($redirectTo, 302);

                    break;
                case 'password-reset':
                    if ($POST !== []) {
                        if (! preg_match('/' . Settings::USER_PASSWORD_PATTERN . '/', $POST['new-password'] ?? '')) {
                            $input_errors['new-password'] = _s('Invalid password');
                        }
                        if ($POST['new-password'] !== $POST['new-password-confirm']) {
                            $input_errors['new-password-confirm'] = _s("Passwords don't match");
                        }
                        if (count($input_errors) === 0) {
                            if (Login::hasPassword($user_id)) {
                                $is_process_done = Login::changePassword($user_id, $POST['new-password']);
                            } else {
                                $is_process_done = Login::addPassword($user_id, $POST['new-password']);
                            }
                            if ($is_process_done) {
                                Confirmation::delete([
                                    'type' => $request_db_field,
                                    'user_id' => $user_id,
                                ]);
                            } else {
                                throw new Exception('Unexpected error', 400);
                            }
                        }
                    } else {
                        $is_process_done = false;
                    }

                    break;
                case 'change-email-confirm':
                    $email_candidate = $confirmation_db['confirmation_extra'];
                    /** @var array|false $email_db */
                    $email_db = DB::get(
                        table: 'users',
                        where: [
                            'email' => $email_candidate,
                        ],
                        limit: 1
                    );
                    if (is_array($email_db) && $email_db !== []) {
                        if ($email_db['user_status'] === 'valid') {
                            Confirmation::delete([
                                'id' => $confirmation_db['confirmation_id'],
                            ]);
                            RequestLog::insert([
                                'type' => $request_db_field,
                                'result' => 'fail',
                                'user_id' => $user_id,
                            ]);
                            $handler->issueError(403);

                            return;
                        }
                        DB::delete('users', [
                            'id' => $email_db['user_id'],
                        ]);
                        Confirmation::delete([
                            'type' => 'account-change-email',
                            'user_id' => $email_db['user_id'],
                        ]);
                    }
                    Confirmation::delete([
                        'type' => 'account-change-email',
                        'user_id' => $user_id,
                    ]);
                    sessionVar()->put('change-email-confirm', true);
                    User::update($user_id, [
                        'email' => $email_candidate,
                    ]);
                    Login::login($user_id);
                    redirect('account/email-changed', 302);

                    break;
            }

            break;
        case 'email-needed':
            if ($POST !== [] && ! $is_error) {
                if (! filter_var($POST['email'], FILTER_VALIDATE_EMAIL)) {
                    $is_error = true;
                    $input_errors['email'] = _s('Invalid email');
                }
                if (! $is_error) {
                    $user = User::getSingle($POST['email'], 'email');
                    if ($user !== []) {
                        $is_error = true;
                        $input_errors['email'] = _s('Email already being used');
                    }
                }
                if (! $is_error) {
                    User::update($logged_user['id'], [
                        'status' => getSetting('require_user_email_confirmation') ? 'awaiting-confirmation' : 'valid',
                        'email' => trim($POST['email']),
                    ]);
                    if (! getSetting('require_user_email_confirmation')) {
                        redirect($logged_user['url'], 302);
                    }
                    $hashed_token = generate_hashed_token((int) $logged_user['id']);
                    $array_values = [
                        'type' => 'account-activate',
                        'date' => datetime(),
                        'date_gmt' => datetimegmt(),
                        'token_hash' => $hashed_token['hash'],
                    ];
                    $array_values['user_id'] = $logged_user['id'];
                    $confirmation_db_query = Confirmation::insert($array_values);
                    $activation_link = get_public_url(
                        'account/activate/?token='
                        . base64_encode($hashed_token['public_token_format'])
                    );
                    global $theme_mail;
                    $theme_mail = [
                        'user' => $logged_user,
                        'link' => $activation_link,
                    ];
                    $mail['subject'] = _s('Confirmation required at %s', getSetting('website_name'));
                    $mail['message'] = get_email_body_str('mails/account-confirm');
                    if (send_mail($POST['email'], $mail['subject'], $mail['message'])) {
                        $is_process_done = true;
                    }
                    Login::setSignup([
                        'status' => 'awaiting-confirmation',
                        'email' => $POST['email'],
                    ]);
                    redirect('account/awaiting-confirmation', 302);
                } else {
                    RequestLog::insert(
                        [
                            'result' => 'fail',
                            'type' => $request_db_field,
                            'user_id' => $user['id'] ?? null,
                        ]
                    );
                    if ((getSetting('captcha') ?? false)
                        && isset($request_log)
                        && must_use_captcha($request_log['day'] + 1)) {
                        $captcha_needed = true;
                    }
                }
                if ($is_error) {
                    $error_message = $input_errors['email'];
                }
            }

            break;
        case 'email-changed':
            if (! isset(session()['change-email-confirm'])) {
                $handler->issueError(404);

                return;
            }
            $handler->setTemplate($route . '/' . 'email-changed');

            break;
        case 'two-factor':
            $handler->setTemplate($route . '/' . 'two-factor');
            if (null !== ($POST['user-two-factor'] ?? null) && ! $is_error) {
                $twoFactor = (new TwoFactor())->withSecret(
                    TwoFactor::getSecretFor(intval($logged_user['id']))
                );
                if ($twoFactor->verify($POST['user-two-factor'])) {
                    sessionVar()->remove('challenge_two_factor');
                    redirect($logged_user['url'], 302);
                } else {
                    $is_error = true;
                    $input_errors['user-two-factor'] = _s('Invalid code');
                    RequestLog::insert([
                        'type' => $request_db_field,
                        'result' => 'fail',
                        'user_id' => $logged_user['id'],
                    ]);
                    if ((getSetting('captcha') ?? false)
                        && isset($request_log)
                        && must_use_captcha($request_log['day'] + 1)) {
                        $captcha_needed = true;
                    }
                }
            }

            break;
        default:
            $handler->issueError(404);

            return;
    }
    $handler::setVar('pre_doctitle', $pre_doctitles[$doing]);
    $handler::setCond('error', $is_error);
    $handler::setCond('process_done', $is_process_done);
    $handler::setVar('input_errors', $input_errors);
    $handler::setVar('error', $error_message ?? _s('Check the errors in the form to continue.'));
    $handler::setCond('captcha_needed', $captcha_needed);
};
