<?php

declare(strict_types=1);

namespace SHServ\Integrations\GAuth;

use SHServ\Integrations\GAuth\AuthService;
use SHServ\Integrations\GAuth\PermissionResolver;

trait AuthControllerTrait
{
    private ?string $authError = null;

    /**
     * Require authenticated user. Returns error response if not auth.
     */
    protected function require_auth(): ?string
    {
        if (!$this->resolve_user()) {
            $code = $this->authError ?? 'unauthenticated';
            logging()->warn('php:Auth', 'Unauthenticated access', [
                'error' => $code,
                'client_ip' => $this->getClientIp(),
            ]);
            return $this->utils()->response_error($code, [], [], 401);
        }
        return null;
    }

    /**
     * Require specific permission. Returns error response if denied.
     */
    protected function require_permission(string $permissionSlug): ?string
    {
        $authError = $this->require_auth();
        if ($authError !== null) {
            return $authError;
        }

        $user = $this->resolve_user();
        if (!$user) {
            return $this->utils()->response_error('unauthenticated', [], [], 401);
        }

        $resolver = new PermissionResolver();
        if (!$resolver->has($user['id'], $user['system_role'], $permissionSlug)) {
            logging()->warn('php:Auth', 'Permission denied', [
                'user_id' => $user['id'],
                'permission' => $permissionSlug,
                'client_ip' => $this->getClientIp(),
            ]);
            return $this->utils()->response_error('permission_denied', [$permissionSlug], [], 403);
        }

        return null;
    }

    /**
     * Resolve current user from session or Bearer token.
     */
    protected function resolve_user(): ?array
    {
        $this->authError = null;

        if (session_status() === PHP_SESSION_NONE) {
            @session_start();
        }

        // 1. Session-based auth
        $sessionUserId = $_SESSION['shserv_user_id'] ?? null;
        if ($sessionUserId) {
            $user = $this->load_user_by_id((int) $sessionUserId);
            if ($user) {
                logging()->debug('php:Auth', 'User resolved via session', ['user_id' => $user['id']]);
            }
            return $user;
        }

        // 2. Bearer token auth
        $bearer = $this->get_bearer_token();
        if ($bearer) {
            $user = $this->resolve_user_by_bearer($bearer);
            if ($user) {
                logging()->debug('php:Auth', 'User resolved via bearer token', ['user_id' => $user['id']]);
            }
            return $user;
        }

        return null;
    }

    /**
     * Extract Bearer token from Authorization header.
     */
    protected function get_bearer_token(): ?string
    {
        $header = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
        if (str_starts_with($header, 'Bearer ')) {
            return substr($header, 7);
        }
        return null;
    }

    /**
     * Resolve user by OAuth access token via local session table.
     * Checks token expiration and IP/UA binding before resolving.
     */
    protected function resolve_user_by_bearer(string $token): ?array
    {
        $tb = app()->thin_builder;
        $result = $tb->select(
            'shserv_sessions',
            ['user_id', 'expires_at', 'ip_address', 'user_agent'],
            [['access_token', '=', $token]]
        );
        if (!$result) {
            logging()->warn('php:Auth', 'Bearer token not found in DB', ['client_ip' => $this->getClientIp()]);
            return null;
        }

        $row = $result[0];
        if (!empty($row['expires_at'])) {
            $expiresAt = strtotime((string) $row['expires_at']);
            if ($expiresAt !== false && time() > $expiresAt) {
                logging()->warn('php:Auth', 'Bearer token expired', [
                    'user_id' => (int) $row['user_id'],
                    'client_ip' => $this->getClientIp(),
                ]);
                return null;
            }
        }

        // NOTE: IP / User-Agent binding disabled because reverse-proxy setups
        // (nginx → php-fpm via unix socket) report REMOTE_ADDR as 127.0.0.1
        // for all requests, making binding unreliable. The fields are still
        // stored in shserv_sessions for audit purposes.
        return $this->load_user_by_id((int) $row['user_id']);
    }

    /**
     * Load user row from shserv_users by local ID.
     * Returns null if user is inactive or banned.
     */
    protected function load_user_by_id(int $userId): ?array
    {
        $tb = app()->thin_builder;
        $result = $tb->select(
            'shserv_users',
            ['id', 'gauth_user_id', 'email', 'display_name', 'avatar_url', 'system_role', 'status', 'timezone'],
            [['id', '=', $userId]]
        );
        if (!$result) {
            return null;
        }

        $user = $result[0];
        if (($user['status'] ?? '') !== 'active') {
            return null;
        }

        return $user;
    }

    /**
     * Get current user data (session or Bearer).
     */
    protected function get_current_user(): ?array
    {
        return $this->resolve_user();
    }

    /**
     * Get effective permissions for current user.
     */
    protected function get_current_permissions(): array
    {
        $user = $this->resolve_user();
        if (!$user) {
            return [];
        }

        $resolver = new PermissionResolver();
        return $resolver->resolve((int) $user['id'], $user['system_role']);
    }

    private function getClientIp(): ?string
    {
        if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            $ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
            $ip = trim($ips[0]);
            if (filter_var($ip, FILTER_VALIDATE_IP)) {
                return $ip;
            }
        }
        return $_SERVER['REMOTE_ADDR'] ?? null;
    }
}
