<?php

namespace MintyPHP\Service\Auth;

use MintyPHP\Auth;
use MintyPHP\Http\CookieStoreInterface;
use MintyPHP\Http\RequestRuntimeInterface;
use MintyPHP\Http\SessionStoreInterface;
use MintyPHP\I18n;
use MintyPHP\Repository\Auth\RememberTokenRepositoryInterface;
use MintyPHP\Repository\User\UserReadRepositoryInterface;
use MintyPHP\Repository\User\UserWriteRepositoryInterface;
use MintyPHP\Service\Access\PermissionService;
use MintyPHP\Session;

class RememberMeService
{
    private const COOKIE_NAME = 'remember';
    private const LIFETIME_DAYS = 30;

    public function __construct(
        private readonly UserReadRepositoryInterface $userReadRepository,
        private readonly UserWriteRepositoryInterface $userWriteRepository,
        private readonly RememberTokenRepositoryInterface $rememberTokenRepository,
        private readonly PermissionService $permissionService,
        private readonly AuthSessionTenantContextService $authSessionTenantContextService,
        private readonly SessionStoreInterface $sessionStore,
        private readonly CookieStoreInterface $cookieStore,
        private readonly RequestRuntimeInterface $requestRuntime,
        private readonly ?AuthSettingsGateway $authSettingsGateway = null,
        private readonly ?RememberMeRateLimiter $rateLimiter = null
    ) {
    }

    public function rememberUser(int $userId): void
    {
        $selector = bin2hex(random_bytes(12));
        $token = bin2hex(random_bytes(32));
        $tokenHash = hash('sha256', $token);
        $expiresAt = gmdate('Y-m-d H:i:s', time() + $this->lifetimeSeconds());
        $this->rememberTokenRepository->create($userId, $selector, $tokenHash, $expiresAt);
        $this->setCookie($selector, $token);
    }

    public function autoLoginFromCookie(): bool
    {
        $sessionUser = $this->sessionStore->get('user', []);
        if (is_array($sessionUser) && !empty($sessionUser['id'])) {
            return false;
        }
        $value = $this->cookieStore->get($this->cookieName());
        if ($value === '' || strpos($value, ':') === false) {
            return false;
        }

        $ip = $this->requestRuntime->ip();

        if ($this->rateLimiter !== null && $this->rateLimiter->isBlocked($ip)) {
            $this->clearCookie();
            return false;
        }

        [$selector, $token] = explode(':', $value, 2);
        $selector = trim($selector);
        $token = trim($token);
        if ($selector === '' || $token === '') {
            $this->clearCookie();
            return false;
        }

        $record = $this->rememberTokenRepository->findBySelector($selector);
        if (!$record) {
            $this->rateLimiter?->recordFailure($ip);
            $this->clearCookie();
            return false;
        }

        $expiresAt = (string) ($record['expires_at'] ?? '');
        if ($expiresAt !== '' && strtotime($expiresAt . ' UTC') <= time()) {
            $this->rememberTokenRepository->deleteById((int) $record['id']);
            $this->clearCookie();
            return false;
        }

        $hash = (string) ($record['token_hash'] ?? '');
        if ($hash === '' || !hash_equals($hash, hash('sha256', $token))) {
            $this->rateLimiter?->recordFailure($ip);
            $this->rememberTokenRepository->deleteById((int) $record['id']);
            $this->clearCookie();
            return false;
        }

        $userId = (int) ($record['user_id'] ?? 0);
        if ($userId <= 0) {
            $this->clearCookie();
            return false;
        }

        $user = $this->userReadRepository->find($userId);
        if (!$user || empty($user['id']) || !($user['active'] ?? 1)) {
            $this->rememberTokenRepository->deleteById((int) $record['id']);
            $this->clearCookie();
            return false;
        }

        Session::regenerate();
        $this->sessionStore->set('user', $user);
        $now = time();
        $this->sessionStore->set('session_started_at', $now);
        $this->sessionStore->set('session_last_activity', $now);
        if (!empty($user['locale'])) {
            I18n::$locale = (string) $user['locale'];
        }
        $userId = (int) $user['id'];
        if ($userId > 0) {
            $this->permissionService->getUserPermissions($userId, true);
            $this->authSessionTenantContextService->hydrateForUser($userId);
            if ((bool) $this->sessionStore->get('no_active_tenant', false)) {
                // Enforce the same tenant policy as interactive login.
                $this->forgetCurrentUser();
                $this->authSessionTenantContextService->clearModuleSessionData();
                Auth::logout();
                return false;
            }
            $this->userWriteRepository->updateLastLogin($userId, 'local');
        }

        // Rotate token on each auto-login — limits the window if a cookie is stolen.
        $newToken = bin2hex(random_bytes(32));
        $newHash = hash('sha256', $newToken);
        $newExpires = gmdate('Y-m-d H:i:s', time() + $this->lifetimeSeconds());
        $this->rememberTokenRepository->updateToken((int) $record['id'], $newHash, $newExpires);
        $this->setCookie($selector, $newToken);

        $this->rateLimiter?->reset($ip);

        return true;
    }

    public function forgetCurrentUser(): void
    {
        $value = $this->cookieStore->get($this->cookieName());
        if ($value !== '' && strpos($value, ':') !== false) {
            [$selector] = explode(':', $value, 2);
            $selector = trim($selector);
            if ($selector !== '') {
                $record = $this->rememberTokenRepository->findBySelector($selector);
                if ($record && isset($record['id'])) {
                    $this->rememberTokenRepository->deleteById((int) $record['id']);
                }
            }
        }
        $this->clearCookie();
    }

    public function forgetAllForUser(int $userId): void
    {
        $this->rememberTokenRepository->deleteByUserId($userId);
    }

    public function expireAllTokensByAdmin(): int
    {
        return $this->rememberTokenRepository->expireAllByAdmin();
    }

    private function setCookie(string $selector, string $token): void
    {
        $value = $selector . ':' . $token;
        $expires = time() + $this->lifetimeSeconds();
        $secure = $this->requestRuntime->isSecure();

        $this->cookieStore->set($this->cookieName(), $value, [
            'expires' => $expires,
            'path' => '/',
            'secure' => $secure,
            'httponly' => true,
            'samesite' => 'Lax',
        ]);
    }

    private function clearCookie(): void
    {
        $secure = $this->requestRuntime->isSecure();
        $this->cookieStore->remove($this->cookieName(), [
            'path' => '/',
            'secure' => $secure,
            'httponly' => true,
            'samesite' => 'Lax',
        ]);
    }

    private function lifetimeSeconds(): int
    {
        if ($this->authSettingsGateway !== null) {
            return $this->authSettingsGateway->getRememberTokenLifetimeDays() * 86400;
        }

        return self::LIFETIME_DAYS * 86400;
    }

    private function cookieName(): string
    {
        $name = getenv('REMEMBER_COOKIE_NAME') ?: self::COOKIE_NAME;
        return trim($name) !== '' ? $name : self::COOKIE_NAME;
    }

}
