<?php

declare(strict_types=1);

namespace UFPE\GoogleOIDC;

use WP_Error;
use WP_User;

final class UserProvisioner
{
    private Config $config;

    public function __construct(Config $config)
    {
        $this->config = $config;
    }

    /**
     * @return WP_User|WP_Error
     */
    public function provision(array $claims)
    {
        $sub = sanitize_text_field((string) $claims['sub']);
        $email = sanitize_email((string) $claims['email']);
        $user = $this->findBySub($sub);
        $isNew = false;

        if (! $user) {
            if (! $this->config->autoProvision()) {
                return new WP_Error('ufpe_google_provisioning_disabled', 'Usuario institucional sem conta local provisionada.');
            }

            $user = $this->claimExistingUserByEmail($email, $sub);

            if (is_wp_error($user)) {
                return $user;
            }

            if (! $user) {
                $user = $this->createUser($claims);
                $isNew = true;
            }
        }

        if (get_user_meta($user->ID, Config::META_BLOCKED, true)) {
            return new WP_Error('ufpe_google_user_blocked', 'Usuario bloqueado localmente.');
        }

        $this->syncUser($user->ID, $claims, $isNew);

        return get_user_by('id', $user->ID);
    }

    private function findBySub(string $sub): ?WP_User
    {
        $users = get_users([
            'meta_key' => Config::META_SUB,
            'meta_value' => $sub,
            'number' => 1,
            'fields' => 'all',
        ]);

        return $users ? $users[0] : null;
    }

    /**
     * @return WP_User|WP_Error|null
     */
    private function claimExistingUserByEmail(string $email, string $sub)
    {
        if (! $this->config->linkExistingByEmail() || $email === '') {
            return null;
        }

        $existing = get_user_by('email', $email);

        if (! $existing) {
            return null;
        }

        $existingSub = (string) get_user_meta($existing->ID, Config::META_SUB, true);

        if ($existingSub !== '' && $existingSub !== $sub) {
            return new WP_Error('ufpe_google_email_link_conflict', 'Conta WordPress ja vinculada a outro Google sub.');
        }

        return $existing;
    }

    /**
     * @return WP_User|WP_Error
     */
    private function createUser(array $claims)
    {
        $sub = sanitize_text_field((string) $claims['sub']);
        $email = sanitize_email((string) $claims['email']);
        $name = sanitize_text_field((string) ($claims['name'] ?? $email));
        $login = 'ufpe_' . substr(hash('sha256', $sub), 0, 32);
        $role = $this->roleForClaims($claims);

        $userId = wp_insert_user([
            'user_login' => $login,
            'user_email' => $email,
            'display_name' => $name,
            'first_name' => sanitize_text_field((string) ($claims['given_name'] ?? '')),
            'last_name' => sanitize_text_field((string) ($claims['family_name'] ?? '')),
            'user_pass' => wp_generate_password(64, true, true),
            'role' => is_string($role) && wp_roles()->is_role($role) ? $role : 'subscriber',
        ]);

        if (is_wp_error($userId)) {
            return $userId;
        }

        return get_user_by('id', $userId);
    }

    private function syncUser(int $userId, array $claims, bool $isNew): void
    {
        $email = sanitize_email((string) $claims['email']);
        $currentByEmail = $email ? get_user_by('email', $email) : false;

        $update = [
            'ID' => $userId,
            'display_name' => sanitize_text_field((string) ($claims['name'] ?? $email)),
            'first_name' => sanitize_text_field((string) ($claims['given_name'] ?? '')),
            'last_name' => sanitize_text_field((string) ($claims['family_name'] ?? '')),
        ];

        if ($email && (! $currentByEmail || (int) $currentByEmail->ID === $userId)) {
            $update['user_email'] = $email;
        }

        wp_update_user($update);

        update_user_meta($userId, Config::META_SUB, sanitize_text_field((string) $claims['sub']));
        update_user_meta($userId, Config::META_EMAIL, $email);
        update_user_meta($userId, Config::META_HD, sanitize_text_field((string) $claims['hd']));
        update_user_meta($userId, Config::META_PICTURE, esc_url_raw((string) ($claims['picture'] ?? '')));
        update_user_meta($userId, Config::META_LAST_LOGIN, current_time('mysql', true));

        if ($this->config->isAdministratorEmail($email)) {
            $user = get_user_by('id', $userId);

            if ($user instanceof WP_User && ! user_can($user, 'manage_options')) {
                $user->set_role('administrator');
            }
        }

        if ($isNew) {
            update_user_meta($userId, Config::META_PROVISIONED, '1');
        }
    }

    private function roleForClaims(array $claims): string
    {
        $email = sanitize_email((string) ($claims['email'] ?? ''));

        if ($this->config->isAdministratorEmail($email)) {
            return 'administrator';
        }

        $role = apply_filters('ufpe_google_oidc_default_role', $this->config->defaultRole(), $claims);

        return is_string($role) && wp_roles()->is_role($role) ? $role : 'subscriber';
    }
}
