<?php

namespace App\Services;

use App\Models\UserLoginTokenModel;
use App\Models\UserModel;
use CodeIgniter\Cookie\Cookie;
use Config\Encryption;
use Config\Tieasy;
use DateTimeImmutable;

class PersistentLoginService
{
    public function __construct(
        private readonly UserLoginTokenModel $tokens = new UserLoginTokenModel(),
        private readonly UserModel $users = new UserModel(),
        private readonly Tieasy $config = new Tieasy(),
    ) {
    }

    public function issueToken(int $userId): void
    {
        $selector  = bin2hex(random_bytes(12));
        $validator = bin2hex(random_bytes(24));

        $this->tokens->insert([
            'user_id'      => $userId,
            'selector'     => $selector,
            'token_hash'   => $this->hashValidator($validator),
            'expires_at'   => null,
            'last_used_at' => date('Y-m-d H:i:s'),
            'created_at'   => date('Y-m-d H:i:s'),
        ]);

        $this->writeCookie($selector, $validator);
    }

    public function restoreSessionIfValid(): bool
    {
        $parts = $this->readCookie();
        if ($parts === null) {
            return false;
        }

        [$selector, $validator] = $parts;

        $row = $this->tokens->where('selector', $selector)->first();
        if ($row === null) {
            $this->clearCookie();

            return false;
        }

        if ($row['expires_at'] !== null && strtotime((string) $row['expires_at']) <= time()) {
            $this->tokens->delete($row['id']);
            $this->clearCookie();

            return false;
        }

        if (! hash_equals((string) $row['token_hash'], $this->hashValidator($validator))) {
            $this->tokens->delete($row['id']);
            $this->clearCookie();

            return false;
        }

        $user = $this->users->find($row['user_id']);
        if ($user === null || (int) $user['is_active'] !== 1) {
            $this->tokens->delete($row['id']);
            $this->clearCookie();

            return false;
        }

        $newValidator = bin2hex(random_bytes(24));
        $this->tokens->update($row['id'], [
            'token_hash'   => $this->hashValidator($newValidator),
            'last_used_at' => date('Y-m-d H:i:s'),
        ]);
        $this->writeCookie($selector, $newValidator);

        (new AuthSessionService())->establish($user, regenerate: true);

        return true;
    }

    public function revokeCurrent(): void
    {
        $parts = $this->readCookie();
        if ($parts !== null) {
            $this->tokens->where('selector', $parts[0])->delete();
        }

        $this->clearCookie();
    }

    public function revokeAllForUser(int $userId): void
    {
        $this->tokens->where('user_id', $userId)->delete();
    }

    public function clearCookie(): void
    {
        $cookie = new Cookie($this->config->rememberCookie, '', [
            'expires'  => new DateTimeImmutable('-1 hour'),
            'path'     => '/',
            'httponly' => true,
            'samesite' => Cookie::SAMESITE_LAX,
            'secure'   => $this->cookieSecure(),
            'raw'      => false,
        ]);

        service('response')->setCookie($cookie);
    }

    /**
     * @return list{string, string}|null
     */
    private function readCookie(): ?array
    {
        $raw = (string) service('request')->getCookie($this->config->rememberCookie);
        if ($raw === '') {
            return null;
        }

        $parts = explode('.', $raw, 2);
        if (count($parts) !== 2 || $parts[0] === '' || $parts[1] === '') {
            return null;
        }

        return [$parts[0], $parts[1]];
    }

    private function writeCookie(string $selector, string $validator): void
    {
        $cookie = new Cookie($this->config->rememberCookie, $selector . '.' . $validator, [
            'expires'  => new DateTimeImmutable('+10 years'),
            'path'     => '/',
            'httponly' => true,
            'samesite' => Cookie::SAMESITE_LAX,
            'secure'   => $this->cookieSecure(),
            'raw'      => false,
        ]);

        service('response')->setCookie($cookie);
    }

    private function hashValidator(string $validator): string
    {
        $key = (string) config(Encryption::class)->key;
        if ($key === '') {
            throw new \RuntimeException('encryption.key is not configured.');
        }

        return hash_hmac('sha256', $validator, $key);
    }

    private function cookieSecure(): bool
    {
        $configured = config('Cookie')->secure;

        return $configured || service('request')->isSecure();
    }
}
