<?php

namespace App\Http\Controllers\Admin\Auth;

use App\Http\Controllers\Controller;
use App\Rules\RecaptchaV3Rule;
use App\Services\Admin\AdminAuthService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;

final class AdminAuthController extends Controller
{
    public function __construct(
        private readonly AdminAuthService $authService
    ) {}

    public function showLogin()
    {
        return view('admin.auth.login');
    }

    public function storeLogin(Request $request)
    {
        $rules = [
            'login_id' => ['required', 'string', 'max:190'],
            'password' => ['required', 'string', 'max:255'],
            'remember' => ['nullable'],
        ];

        if (app()->environment('production')) {
            $rules['g-recaptcha-response'] = ['required', new RecaptchaV3Rule('admin_login')];
        }

        $data = $request->validate($rules);

        $email    = strtolower(trim((string) $data['login_id']));
        $remember = (bool) $request->boolean('remember');
        $ip       = (string) $request->ip();

        $res = $this->authService->startLogin(
            email: $email,
            password: (string) $data['password'],
            remember: $remember,
            ip: $ip
        );

        $state = (string) ($res['state'] ?? '');

        // ✅ 1) 계정 잠김
        if ($state === 'locked') {
            $msg = '계정이 잠금 상태입니다. 최고관리자에게 잠금 해제를 요청해 주세요.';

            return back()
                ->withInput()
                ->withErrors(['login_id' => $msg])
                ->with('toast', [
                    'type' => 'danger',
                    'title' => '계정 잠김',
                    'message' => $msg,
                ]);
        }

        // ✅ 2) 비번 불일치/계정없음 (남은 시도 횟수 포함)
        if ($state === 'invalid') {
            $left = $res['attempts_left'] ?? null;

            $msg = '이메일 또는 비밀번호를 확인하세요.';
            if ($left !== null && is_numeric($left)) {
                $msg .= ' (남은 시도 ' . (int)$left . '회)';
            }

            return back()
                ->withInput()
                ->withErrors(['login_id' => $msg])
                ->with('toast', [
                    'type' => 'danger',
                    'title' => '로그인 실패',
                    'message' => $msg,
                ]);
        }

        // ✅ 3) 차단/비활성 계정
        if ($state === 'blocked') {
            $msg = '로그인 할 수 없는 계정입니다.';

            return back()
                ->withInput()
                ->withErrors(['login_id' => $msg])
                ->with('toast', [
                    'type' => 'danger',
                    'title' => '접근 불가',
                    'message' => $msg,
                ]);
        }

        // ✅ 4) SMS 발송 실패
        if ($state === 'sms_error') {
            $msg = '인증 SMS 발송에 실패했습니다. 잠시 후 다시 시도해 주세요.';

            return back()
                ->withInput()
                ->withErrors(['login_id' => $msg])
                ->with('toast', [
                    'type' => 'danger',
                    'title' => 'SMS 오류',
                    'message' => $msg,
                ]);
        }

        // ✅ 5) 비번 재설정 강제
        if ($state === 'must_reset') {
            $request->session()->put('admin_pwreset', [
                'admin_id' => (int) ($res['admin_id'] ?? 0),
                'email' => $email,
                'expires_at' => time() + 600,
                'ip' => $ip,
            ]);

            return redirect()
                ->route('admin.password.reset.form')
                ->with('toast', [
                    'type' => 'info',
                    'title' => '비밀번호 변경 필요',
                    'message' => '비밀번호 초기화가 필요합니다. 새 비밀번호를 설정해 주세요.',
                ]);
        }

        if ($state === 'totp_required') {
            $adminId = (int) ($res['admin_id'] ?? 0);
            if ($adminId <= 0) {
                return back()
                    ->withInput()
                    ->withErrors(['login_id' => '로그인 처리 중 오류가 발생했습니다. 다시 시도해 주세요.']);
            }

            // (권장) 세션 고정 공격 방지
            $request->session()->regenerate();

            $ttl = (int) config('admin.totp_ttl', 300);
            $request->session()->put('admin_totp', [
                'admin_id' => $adminId,
                'remember' => $remember,
                'ip' => $ip,
                'attempts' => 0,
                'max_attempts' => (int) config('admin.totp_max_attempts', 5),
                'expires_at' => time() + $ttl,
            ]);

            return redirect()
                ->route('admin.totp.form')
                ->with('toast', [
                    'type' => 'info',
                    'title' => 'Google OTP 인증',
                    'message' => 'Google Authenticator 앱의 6자리 코드를 입력해 주세요.',
                ]);
        }

        // ✅ 6) OTP 발송 성공
        if ($state === 'otp_sent') {
            $request->session()->put('admin_2fa', [
                'challenge_id' => (string) ($res['challenge_id'] ?? ''),
                'masked_phone' => (string) ($res['masked_phone'] ?? ''),
                'expires_at' => time() + (int) config('admin.sms_ttl', 180),
            ]);

            // (권장) 세션 고정 공격 방지
            $request->session()->regenerate();

            return redirect()
                ->route('admin.otp.form')
                ->with('toast', [
                    'type' => 'success',
                    'title' => '인증번호 발송',
                    'message' => '인증번호를 문자로 발송했습니다.',
                ]);
        }

        // ✅ 방어: 예상치 못한 상태
        return back()
            ->withInput()
            ->withErrors(['login_id' => '로그인 처리 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.'])
            ->with('toast', [
                'type' => 'danger',
                'title' => '처리 오류',
                'message' => '로그인 처리 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.',
            ]);
    }


    // 비밀번호 초기화 폼
    public function showForceReset(Request $request)
    {
        $pending = (array) $request->session()->get('admin_pwreset', []);
        if (empty($pending['admin_id'])) {
            return redirect()->route('admin.login.form')
                ->withErrors(['login_id' => '비밀번호 초기화 세션이 없습니다. 다시 로그인해 주세요.']);
        }

        if ((int)($pending['expires_at'] ?? 0) < time()) {
            $request->session()->forget('admin_pwreset');
            return redirect()->route('admin.login.form')
                ->withErrors(['login_id' => '비밀번호 초기화가 만료되었습니다. 다시 로그인해 주세요.']);
        }

        return view('admin.auth.password_reset', [
            'email' => (string)($pending['email'] ?? ''),
        ]);
    }

    // 비밀번호 초기화 저장
    public function storeForceReset(Request $request)
    {
        $pending = (array) $request->session()->get('admin_pwreset', []);
        if (empty($pending['admin_id'])) {
            return redirect()->route('admin.login.form')
                ->withErrors(['login_id' => '비밀번호 초기화 세션이 없습니다. 다시 로그인해 주세요.']);
        }

        if ((int)($pending['expires_at'] ?? 0) < time()) {
            $request->session()->forget('admin_pwreset');
            return redirect()->route('admin.login.form')
                ->withErrors(['login_id' => '비밀번호 초기화가 만료되었습니다. 다시 로그인해 주세요.']);
        }

        $data = $request->validate([
            'password' => ['required', 'string', 'min:10', 'max:255', 'confirmed'],
        ]);

        $ip = (string) $request->ip();
        $ua = (string) $request->userAgent();
        $res = $this->authService->resetPassword(
            adminId: (int) $pending['admin_id'],
            newPassword: (string) $data['password'],
            ip: $ip,
            ua: $ua,
        );

        if (!($res['ok'] ?? false)) {
            $request->session()->forget('admin_pwreset');
            return redirect()->route('admin.login.form')
                ->withErrors(['login_id' => '비밀번호 변경에 실패했습니다. 다시 로그인해 주세요.']);
        }

        $request->session()->forget('admin_pwreset');

        return redirect()->route('admin.login.form')
            ->with('status', '비밀번호가 변경되었습니다. 다시 로그인해 주세요.');
    }

    public function showOtp(Request $request)
    {
        $pending = (array) $request->session()->get('admin_2fa', []);

        if (empty($pending['challenge_id'])) {
            return redirect()->route('admin.login.form')
                ->withErrors(['login_id' => '인증 정보가 없습니다. 다시 로그인해 주세요.']);
        }

        if ((int)($pending['expires_at'] ?? 0) < time()) {
            $request->session()->forget('admin_2fa');
            return redirect()->route('admin.login.form')
                ->withErrors(['login_id' => '인증이 만료되었습니다. 다시 로그인해 주세요.']);
        }

        return view('admin.auth.otp', [
            'masked_phone' => (string)($pending['masked_phone'] ?? ''),
        ]);
    }

    public function verifyOtp(Request $request)
    {
        $data = $request->validate([
            'otp' => ['required', 'digits:6'],
        ]);

        $pending = (array) $request->session()->get('admin_2fa', []);
        $challengeId = (string)($pending['challenge_id'] ?? '');

        if ($challengeId === '') {
            return redirect()->route('admin.login.form')
                ->withErrors(['login_id' => '인증 정보가 없습니다. 다시 로그인해 주세요.']);
        }

        $res = $this->authService->verifyOtp(
            challengeId: $challengeId,
            otp: (string) $data['otp'],
            ip: (string) $request->ip()
        );

        if (!($res['ok'] ?? false)) {
            $reason = (string)($res['reason'] ?? '');
            $msg = match ($reason) {
                'expired'  => '인증이 만료되었습니다. 다시 로그인해 주세요.',
                'attempts' => '시도 횟수를 초과했습니다. 다시 로그인해 주세요.',
                'ip'       => '접속 정보가 변경되었습니다. 다시 로그인해 주세요.',
                'blocked'  => '로그인 할 수 없는 계정입니다.',
                default    => '인증번호가 올바르지 않습니다.',
            };

            if ($reason !== 'invalid') {
                $request->session()->forget('admin_2fa');
                return redirect()->route('admin.login.form')->withErrors(['login_id' => $msg]);
            }

            return back()->withErrors(['otp' => $msg]);
        }

        /** @var \App\Models\AdminUser $admin */
        $admin = $res['admin'];

        Auth::guard('admin')->login($admin, (bool)($res['remember'] ?? false));

        // 권한/관리자 정보 세션 적재
        $ctx = $this->authService->buildAdminSessionContext((int)$admin->id);
        if (!empty($ctx)) $request->session()->put('admin_ctx', $ctx);

        $request->session()->forget('admin_2fa');

        return redirect()->route('admin.home');
    }

    public function logout(Request $request)
    {
        Auth::guard('admin')->logout();

        $request->session()->forget('admin_ctx');

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

        return redirect()->route('admin.login.form');
    }

    public function security(Request $request)
    {
        $adminId = (int) auth()->guard('admin')->id();
        $vm = $this->authService->totpViewModel($adminId);

        abort_unless(($vm['ok'] ?? false), 404);
        return view('admin.me.security', $vm);
    }

    public function totpStart(Request $request)
    {
        $adminId = (int) auth()->guard('admin')->id();
        $res = $this->authService->totpStart(
            $adminId,
            false,
            (string)$request->ip(),
            (string)($request->userAgent() ?? '')
        );

        return back()->with($res['ok'] ? 'status' : 'error', $res['message']);
    }

    public function totpConfirm(Request $request)
    {
        $adminId = (int) auth()->guard('admin')->id();
        $data = $request->validate([
            'code' => ['required', 'string', 'max:10'],
        ]);

        $res = $this->authService->totpConfirm(
            $adminId,
            (string) $data['code'],
            (string)$request->ip(),
            (string)($request->userAgent() ?? '')
        );

        return redirect()->route('admin.security')
            ->with($res['ok'] ? 'status' : 'error', $res['message']);
    }

    public function totpDisable(Request $request)
    {
        $adminId = (int) auth()->guard('admin')->id();
        $res = $this->authService->totpDisable(
            $adminId,
            (string)$request->ip(),
            (string)($request->userAgent() ?? '')
        );

        return redirect()->route('admin.security')
            ->with($res['ok'] ? 'status' : 'error', $res['message']);
    }

    public function totpReset(Request $request)
    {
        $adminId = (int) auth()->guard('admin')->id();
        $res = $this->authService->totpReset($adminId);

        return redirect()->route('admin.security')
            ->with($res['ok'] ? 'status' : 'error', $res['message']);
    }

    public function totpMode(Request $request)
    {
        $adminId = (int) auth()->guard('admin')->id();
        $data = $request->validate([
            'totp_enabled' => ['required', 'in:0,1'],
        ]);

        $res = $this->authService->totpMode(
            $adminId,
            (int) $data['totp_enabled'],
            (string)$request->ip(),
            (string)($request->userAgent() ?? '')
        );

        return redirect()->route('admin.security')
            ->with($res['ok'] ? 'status' : 'error', $res['message']);
    }

    public function showTotp(Request $request)
    {
        $pending = (array) $request->session()->get('admin_totp', []);

        if (empty($pending['admin_id'])) {
            return redirect()->route('admin.login.form')
                ->withErrors(['login_id' => '인증 정보가 없습니다. 다시 로그인해 주세요.']);
        }

        if ((int)($pending['expires_at'] ?? 0) < time()) {
            $request->session()->forget('admin_totp');
            return redirect()->route('admin.login.form')
                ->withErrors(['login_id' => '인증이 만료되었습니다. 다시 로그인해 주세요.']);
        }

        return view('admin.auth.totp');
    }

    public function verifyTotp(Request $request)
    {
        $data = $request->validate([
            'code' => ['required', 'digits:6'],
        ]);

        $pending = (array) $request->session()->get('admin_totp', []);
        $adminId = (int)($pending['admin_id'] ?? 0);

        if ($adminId <= 0) {
            return redirect()->route('admin.login.form')
                ->withErrors(['login_id' => '인증 정보가 없습니다. 다시 로그인해 주세요.']);
        }

        if ((int)($pending['expires_at'] ?? 0) < time()) {
            $request->session()->forget('admin_totp');
            return redirect()->route('admin.login.form')
                ->withErrors(['login_id' => '인증이 만료되었습니다. 다시 로그인해 주세요.']);
        }

        // IP 고정 (SMS와 동일 정책)
        $ip = (string) $request->ip();
        if (!hash_equals((string)($pending['ip'] ?? ''), $ip)) {
            $request->session()->forget('admin_totp');
            return redirect()->route('admin.login.form')
                ->withErrors(['login_id' => '접속 정보가 변경되었습니다. 다시 로그인해 주세요.']);
        }

        // 시도 횟수 제한
        $attempts = (int)($pending['attempts'] ?? 0);
        $max = (int)($pending['max_attempts'] ?? 5);
        if ($attempts >= $max) {
            $request->session()->forget('admin_totp');
            return redirect()->route('admin.login.form')
                ->withErrors(['login_id' => '시도 횟수를 초과했습니다. 다시 로그인해 주세요.']);
        }

        $pending['attempts'] = $attempts + 1;
        $request->session()->put('admin_totp', $pending);

        $res = $this->authService->verifyTotp(
            adminId: $adminId,
            code: (string) $data['code'],
            ip: $ip
        );

        if (!($res['ok'] ?? false)) {
            $reason = (string)($res['reason'] ?? '');
            $msg = match ($reason) {
                'blocked'        => '로그인 할 수 없는 계정입니다.',
                'not_registered' => 'OTP 등록 상태가 올바르지 않습니다. 최고관리자에게 문의해 주세요.',
                default          => '인증코드가 올바르지 않습니다.',
            };

            // invalid는 페이지에서 재입력, 나머지는 로그인으로
            if ($reason !== 'invalid') {
                $request->session()->forget('admin_totp');
                return redirect()->route('admin.login.form')->withErrors(['login_id' => $msg]);
            }

            return back()->withErrors(['code' => $msg]);
        }

        /** @var \App\Models\AdminUser $admin */
        $admin = $res['admin'];

        Auth::guard('admin')->login($admin, (bool)($pending['remember'] ?? false));

        $request->session()->forget('admin_totp');

        $ctx = $this->authService->buildAdminSessionContext((int)$admin->id);
        if (!empty($ctx)) $request->session()->put('admin_ctx', $ctx);

        return redirect()->route('admin.home');
    }
}
