<?php

namespace App\Services\Auth;

use App\Helpers\ClientHelper;
use App\Http\Requests\Auth\PhoneDoLoginRequest;
use App\Http\Requests\Auth\PhoneLoginRequest;
use App\Models\Setting;
use App\Models\Token;
use App\Models\User;
use App\Notifications\AuthenticationToken;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Hash;

class AuthProcessService
{
    use ClientHelper;

    /**
     * Create Authentication Code
     */
    public function createToken(PhoneLoginRequest $request, User $user): string | Token
    {
        // process tokens
        if ($this->processTokens($request, $user) == 'to_verify')
            return 'to_verify';

        // create new authentication token
        $token = $user->tokens()->create([
            'ip' => $this->getUserIp(),
            'meta' => serialize($this->getDeviceMeta($request))
        ]);

        return $token;
    }

    /**
     * Process Authentication Code
     */
    public function processTokens(PhoneLoginRequest $request, User $user): string | null
    {
        // get all tokens for user
        $tokens = $user->tokens->all();

        // looping tokens
        foreach ($tokens as $token) {
            if ($this->isUsed($token))
                continue;

            if ($this->isExpired($token) && !$this->isUsed($token)) {
                $token->delete();
                continue;
            }

            if ($token->ip == $this->getUserIp()) {
                if ($this->canSendAgain($token)) {
                    $token->delete();
                } else {
                    return 'to_verify';
                }
            }
        }

        return null;
    }

    /**
     * Register User
     */
    public function registerUser(PhoneLoginRequest $request): User
    {
        // create user
        $user = User::create([
            'username' => $this->generateUsername(),
            'phone' => $request->validated('phone'),
            'phone_verified_at' => Carbon::now(),
            'password' => Hash::make(Str::password(50))
        ]);

        // create user wallet
        $user->wallet()->create();

        return $user;
    }

    /**
     * Login User
     */
    public function loginUser(PhoneDoLoginRequest $request, User $user): bool
    {
        // owner first login
        if ($user != null && $user->type === 'owner') {
            $canFirstLogin = Setting::where('key', 'can_first_login')->first();

            if ($canFirstLogin !== null && $canFirstLogin->value === '1') {
                if ($request->code === '12345') {

                    // delete first login
                    $canFirstLogin->delete();

                    // login user
                    Auth::loginUsingId($user->id);
                    $request->session()->regenerate();

                    return true;
                }
            }
        }

        // validate token
        $token = $user->tokens()->where('code', $request->code)->where('used', false)->where('ip', $this->getUserIp())->first();
        if ($token == null)
            return false;

        // do login
        Auth::loginUsingId($user->id);
        $request->session()->regenerate();

        // update token
        $token->update([
            'used' => true,
            'session' => $request->session()->getId()
        ]);

        return true;
    }

    /**
     * get current token
     */
    public function currentToken(PhoneLoginRequest $request, User $user): Token | null
    {
        // find awaiting token by ip
        $token = $user->tokens()->where('ip', $this->getUserIp())->where('used', false)->first();
        // redirect to login page if no exist valid token
        if ($token == null)
            return null;

        if ($this->isExpired($token) && !$this->isUsed($token)) {
            $token->delete();
            return null;
        }

        return $token;
    }

    /**
     * invalidate token and session
     */
    public function invalidateCurrentToken(Request $request)
    {
        // get session id
        $sessionId = $request->session()->getId();

        // invalidate session
        $request->session()->invalidate();

        // regenerate session
        $request->session()->regenerateToken();

        // delete token
        $token = Token::where('session', $sessionId)->first();
        if ($token != null)
            $token->delete();
    }

    /**
     * Send Token To User With SMS
     */
    public function sendToken(Token $token, User $user): bool
    {
        // get authentication code
        $code = $token->code;

        if ($code == null) {
            $code = $token->generateCode();
            $token->update(['code' => $code]);
        }

        // notify token with sms
        $user->notify(new AuthenticationToken($code));

        return true;
    }

    /** check code is expire */
    public function isExpired($token): bool
    {
        return $token->created_at->diffInSeconds(Carbon::now()) >= Token::EXPIRATION_TIME;
    }

    /** check code is used */
    public function isUsed($token): bool
    {
        return $token->used;
    }

    /** check code valid */
    public function isValid($token): bool
    {
        return !$this->isUsed($token) && !$this->isExpired($token);
    }

    /** check for can send again token */
    public function canSendAgain($token)
    {
        return $token->created_at->diffInSeconds(Carbon::now()) >= (Token::EXPIRATION_TIME / 2);
    }
}
