<?php

namespace Lucent\Account;

use Carbon\Carbon;
use Illuminate\Contracts\Session\Session;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str;
use Lucent\Channel\ChannelService;
use Lucent\LucentException;
use Lucent\Mail\LoginMail;

readonly class AuthService
{

    public function __construct(
        private ChannelService $channelService,
        private UserRepo       $userRepo,
        public Session         $session,
    )
    {

    }


    public function currentUserId(): ?string
    {

        if (app()->runningInConsole()) {
            return config("lucent.systemUserId");
        } elseif(request()->segment(1) !== "lucent") {
            return config("lucent.systemUserId");
        } else {
            return $this->session->get("user.id");
        }

    }

    public
    function currentUserRoles(): array
    {
        return $this->session->get("user.roles") ?? [];
    }

    public
    function isLoggedIn(): bool
    {
        return !empty($this->currentUserId());
    }

    /**
     * @throws LucentException
     */
    public
    function login(string $email, string $token): void
    {

        $user = $this->userRepo->findByEmail(new Email($email));

        if ($user->isEmpty()) {
            throw new LucentException("Your account was not found");
        }

        if ($user->get()->isRemoved()) {
            throw new LucentException("Your account is not active");
        }

        if ($user->get()->mailToken !== $token) {
            throw new LucentException("Token has expired or is invalid");
        }

        if (Carbon::parse($user->get()->loggedInAt)->lte(Carbon::now()->subHours(1))) {
            throw new LucentException("Token has expired.");
        }

        $newUser = $user->get();
        $newUser->updatedAt = Carbon::now()->toJson();
        $newUser->mailToken = null;
        $this->userRepo->update($newUser);
        $this->session->put(["user" => $user->get()->safe()]);
    }

    public
    function refreshSession()
    {


        $user = $this->userRepo->findById($this->currentUserId());

        if ($user->isEmpty()) {
            throw new LucentException("Your account was not found");
        }

        if ($user->get()->isRemoved()) {
            throw new LucentException("Your account is not active");
        }

        $newUser = $user->get();
        $this->session->put(["user" => $user->get()->safe()]);
    }


    public
    function create(string $name, string $email, array $roles): User
    {
        $user = new User(
            id: (string)Str::uuid(),
            name: new Name($name),
            email: new Email($email),
            roles: $this->validateRoles($roles),
            createdAt: Carbon::now()->toJson(),
            updatedAt: Carbon::now()->toJson(),
            loggedInAt: Carbon::now()->toJson(),
            mailToken: Token::new(32),
        );

        $this->userRepo->insert($user);
        return $user;
    }


    /**
     * @throws LucentException
     */
    public
    function sendLoginEmail(string $email): void
    {
        $emailAddress = (new Email($email));
        $user = $this->userRepo->findByEmail($emailAddress);

        if ($user->isEmpty()) {
            throw new LucentException("User not found");
        }

        if ($user->get()->isRemoved()) {
            throw new LucentException("Cannot reset email if the user is not active");
        }


        $newToken = $this->userRepo->updateLoginToken($user->get()->id);

        Mail::to($email)->send(
            new LoginMail(
                $email,
                $newToken,
                $this->channelService->channel->lucentUrl
            )
        );
    }


    /**
     * @throws LucentException
     */
    public
    function changeRoles(string $userId, array $roles): void
    {
        $user = $this->userRepo->findById($userId);

        if ($user->isEmpty()) {
            throw new LucentException("User not found");
        }

        $newUser = $user->get();
        $newUser->roles = $this->validateRoles($roles);
        $newUser->updatedAt = Carbon::now()->toJson();
        $this->userRepo->update($newUser);
    }

    /**
     * @throws LucentException
     */
    public
    function updateName(string $name): void
    {
        $name = (new Name($name));
        $this->userRepo->updateName($this->currentUserId(), $name);
        $user = $this->userRepo->findById($this->currentUserId());
        $this->session->put(["user" => $user->get()->safe()]);
    }

    /**
     * @throws LucentException
     */
    public
    function updateEmail(string $email): void
    {
        $email = (new Email($email));
        $user = $this->userRepo->findByEmail($email);
        if ($user->isDefined()) {
            throw new LucentException("Email already assigned to user");
        }

        $this->userRepo->updateEmail($this->currentUserId(), $email);
        $user = $this->userRepo->findById($this->currentUserId());
        $this->session->put(["user" => $user->get()->safe()]);
    }


    /**
     * @throws LucentException
     */
    public
    function invite(
        string $name,
        string $email,
        array  $roles
    ): User
    {
        $user = $this->create($name, $email, $roles);
        $this->sendLoginEmail($user->email);
        return $user;
    }

    /**
     * @throws LucentException
     */
    public
    function registerAdmin(
        string $name,
        string $email
    ): User
    {
        $user = $this->invite($name, $email, ["admin"]);
        $this->sendLoginEmail($user->email);
        return $user;
    }

    public
    function validateRoles(array $roles): array
    {
        return collect($roles)
            ->filter(fn(string $role) => in_array($role, $this->channelService->channel->roles))
            ->unique()
            ->values()
            ->toArray();
    }


}
