<?php

namespace App\Services;

use App\Models\User;
use App\Models\SystemSetting;
use PragmaRX\Google2FA\Google2FA;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Collection;

class TwoFactorService
{
    protected Google2FA $google2fa;

    public function __construct()
    {
        $this->google2fa = new Google2FA();
    }

    /**
     * Generate a new 2FA secret.
     *
     * @return string
     */
    public function generateSecret(): string
    {
        return $this->google2fa->generateSecretKey();
    }

    /**
     * Get QR code URL for authenticator app setup.
     *
     * @param User $user
     * @param string $secret
     * @return string
     */
    public function getQrCodeUrl(User $user, string $secret): string
    {
        return $this->google2fa->getQRCodeUrl(
            config('app.name'),
            $user->email,
            $secret
        );
    }

    /**
     * Verify a 2FA code.
     *
     * @param string $secret
     * @param string $code
     * @return bool
     */
    public function verifyCode(string $secret, string $code): bool
    {
        return $this->google2fa->verifyKey($secret, $code);
    }

    /**
     * Enable 2FA for a user.
     *
     * @param User $user
     * @param string $secret
     * @return Collection Recovery codes
     */
    public function enable(User $user, string $secret): Collection
    {
        $recoveryCodes = $this->generateRecoveryCodes();

        $user->update([
            'two_factor_enabled' => true,
            'two_factor_secret' => encrypt($secret),
            'two_factor_recovery_codes' => encrypt(json_encode($recoveryCodes)),
            'two_factor_confirmed_at' => now(),
        ]);

        return $recoveryCodes;
    }

    /**
     * Disable 2FA for a user.
     *
     * @param User $user
     * @return void
     */
    public function disable(User $user): void
    {
        $user->update([
            'two_factor_enabled' => false,
            'two_factor_secret' => null,
            'two_factor_recovery_codes' => null,
            'two_factor_confirmed_at' => null,
        ]);
    }

    /**
     * Verify 2FA for a user during login.
     *
     * @param User $user
     * @param string $code
     * @return bool
     */
    public function verify(User $user, string $code): bool
    {
        // Check if it's a recovery code
        if ($this->isValidRecoveryCode($user, $code)) {
            $this->useRecoveryCode($user, $code);
            return true;
        }

        // Verify regular code
        $secret = decrypt($user->two_factor_secret);
        return $this->verifyCode($secret, $code);
    }

    /**
     * Generate recovery codes.
     *
     * @param int $count
     * @return Collection
     */
    protected function generateRecoveryCodes(int $count = 8): Collection
    {
        $codes = collect();

        for ($i = 0; $i < $count; $i++) {
            $codes->push($this->generateRecoveryCode());
        }

        return $codes;
    }

    /**
     * Generate a single recovery code.
     *
     * @return string
     */
    protected function generateRecoveryCode(): string
    {
        return strtoupper(substr(md5(random_bytes(32)), 0, 8));
    }

    /**
     * Check if code is a valid recovery code.
     *
     * @param User $user
     * @param string $code
     * @return bool
     */
    protected function isValidRecoveryCode(User $user, string $code): bool
    {
        $codes = json_decode(decrypt($user->two_factor_recovery_codes), true);
        return in_array(strtoupper($code), $codes);
    }

    /**
     * Use a recovery code (remove it from the list).
     *
     * @param User $user
     * @param string $code
     * @return void
     */
    protected function useRecoveryCode(User $user, string $code): void
    {
        $codes = json_decode(decrypt($user->two_factor_recovery_codes), true);
        $codes = array_filter($codes, fn($c) => $c !== strtoupper($code));

        $user->update([
            'two_factor_recovery_codes' => encrypt(json_encode(array_values($codes))),
        ]);
    }

    /**
     * Get remaining recovery codes count.
     *
     * @param User $user
     * @return int
     */
    public function remainingRecoveryCodes(User $user): int
    {
        if (!$user->two_factor_recovery_codes) {
            return 0;
        }

        $codes = json_decode(decrypt($user->two_factor_recovery_codes), true);
        return count($codes);
    }

    /**
     * Regenerate recovery codes.
     *
     * @param User $user
     * @return Collection
     */
    public function regenerateRecoveryCodes(User $user): Collection
    {
        $recoveryCodes = $this->generateRecoveryCodes();

        $user->update([
            'two_factor_recovery_codes' => encrypt(json_encode($recoveryCodes)),
        ]);

        return $recoveryCodes;
    }

    /**
     * Check if 2FA is enforced for a specific role.
     *
     * @param string $roleName
     * @return bool
     */
    public function isEnforcedForRole(string $roleName): bool
    {
        $enforcedRoles = SystemSetting::get('2fa_enforced_roles', []);
        return in_array($roleName, $enforcedRoles);
    }

    /**
     * Get roles for which 2FA is enforced.
     *
     * @return array
     */
    public function getEnforcedRoles(): array
    {
        return SystemSetting::get('2fa_enforced_roles', []);
    }

    /**
     * Set roles for which 2FA is enforced.
     *
     * @param array $roleNames
     * @return void
     */
    public function setEnforcedRoles(array $roleNames): void
    {
        SystemSetting::set(
            '2fa_enforced_roles',
            $roleNames,
            SystemSetting::TYPE_JSON,
            SystemSetting::GROUP_SECURITY,
            'Roles for which 2FA is mandatory',
            false
        );
    }
}
