<?php

namespace Daylight\Core\Models\Concerns;

use BaconQrCode\Renderer\Image\SvgImageBackEnd;
use BaconQrCode\Renderer\ImageRenderer;
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
use BaconQrCode\Writer;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use PragmaRX\Google2FA\Google2FA;

trait HasTwoFactorAuthentication
{
    public function hasTwoFactorEnabled(): bool
    {
        return ! is_null($this->mfa_secret) && ! is_null($this->mfa_verified_at);
    }

    public function enableTwoFactor(): string
    {
        $google2fa = new Google2FA;
        $secret = $google2fa->generateSecretKey();

        $this->forceFill([
            'mfa_secret' => encrypt($secret),
            'mfa_recovery_code' => encrypt(json_encode($this->generateRecoveryCodes())),
            'mfa_verified_at' => null,
        ])->save();

        return $secret;
    }

    public function confirmTwoFactor(): void
    {
        $this->forceFill([
            'mfa_verified_at' => now(),
        ])->save();
    }

    public function disableTwoFactor(): void
    {
        $this->forceFill([
            'mfa_secret' => null,
            'mfa_recovery_code' => null,
            'mfa_verified_at' => null,
        ])->save();
    }

    public function twoFactorSecret(): ?string
    {
        if (is_null($this->mfa_secret)) {
            return null;
        }

        return decrypt($this->mfa_secret);
    }

    public function verifyTwoFactorCode(string $code): bool
    {
        $google2fa = new Google2FA;

        return $google2fa->verifyKey($this->twoFactorSecret(), $code);
    }

    public function verifyRecoveryCode(string $code): bool
    {
        $codes = $this->recoveryCodes();

        if (! $codes->contains($code)) {
            return false;
        }

        $this->forceFill([
            'mfa_recovery_code' => encrypt(json_encode(
                $codes->reject(fn (string $c) => $c === $code)->values()->all()
            )),
        ])->save();

        return true;
    }

    public function recoveryCodes(): Collection
    {
        if (is_null($this->mfa_recovery_code)) {
            return collect();
        }

        return collect(json_decode(decrypt($this->mfa_recovery_code), true));
    }

    public function regenerateRecoveryCodes(): Collection
    {
        $codes = $this->generateRecoveryCodes();

        $this->forceFill([
            'mfa_recovery_code' => encrypt(json_encode($codes)),
        ])->save();

        return collect($codes);
    }

    public function twoFactorQrCodeSvg(): string
    {
        $renderer = new ImageRenderer(
            new RendererStyle(192),
            new SvgImageBackEnd
        );

        $writer = new Writer($renderer);

        $google2fa = new Google2FA;

        $url = $google2fa->getQRCodeUrl(
            config('app.name'),
            $this->email,
            $this->twoFactorSecret()
        );

        return $writer->writeString($url);
    }

    /**
     * @return string[]
     */
    public function generateRecoveryCodes(int $count = 8): array
    {
        return collect(range(1, $count))
            ->map(fn () => Str::lower(Str::random(4) . '-' . Str::random(4)))
            ->all();
    }
}
