<?php

namespace App\Services;

use App\Repositories\Member\MemberAuthRepository;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\Log;

class FindPasswordService
{
    public function __construct(
        private readonly MemberAuthRepository $members,
        private readonly MailService $mail,
    ) {}

    /**
     * 이메일+성명 검증 후 재설정 링크 메일 발송
     * - DB 조회는 여기서 처리
     * - 컨트롤러는 결과만 받아서 JSON 응답
     */
    public function sendResetMail(Request $request, string $email, string $name, int $expiresMinutes = 30): array
    {
        $emailLower = mb_strtolower(trim($email));
        $name = trim($name);

        // 레이트리밋 (email+ip) - DB 아니지만 정책/로직이므로 서비스에서 처리
        $key = 'findpw:mail:' . $emailLower . ':' . (string)$request->ip();
        if (RateLimiter::tooManyAttempts($key, 5)) {
            $sec = RateLimiter::availableIn($key);
            return [
                'ok' => false,
                'status' => 429,
                'message' => "요청이 너무 많습니다. {$sec}초 후 다시 시도해 주세요.",
            ];
        }
        RateLimiter::hit($key, 600);

        // ✅ 가입자 확인(DB)
        $member = $this->members->findByEmailAndName($emailLower, $name);
        if (!$member) {
            return [
                'ok' => false,
                'status' => 404,
                'code' => 'NOT_MATCHED',
                'message' => '입력하신 이메일/성명 정보가 일치하지 않습니다.',
                'step' => 1,
            ];
        }

        $memNo = (int)$member->mem_no;

        // ✅ DB 저장 없이 signed URL만 생성
        $nonce = bin2hex(random_bytes(10));

        $link = URL::temporarySignedRoute(
            'web.auth.find_password.verify',
            now()->addMinutes($expiresMinutes),
            [
                'mem_no' => $memNo,
                'n'      => $nonce,
            ]
        );

        // ✅ 메일 발송
        try {
            $this->mail->sendTemplate(
                $emailLower,
                '[PIN FOR YOU] 비밀번호 재설정 링크 안내',
                'mail.auth.reset_password',
                [
                    'email' => $emailLower,
                    'name' => $name,
                    'link' => $link,
                    'expires_min' => $expiresMinutes,
                    'accent' => '#E4574B',
                    'brand' => 'PIN FOR YOU',
                    'siteUrl' => config('app.url'),
                ],
                queue: false
            );
        } catch (\Throwable $e) {
            Log::error('FindPassword sendResetMail failed', [
                'mem_no' => $memNo,
                'email' => $emailLower,
                'error' => $e->getMessage(),
            ]);

            return [
                'ok' => false,
                'status' => 500,
                'message' => '메일 발송 중 오류가 발생했습니다.',
            ];
        }

        // 컨트롤러가 세션에 저장할 payload를 서비스에서 만들어줌
        return [
            'ok' => true,
            'status' => 200,
            'message' => '재설정 메일을 발송했습니다. 메일함을 확인해 주세요.',
            'expires_in' => $expiresMinutes * 60,
            'step' => 2,
            'session' => [
                'sent' => true,
                'verified' => false,
                'mem_no' => $memNo,
                'email' => $emailLower,
                'name'  => $name,
                'sent_at' => now()->timestamp,
            ],
        ];
    }

    /**
     * 링크 클릭 검증(서명/만료 + 회원존재)
     * - DB 조회는 여기서 처리
     * - 컨트롤러는 redirect만 담당
     */
    public function verifyResetLink(Request $request, int $memNo): array
    {
        // signed 미들웨어를 라우트에 붙이면 여기 체크는 사실상 안전장치
        if (!$request->hasValidSignature()) {
            return [
                'ok' => false,
                'message' => '링크가 유효하지 않거나 만료되었습니다. 다시 진행해 주세요.',
            ];
        }

        if ($memNo <= 0) {
            return [
                'ok' => false,
                'message' => '잘못된 접근입니다.',
            ];
        }

        $member = $this->members->findByMemNo($memNo);
        if (!$member) {
            return [
                'ok' => false,
                'message' => '계정을 찾을 수 없습니다. 다시 진행해 주세요.',
            ];
        }

        return [
            'ok' => true,
            'message' => '인증이 완료되었습니다. 새 비밀번호를 설정해 주세요.',
            'session' => [
                'sent' => true,
                'verified' => true,
                'verified_at' => now()->timestamp,
                'mem_no' => $memNo,
                'email' => (string)($member->email ?? ''),
                'name'  => (string)($member->name ?? ''),
            ],
        ];
    }

    public function resetPasswordFinal(Request $request, string $newPassword): array
    {
        $sess = (array) $request->session()->get('find_pw', []);

        // 0) 세션 체크: 이메일 링크 인증 완료 상태인지
        if (empty($sess['verified']) || empty($sess['mem_no'])) {
            return [
                'ok' => false,
                'status' => 403,
                'message' => '이메일 인증이 필요합니다. 메일 링크를 통해 다시 진행해 주세요.',
            ];
        }

        $memNo = (int) ($sess['mem_no'] ?? 0);
        if ($memNo <= 0) {
            $request->session()->forget('find_pw');
            return [
                'ok' => false,
                'status' => 403,
                'message' => '세션 정보가 올바르지 않습니다. 다시 진행해 주세요.',
            ];
        }

        // 1) 인증 후 유효시간 정책 (예: 인증 후 10분 내 변경)
        $verifiedAt = (int) ($sess['verified_at'] ?? 0);
        if ($verifiedAt <= 0 || (now()->timestamp - $verifiedAt) > (10 * 60)) {
            $request->session()->forget('find_pw');
            return [
                'ok' => false,
                'status' => 403,
                'message' => '인증이 만료되었습니다. 비밀번호 찾기를 다시 진행해 주세요.',
            ];
        }

        // 2) 레이트리밋 (비번 변경 시도)
        $key = 'findpw:reset:' . $memNo . ':' . (string)$request->ip();
        if (RateLimiter::tooManyAttempts($key, 10)) {
            $sec = RateLimiter::availableIn($key);
            return [
                'ok' => false,
                'status' => 429,
                'message' => "시도 횟수가 많습니다. {$sec}초 후 다시 시도해 주세요.",
            ];
        }
        RateLimiter::hit($key, 600);

        // 3) 방어적 비번 정책 체크(컨트롤러에서 검증하지만 한번 더)
        if (!is_string($newPassword) || $newPassword === '') {
            return ['ok'=>false,'status'=>422,'message'=>'새 비밀번호를 입력해 주세요.'];
        }
        if (strlen($newPassword) < 8 || strlen($newPassword) > 20) {
            return ['ok'=>false,'status'=>422,'message'=>'비밀번호는 8~20자리로 입력해 주세요.'];
        }
        if (!preg_match('/^(?=.*[A-Za-z])(?=.*\d)(?=.*[^A-Za-z0-9\s]).+$/', $newPassword)) {
            return ['ok'=>false,'status'=>422,'message'=>'비밀번호는 영문+숫자+특수문자를 포함해야 합니다.'];
        }

        // 4) 회원 존재 확인 (DB는 Repo 통해서)
        $member = $this->members->findByMemNo($memNo);
        if (!$member) {
            $request->session()->forget('find_pw');
            return [
                'ok' => false,
                'status' => 404,
                'message' => '계정을 찾을 수 없습니다. 다시 진행해 주세요.',
            ];
        }

        // 5) 비밀번호 저장 (mem_st_ring str_0~2) + 성공 로그
        $this->members->updatePasswordOnly($memNo, $newPassword);
        $this->members->logPasswordResetSuccess(
            $memNo,
            (string) $request->ip(),
            (string) $request->userAgent(),
            'E'
        );


        // 6) 세션 정리
        $request->session()->forget('find_pw');
        $request->session()->save();

        return [
            'ok' => true,
            'status' => 200,
            'message' => '비밀번호가 변경되었습니다. 로그인해 주세요.',
            'redirect_url' => route('web.auth.login'),
        ];
    }
}
