<?php

declare(strict_types=1);

namespace UFPE\GoogleOIDC;

use WP_Error;
use WP_User;

final class AuthController
{
    public const START_ACTION = 'ufpe_google_start';
    public const CALLBACK_ACTION = 'ufpe_google_callback';
    private const STATE_COOKIE = 'ufpe_google_oidc_state';
    private const STATE_TTL = 600;

    private Config $config;
    private GoogleClient $client;
    private UserProvisioner $provisioner;

    public function __construct(Config $config, GoogleClient $client, UserProvisioner $provisioner)
    {
        $this->config = $config;
        $this->client = $client;
        $this->provisioner = $provisioner;
    }

    public function register(): void
    {
        add_action('login_form_' . self::START_ACTION, [$this, 'start']);
        add_action('login_form_' . self::CALLBACK_ACTION, [$this, 'callback']);
        add_filter('authenticate', [$this, 'restrictPasswordLogin'], 99, 3);
    }

    public function start(): void
    {
        if ($this->config->clientId() === '' || $this->config->clientSecret() === '') {
            $this->fail('config', 'Google OAuth client_id/client_secret ausentes.');
        }

        $state = bin2hex(random_bytes(32));
        $nonce = bin2hex(random_bytes(32));
        $redirectTo = isset($_REQUEST['redirect_to'])
            ? wp_validate_redirect(esc_url_raw(wp_unslash((string) $_REQUEST['redirect_to'])), admin_url())
            : admin_url();

        set_transient($this->stateKey($state), [
            'nonce' => $nonce,
            'redirect_to' => $redirectTo,
            'created_at' => time(),
        ], self::STATE_TTL);

        $this->setStateCookie($state, time() + self::STATE_TTL);

        wp_redirect($this->client->authorizationUrl($state, $nonce));
        exit;
    }

    public function callback(): void
    {
        if (isset($_GET['error'])) {
            $this->fail('google_denied', sanitize_text_field(wp_unslash((string) $_GET['error'])));
        }

        $state = isset($_GET['state']) ? sanitize_text_field(wp_unslash((string) $_GET['state'])) : '';
        $code = isset($_GET['code']) ? sanitize_text_field(wp_unslash((string) $_GET['code'])) : '';
        $cookieState = isset($_COOKIE[self::STATE_COOKIE]) ? sanitize_text_field(wp_unslash((string) $_COOKIE[self::STATE_COOKIE])) : '';

        if ($state === '' || $code === '' || ! hash_equals($cookieState, $state)) {
            $this->fail('state', 'State OIDC ausente ou divergente.');
        }

        $stateData = get_transient($this->stateKey($state));
        delete_transient($this->stateKey($state));
        $this->setStateCookie('', time() - 3600);

        if (! is_array($stateData) || empty($stateData['nonce'])) {
            $this->fail('state_expired', 'State OIDC expirado ou ja utilizado.');
        }

        $tokens = $this->client->exchangeCode($code);

        if (is_wp_error($tokens)) {
            $this->fail('token', $tokens->get_error_message());
        }

        $claims = $this->client->validateIdToken((string) $tokens['id_token'], (string) $stateData['nonce']);

        if (is_wp_error($claims)) {
            $this->fail($claims->get_error_code(), $claims->get_error_message(), ['claims' => $claims->get_error_data()]);
        }

        $user = $this->provisioner->provision($claims);

        if (is_wp_error($user)) {
            $this->fail($user->get_error_code(), $user->get_error_message(), ['sub' => $claims['sub'] ?? '']);
        }

        wp_set_current_user($user->ID);
        wp_set_auth_cookie($user->ID, false, is_ssl());
        do_action('wp_login', $user->user_login, $user);
        $this->audit('login_success', ['user_id' => $user->ID, 'sub' => $claims['sub'] ?? '', 'hd' => $claims['hd'] ?? '']);

        wp_safe_redirect(wp_validate_redirect((string) ($stateData['redirect_to'] ?? ''), admin_url()));
        exit;
    }

    /**
     * @param null|WP_User|WP_Error $user
     * @return null|WP_User|WP_Error
     */
    public function restrictPasswordLogin($user, string $username, string $password)
    {
        if (! $this->config->restrictPasswordLogin() || ! $user instanceof WP_User) {
            return $user;
        }

        $allowed = user_can($user, 'manage_options')
            && get_user_meta($user->ID, Config::META_LOCAL_ADMIN, true) === '1';

        if ($allowed) {
            $this->audit('local_admin_login', ['user_id' => $user->ID]);
            return $user;
        }

        $this->audit('password_login_blocked', ['user_id' => $user->ID, 'login' => $username]);
        return new WP_Error(
            'ufpe_google_password_login_blocked',
            'Login por senha local bloqueado. Use Entrar com Google UFPE.'
        );
    }

    private function stateKey(string $state): string
    {
        return 'ufpe_google_oidc_state_' . hash('sha256', $state);
    }

    private function fail(string $code, string $message, array $context = []): void
    {
        $this->audit('login_failed_' . $code, ['message' => $message] + $context);

        wp_safe_redirect(add_query_arg(
            ['ufpe_google_error' => rawurlencode($code)],
            network_site_url('wp-login.php', 'login')
        ));
        exit;
    }

    private function audit(string $event, array $context = []): void
    {
        error_log('[ufpe-google-oidc] ' . $event . ' ' . wp_json_encode($context));
    }

    private function setStateCookie(string $value, int $expires): void
    {
        $options = [
            'expires' => $expires,
            'path' => COOKIEPATH ?: '/',
            'secure' => is_ssl(),
            'httponly' => true,
            'samesite' => 'Lax',
        ];

        if (defined('COOKIE_DOMAIN') && COOKIE_DOMAIN) {
            $options['domain'] = COOKIE_DOMAIN;
        }

        setcookie(self::STATE_COOKIE, $value, $options);
    }
}
