<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\TwoFactorConfirmRequest;
use App\Http\Requests\Admin\TwoFactorDisableRequest;
use App\Http\Requests\Admin\TwoFactorEnableRequest;
use App\Http\Requests\Admin\TwoFactorVerifyRequest;
use App\Models\Admin;
use App\Services\IntendedUrlResolver;
use App\Services\TotpService;
use App\Services\TwoFactorChannelService;
use App\Traits\LogsAdminActivity;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;

class TwoFactorController extends Controller
{
    use LogsAdminActivity;

    public function __construct(
        private TotpService $totpService,
        private TwoFactorChannelService $channelService,
    ) {}

    /** Yönetici için izin verilen 2FA kanalları (ayar ∩ telefon zorunluluğu). */
    private function allowedChannels(Admin $admin): array
    {
        $allowed = setting_array('two_factor_admin_allowed_channels');
        $allowed = array_values(array_intersect($allowed, TwoFactorChannelService::methods()));

        if (empty($admin->phone)) {
            $allowed = array_values(array_diff($allowed, ['sms']));
        }

        return $allowed ?: ['totp'];
    }

    public function setup()
    {
        $admin = Auth::guard('admin')->user();

        return view('admin.auth.two-factor-setup', [
            'enabled' => $admin->totp_enabled,
            'method' => $admin->two_factor_method,
            'allowedChannels' => $this->allowedChannels($admin),
        ]);
    }

    public function enable(TwoFactorEnableRequest $request)
    {
        $admin = Auth::guard('admin')->user();

        if (! Hash::check($request->password, $admin->password)) {
            return back()->withErrors(['password' => __('admin/two-factor.password_incorrect')]);
        }

        $method = $request->input('method');
        if (! in_array($method, $this->allowedChannels($admin), true)) {
            return back()->withErrors(['method' => __('admin/two-factor.channel_not_allowed')]);
        }

        // E-posta/SMS kanalı: OTP gönder, onay ekranı.
        if ($method !== 'totp') {
            $request->session()->forget('2fa_setup_secret');
            $request->session()->put('2fa_setup_method', $method);
            $this->channelService->dispatchOtp($admin, $method);

            return view('admin.auth.two-factor-channel-confirm', [
                'method' => $method,
                'maskedTarget' => $this->channelService->maskedTarget($admin, $method),
            ]);
        }

        // TOTP kanalı: mevcut QR akışı.
        $request->session()->forget('2fa_setup_method');
        $secret = $this->totpService->generateSecret();
        $qrUri = $this->totpService->getQrCodeUri($secret, $admin->email);

        $request->session()->put('2fa_setup_secret', $secret);

        return view('admin.auth.two-factor-confirm', [
            'secret' => $secret,
            'qrUri' => $qrUri,
        ]);
    }

    public function confirm(TwoFactorConfirmRequest $request)
    {
        $admin = Auth::guard('admin')->user();

        // Kanal (email/sms) onay yolu.
        $pendingMethod = $request->session()->get('2fa_setup_method');
        if ($pendingMethod) {
            if (! $this->channelService->verifyOtp($admin, $request->code)) {
                return back()->withErrors(['code' => __('admin/two-factor.code_invalid')]);
            }

            $backupCodes = $this->totpService->generateBackupCodes();
            $admin->update([
                'two_factor_method' => $pendingMethod,
                'totp_secret' => null,
                'totp_enabled' => true,
                'totp_backup_codes' => $this->totpService->encryptBackupCodes($backupCodes),
                'totp_verified_at' => now(),
            ]);

            $request->session()->forget('2fa_setup_method');

            return $this->finishEnable($request, $admin, $backupCodes);
        }

        // TOTP onay yolu.
        $secret = $request->session()->get('2fa_setup_secret');

        if (! $secret) {
            return redirect()->route('admin.2fa.setup')
                ->withErrors(['code' => __('admin/two-factor.session_expired')]);
        }

        if (! $this->totpService->verifyCode($secret, $request->code)) {
            return back()->withErrors(['code' => __('admin/two-factor.code_invalid')]);
        }

        $backupCodes = $this->totpService->generateBackupCodes();

        $admin->update([
            'two_factor_method' => 'totp',
            'totp_secret' => $this->totpService->encryptSecret($secret),
            'totp_enabled' => true,
            'totp_backup_codes' => $this->totpService->encryptBackupCodes($backupCodes),
            'totp_verified_at' => now(),
        ]);

        $request->session()->forget('2fa_setup_secret');

        return $this->finishEnable($request, $admin, $backupCodes);
    }

    /** Enable sonrası ortak kuyruk: session verified + log + yedek kod ekranı. */
    private function finishEnable(Request $request, Admin $admin, array $backupCodes)
    {
        $request->session()->put('admin_2fa_verified', true);

        $this->logUpdated(
            model: $admin,
            description: "Two-factor authentication enabled for admin: {$admin->email}"
        );

        return view('admin.auth.two-factor-backup-codes', [
            'backupCodes' => $backupCodes,
        ]);
    }

    public function disable(TwoFactorDisableRequest $request)
    {
        $admin = Auth::guard('admin')->user();

        if (! Hash::check($request->password, $admin->password)) {
            return back()->withErrors(['password' => __('admin/two-factor.password_incorrect')]);
        }

        $admin->update([
            'two_factor_method' => null,
            'totp_secret' => null,
            'totp_enabled' => false,
            'totp_backup_codes' => null,
            'totp_verified_at' => null,
            'two_factor_otp_hash' => null,
            'two_factor_otp_expires_at' => null,
            'two_factor_otp_sent_at' => null,
        ]);

        $request->session()->forget('admin_2fa_verified');
        $request->session()->forget('2fa_setup_method');

        $this->logUpdated(
            model: $admin,
            description: "Two-factor authentication disabled for admin: {$admin->email}"
        );

        return redirect()->route('admin.2fa.setup')
            ->with('success', __('admin/two-factor.disabled_success'));
    }

    public function showVerify()
    {
        $admin = Auth::guard('admin')->user();

        if (! $admin || ! $admin->totp_enabled) {
            return redirect()->route('admin.dashboard');
        }

        if (session('admin_2fa_verified')) {
            return redirect()->route('admin.dashboard');
        }

        $method = $admin->two_factor_method ?? 'totp';

        if ($method !== 'totp' && ! $this->channelService->hasActiveOtp($admin)) {
            $this->channelService->dispatchOtp($admin);
        }

        return view('admin.auth.two-factor-verify', [
            'method' => $method,
            'maskedTarget' => $method === 'totp' ? '' : $this->channelService->maskedTarget($admin),
        ]);
    }

    /** OTP'yi yeniden gönder (email/sms) — throttle + canResend guard. */
    public function resend()
    {
        $admin = Auth::guard('admin')->user();

        if (! $admin || ! $admin->totp_enabled) {
            return redirect()->route('admin.login');
        }

        $method = $admin->two_factor_method ?? 'totp';
        if ($method === 'totp') {
            return redirect()->route('admin.2fa.verify');
        }

        if (! $this->channelService->canResend($admin)) {
            return back()->with('warning', __('admin/two-factor.resend_too_soon'));
        }

        $this->channelService->dispatchOtp($admin);

        return back()->with('success', __('admin/two-factor.resend_sent'));
    }

    public function verify(TwoFactorVerifyRequest $request)
    {
        $admin = Auth::guard('admin')->user();

        if (! $admin || ! $admin->totp_enabled) {
            return redirect()->route('admin.login');
        }

        $code = $request->code;
        $method = $admin->two_factor_method ?? 'totp';

        // TOTP kanalı — PRC-217 replay-protected verify.
        if ($method === 'totp') {
            if (! $admin->totp_secret) {
                return redirect()->route('admin.login');
            }
            $secret = $this->totpService->decryptSecret($admin->totp_secret);
            $verifiedStep = $this->totpService->verifyCodeWithReplayCheck(
                $secret, $code, $admin->last_used_totp_step
            );
            if ($verifiedStep !== null) {
                $admin->forceFill(['last_used_totp_step' => $verifiedStep])->save();

                $request->session()->put('admin_2fa_verified', true);
                // PRC-165: SEC-Critical — 2FA verify sonrası session id rotate.
                // Pre-2FA session id'sini ele geçiren saldırgan 2FA bypass
                // edemesin (kurban verify ettiğinde session id değişir).
                $request->session()->migrate(true);

                return redirect(IntendedUrlResolver::pullForAdmin(route('admin.dashboard')));
            }
        } else {
            // E-posta/SMS kanalı — tek-kullanım OTP.
            if ($this->channelService->verifyOtp($admin, $code)) {
                $request->session()->put('admin_2fa_verified', true);
                $request->session()->migrate(true);

                return redirect(IntendedUrlResolver::pullForAdmin(route('admin.dashboard')));
            }
        }

        // Try backup code (tüm kanallar için geçerli fallback)
        if ($admin->totp_backup_codes) {
            $backupCodes = $this->totpService->decryptBackupCodes($admin->totp_backup_codes);
            $upperCode = strtoupper($code);

            if (in_array($upperCode, $backupCodes)) {
                $backupCodes = array_values(array_diff($backupCodes, [$upperCode]));
                $admin->update([
                    'totp_backup_codes' => $this->totpService->encryptBackupCodes($backupCodes),
                ]);

                $request->session()->put('admin_2fa_verified', true);
                // PRC-165: SEC-Critical — backup code path için de session rotate.
                $request->session()->migrate(true);

                Log::info('Admin used backup code for 2FA', [
                    'admin_id' => $admin->id,
                    'remaining_codes' => count($backupCodes),
                ]);

                return redirect(IntendedUrlResolver::pullForAdmin(route('admin.dashboard')))
                    ->with('warning', __('admin/two-factor.backup_code_used', ['remaining' => count($backupCodes)]));
            }
        }

        Log::warning('Failed 2FA verification attempt', [
            'admin_id' => $admin->id,
            'ip' => $request->ip(),
        ]);

        return back()->withErrors(['code' => __('admin/two-factor.code_invalid')]);
    }
}
