<?php

namespace Modules\Auth\Services;

use App\Models\ActivityLogModel;
use App\Models\RoleModel;
use App\Models\RolePermissionModel;
use Config\Auth as AuthConfig;
use Modules\Auth\Entities\UserEntity;
use Modules\Auth\Models\LoginAttemptModel;
use Modules\Auth\Models\UserModel;
use Modules\Auth\Models\UserSessionModel;

class AuthService
{
    protected AuthConfig $config;
    protected UserModel $users;
    protected LoginAttemptModel $attempts;
    protected UserSessionModel $sessions;
    protected ActivityLogModel $activity;

    public function __construct()
    {
        $this->config   = config('Auth');
        $this->users    = new UserModel();
        $this->attempts = new LoginAttemptModel();
        $this->sessions = new UserSessionModel();
        $this->activity = new ActivityLogModel();
    }

    /**
     * Attempt to authenticate a user.
     *
     * @return array{success: bool, user?: UserEntity, error?: string}
     */
    public function attemptLogin(string $identifier, string $password, string $ip, ?string $userAgent): array
    {
        // Rate limiting: check the rolling window before doing anything else.
        if ($this->isRateLimited($identifier, $ip)) {
            $this->attempts->record([
                'identifier'     => $identifier,
                'ip_address'     => $ip,
                'user_agent'     => $userAgent,
                'was_successful' => 0,
                'failure_reason' => 'rate_limited',
            ]);

            return ['success' => false, 'error' => 'Too many failed attempts. Please try again later.'];
        }

        $user = $this->users->findByLogin($identifier);

        if (! $user) {
            $this->attempts->record([
                'identifier'     => $identifier,
                'ip_address'     => $ip,
                'user_agent'     => $userAgent,
                'was_successful' => 0,
                'failure_reason' => 'no_such_user',
            ]);

            return ['success' => false, 'error' => 'Invalid credentials.'];
        }

        if ($user->isLocked()) {
            $this->attempts->record([
                'user_id'        => $user->id,
                'identifier'     => $identifier,
                'ip_address'     => $ip,
                'user_agent'     => $userAgent,
                'was_successful' => 0,
                'failure_reason' => 'account_locked',
            ]);

            return ['success' => false, 'error' => 'This account is temporarily locked. Please try again later.'];
        }

        if (! $user->isActive()) {
            $this->attempts->record([
                'user_id'        => $user->id,
                'identifier'     => $identifier,
                'ip_address'     => $ip,
                'user_agent'     => $userAgent,
                'was_successful' => 0,
                'failure_reason' => 'inactive_account',
            ]);

            return ['success' => false, 'error' => 'This account is disabled. Contact your administrator.'];
        }

        if (! password_verify($password, $user->password_hash)) {
            $count = $this->users->incrementFailedLogins($user->id);

            $this->attempts->record([
                'user_id'        => $user->id,
                'identifier'     => $identifier,
                'ip_address'     => $ip,
                'user_agent'     => $userAgent,
                'was_successful' => 0,
                'failure_reason' => 'bad_password',
            ]);

            if ($count >= $this->config->maxLoginAttempts) {
                $lockUntil = date('Y-m-d H:i:s', time() + ($this->config->lockoutMinutes * 60));
                $this->users->lockUntil($user->id, $lockUntil);
                $this->activity->log('auth', 'account_locked', $user->id, "Locked after {$count} failed attempts");

                return ['success' => false, 'error' => 'Too many failed attempts. This account is now locked.'];
            }

            return ['success' => false, 'error' => 'Invalid credentials.'];
        }

        // Success.
        $this->users->resetFailedLogins($user->id);
        $this->users->touchLastLogin($user->id, $ip);

        $this->attempts->record([
            'user_id'        => $user->id,
            'identifier'     => $identifier,
            'ip_address'     => $ip,
            'user_agent'     => $userAgent,
            'was_successful' => 1,
        ]);

        $this->activity->log('auth', 'login', $user->id, 'Successful login');

        return ['success' => true, 'user' => $this->users->find($user->id)];
    }

    protected function isRateLimited(string $identifier, string $ip): bool
    {
        if ($this->config->lockoutStrategy === 'account') {
            return false; // account-level lock is handled via users.locked_until instead
        }

        $count = $this->attempts->recentFailedCount($identifier, $ip, $this->config->attemptWindowMinutes);

        return $count >= $this->config->maxLoginAttempts;
    }

    /**
     * Establish the CI session + a tracked "user_sessions" row, and
     * optionally set a remember-me cookie.
     */
    public function establishSession(UserEntity $user, bool $rememberMe, string $ip, ?string $userAgent): void
    {
        $session = session();
        $session->regenerate(true); // prevent session fixation

        $roleSlug = null;
        if ($user->role_id) {
            $role = (new RoleModel())->find($user->role_id);
            $roleSlug = $role['slug'] ?? null;
        }

        $session->set([
            'user_id'       => $user->id,
            'username'      => $user->username,
            'full_name'     => $user->getFullName(),
            'role_id'       => $user->role_id,
            'role_slug'     => $roleSlug,
            'isLoggedIn'    => true,
            'last_activity' => time(),
            'permission_slugs' => $roleSlug && $user->role_id
                ? (new RolePermissionModel())->slugsForRole((int) $user->role_id)
                : [],
        ]);

        $token = bin2hex(random_bytes(32));
        $session->set('session_token', $token);
        $this->sessions->startSession($user->id, $token, $ip, $userAgent);

        if ($rememberMe) {
            $this->setRememberMeCookie($user);
        }
    }

    protected function setRememberMeCookie(UserEntity $user): void
    {
        $selector   = bin2hex(random_bytes(9));
        $validator  = bin2hex(random_bytes(33));
        $tokenValue = $selector . ':' . $validator;

        $this->users->update($user->id, [
            'remember_token' => hash('sha256', $validator) . ':' . $selector,
        ]);

        $response = service('response');
        $response->setCookie([
            'name'     => 'remember_me',
            'value'    => $tokenValue,
            'expire'   => $this->config->rememberMeDurationMinutes * 60,
            'httponly' => true,
            'secure'   => true,
            'samesite' => 'Lax',
        ]);
    }

    public function logout(): void
    {
        $session = session();
        $token   = $session->get('session_token');

        if ($token) {
            $this->sessions->revoke($token);
        }

        $userId = $session->get('user_id');
        if ($userId) {
            $this->activity->log('auth', 'logout', $userId, 'User logged out');
        }

        $session->destroy();

        $response = service('response');
        $response->deleteCookie('remember_me');
    }

    public function forceLogoutOtherDevices(int $userId, string $currentToken): void
    {
        $this->sessions->revokeAllExcept($userId, $currentToken);
        $this->activity->log('auth', 'force_logout_devices', $userId, 'Revoked all other active sessions');
    }

    /**
     * Safe post-login landing page for a role. Users without dashboard
     * access are sent to profile instead of looping on /dashboard.
     */
    public function landingUrlForRole(?int $roleId): string
    {
        if ($roleId && (new RolePermissionModel())->roleHasPermission($roleId, 'dashboard.view')) {
            return $this->config->defaultRedirectUrl;
        }

        return $this->config->accessDeniedRedirectUrl;
    }

    /**
     * Where to send a logged-in user who was denied a permission.
     * Never returns the URI that just failed.
     */
    public function permissionDeniedRedirectUrl(?string $deniedSlug, ?int $roleId, string $currentPath): string
    {
        $path = trim($currentPath, '/');
        if (str_ends_with($path, 'dashboard') || $deniedSlug === 'dashboard.view') {
            return $this->config->accessDeniedRedirectUrl;
        }

        return $this->landingUrlForRole($roleId);
    }
}
