<?php

namespace App\Repositories\Member;

use App\Models\Member\MemAuth;
use App\Models\Member\MemAuthInfo;
use App\Models\Member\MemAuthLog;
use App\Models\Member\MemInfo;
use App\Models\Member\MemJoinFilter;
use App\Support\Legacy\Carrier;
use App\Services\SmsService;
use App\Services\MemInfoService;
use App\Support\LegacyCrypto\CiSeedCrypto;
use App\Support\LegacyCrypto\CiPassword;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Carbon;


class MemberAuthRepository
{
    public function __construct(private readonly MemInfoService $memInfoService)
    {
    }

    /* =========================================================
     * mem_auth (기존)
     * ========================================================= */

    public function upsertState(
        int $memNo,
        string $authType,
        string $authState,
        ?string $authDate = null
    ): void {
        $authDate = $authDate ?: Carbon::now()->toDateString();

        DB::table('mem_auth')->updateOrInsert(
            ['mem_no' => $memNo, 'auth_type' => $authType],
            ['auth_state' => $authState, 'auth_date' => $authDate]
        );
    }

    public function markRequested(int $memNo, string $authType, array $logInfo = []): void
    {
        $this->setStateWithLog($memNo, $authType, MemAuth::STATE_R, MemAuthLog::STATE_P, $logInfo);
    }

    public function markProcessing(int $memNo, string $authType, array $logInfo = []): void
    {
        $this->setStateWithLog($memNo, $authType, MemAuth::STATE_P, MemAuthLog::STATE_P, $logInfo);
    }

    public function markSuccess(int $memNo, string $authType, array $logInfo = []): void
    {
        $this->setStateWithLog($memNo, $authType, MemAuth::STATE_Y, MemAuthLog::STATE_S, $logInfo);
    }

    public function markFail(int $memNo, string $authType, array $logInfo = []): void
    {
        $this->setStateWithLog($memNo, $authType, MemAuth::STATE_N, MemAuthLog::STATE_F, $logInfo);
    }

    public function mergeAuthInfo(int $memNo, string $authType, array $payload): void
    {
        DB::transaction(function () use ($memNo, $authType, $payload) {
            $row = MemAuthInfo::query()->find($memNo);

            if (!$row) {
                $row = new MemAuthInfo();
                $row->mem_no = $memNo;
                $row->auth_info = [];
            }

            $data = $row->auth_info ?: [];
            $data[$authType] = array_merge($data[$authType] ?? [], $payload);

            $row->auth_info = $data;
            $row->save();
        });
    }

    private function setStateWithLog(
        int $memNo,
        string $authType,
        string $authState,
        string $logState,
        array $logInfo
    ): void {
        DB::transaction(function () use ($memNo, $authType, $authState, $logState, $logInfo) {
            $this->upsertState($memNo, $authType, $authState);

            MemAuthLog::query()->create([
                'mem_no' => $memNo,
                'type'   => $authType,
                'state'  => $logState,
                'info'   => $logInfo,
                'rgdate' => Carbon::now()->toDateTimeString(),
            ]);
        });
    }

    public function getState(int $memNo, string $authType): ?string
    {
        return DB::table('mem_auth')
            ->where('mem_no', $memNo)
            ->where('auth_type', $authType)
            ->value('auth_state');
    }

    private function normalizeKoreanPhone(string $raw): ?string
    {
        $digits = preg_replace('/\D+/', '', $raw ?? '');

        if (strlen($digits) !== 11) {
            return null;
        }
        if (substr($digits, 0, 3) !== '010') {
            return null;
        }

        return $digits; // ✅ 무조건 11자리 숫자만 리턴
    }

    /**
     * filter 컬럼에 phone/ip/ip_c가 들어있다는 전제의 기본 구현.
     * - join_block: A 차단 / S 주의(알림) / N 비활성
     */
    public function checkJoinFilter(string $phone, string $ip4 = '', string $ip4c = ''): ?array
    {
        $targets = array_values(array_filter([$phone, $ip4, $ip4c]));
        if (!$targets) return null;

        $rows = MemJoinFilter::query()
            ->whereIn('filter', $targets)
            ->where(function ($q) {
                $q->whereNull('join_block')->orWhere('join_block', '!=', 'N');
            })
            ->orderByDesc('seq')
            ->get();

        if ($rows->isEmpty()) return null;

        foreach ($rows as $r) {
            if ((string)$r->join_block === 'A') {
                return ['hit' => true, 'block' => true, 'gubun' => $r->gubun ?? 'filter_block', 'row' => $r];
            }
        }
        foreach ($rows as $r) {
            if ((string)$r->join_block === 'S') {
                return ['hit' => true, 'block' => false, 'gubun' => $r->gubun ?? 'filter_notice', 'row' => $r];
            }
        }

        $r = $rows->first();
        return ['hit' => true, 'block' => false, 'gubun' => $r->gubun ?? 'filter_hit', 'row' => $r];
    }

    /**
     * Step0 통합 처리
     */
    public function step0PhoneCheck(string $rawPhone, string $carrier = '', string $ip4 = ''): array
    {
        $base = [
            'ok' => false,
            'reason' => '',
            'message' => '',
            'redirect' => null,
            'phone' => null,
            'carrier' => null,
            'filter' => null,
            'admin_phones' => [],
        ];

        // 0) IP 필터 먼저
        $ip4c = $this->ipToCClass($ip4);
        $ipHit = $this->checkJoinFilterByIp($ip4, $ip4c);

        $carrier = trim($carrier);
        $base['carrier'] = $carrier ?: null;

        if ($ipHit) {
            $base['filter'] = $ipHit;
            $base['admin_phones'] = $ipHit['admin_phones'] ?? [];
        }

        if ($ipHit && ($ipHit['join_block'] ?? '') === 'A') {
            return array_merge($base, [
                'ok' => false,
                'reason' => 'blocked_ip',
                'message' => '현재 가입이 제한된 정보입니다. 고객센터로 문의해 주세요.',
            ]);
        }

        // 1) 전화번호 검증
        $phone = $this->normalizeKoreanPhone($rawPhone);
        if (!$phone) {
            return array_merge($base, [
                'ok' => false,
                'reason' => 'invalid_phone',
                'message' => '휴대폰 번호 형식이 올바르지 않습니다.',
            ]);
        }
        $base['phone'] = $phone;

        // 2) 이미 회원인지 체크
        $member = $this->findMemberByPhone($phone, $carrier);
        if ($member && !empty($member->mem_no)) {
            return array_merge($base, [
                'ok' => false,
                'reason' => 'already_member',
                'message' => "이미 가입된 전화번호 입니다.\n\n아이디 찾기로 이동할까요?",
                'redirect' => route('web.auth.find_id'),
                'matched_mem_no' => (int) $member->mem_no,
                'matched_cell_corp' => $member->cell_corp ?? null, // ✅ 필요시
            ]);
        }

        // 3) 기존 phone+ip 필터
        $filter = $this->checkJoinFilter($phone, $ip4, $ip4c);
        if ($filter && ($filter['block'] ?? false) === true) {
            return array_merge($base, [
                'ok' => false,
                'reason' => 'blocked',
                'filter' => $filter,
                'message' => '현재 가입이 제한된 정보입니다. 고객센터로 문의해 주세요.',
            ]);
        }

        // 4) 성공(가입 가능)
        return array_merge($base, [
            'ok' => true,
            'reason' => 'ok',
            'filter' => $filter ?: $ipHit,
            'message' => "회원가입 가능한 전화번호 입니다.\n\n약관동의 페이지로 이동합니다.",
            'redirect' => route('web.auth.register.terms'),
        ]);
    }

    private function findMemberByPhone(string $phone, string $carrier): ?object
    {
        /** @var CiSeedCrypto $seed */
        $seed = app(CiSeedCrypto::class);
        $phoneEnc = $seed->encrypt($phone);

        // 실제로 매칭되는 회원 1건을 가져옴
        return DB::table('mem_info')
            ->select('mem_no', 'cell_phone')
            ->where('cell_phone', $phoneEnc)
            ->where('cell_corp', $carrier)
            ->limit(1)
            ->first();
    }

    public function existsEmail(string $email): bool
    {
        $email = trim($email);
        if ($email === '') return false;

        return DB::table('mem_info')
            ->where('email', $email) // ✅ mem_info.email
            ->exists();
    }


    public function ipToCClass(string $ip): string
    {
        if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
            return '';
        }
        $p = explode('.', $ip);
        return count($p) === 4 ? ($p[0] . '.' . $p[1] . '.' . $p[2] . '.0') : '';
    }

    private function checkJoinFilterByIp(string $ip4, string $ip4c): ?array
    {
        // IPv4 아니면 필터 적용 안 함
        if (!filter_var($ip4, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
            return null;
        }

        // 우선순위: A(차단) > S(관리자알림) > N(무시)
        $row = DB::table('mem_join_filter')
            ->whereIn('join_block', ['A', 'S'])
            ->where(function ($q) use ($ip4, $ip4c) {
                // ✅ C class (보통 gubun_code='01', filter='162.168.0.0')
                $q->where(function ($q2) use ($ip4c) {
                    $q2->where('gubun_code', '01')
                        ->where('filter', $ip4c);
                });

                // ✅ D class (보통 gubun_code='02', filter='162.168.0.2')
                $q->orWhere(function ($q2) use ($ip4) {
                    $q2->where('gubun_code', '02')
                        ->where('filter', $ip4);
                });

                // ✅ (레거시) gubun_code가 01인데도 filter에 단일 IP가 들어있는 케이스 방어
                $q->orWhere(function ($q2) use ($ip4) {
                    $q2->where('gubun_code', '01')
                        ->where('filter', $ip4);
                });
            })
            ->orderByRaw("CASE join_block WHEN 'A' THEN 0 WHEN 'S' THEN 1 ELSE 9 END")
            ->limit(1)
            ->first();

        if (!$row) return null;

        $adminPhones = $this->parseAdminPhones($row->admin_phone ?? '');

        return [
            'gubun_code' => $row->gubun_code ?? null,
            'join_block' => $row->join_block ?? null,
            'filter' => $row->filter ?? null,
            'admin_phones' => $adminPhones,
            'ip' => $ip4,
            'ip4c' => $ip4c,
        ];
    }

    private function parseAdminPhones($value): array
    {
        // admin_phone: ["010-3682-8958", ...] 형태(=JSON 배열)라고 했으니 JSON 우선
        if (is_array($value)) return $value;

        $s = trim((string)$value);
        if ($s === '') return [];

        $json = json_decode($s, true);
        if (json_last_error() === JSON_ERROR_NONE && is_array($json)) {
            return array_values(array_filter(array_map('trim', $json)));
        }

        // 혹시 "010...,010..." 같은 문자열이면 콤마 분리 fallback
        $parts = array_map('trim', explode(',', $s));
        return array_values(array_filter($parts));
    }

    public function createMemberFromSignup(array $final, array $sessionAll = []): array
    {
        // 0) 필수값 최소 체크(컨트롤러에서 검증했지만 방어)
        $email = strtolower(trim((string)($final['email'] ?? '')));
        $pwPlain = (string)($final['password_plain'] ?? '');
        $pin2 = (string)($final['pin2_plain'] ?? '');

        if ($email === '' || $pwPlain === '' || !preg_match('/^\d{4}$/', $pin2)) {
            throw new \RuntimeException('invalid_payload');
        }

        $pass = (array)($final['pass'] ?? []);
        $name = (string)($pass['name'] ?? '');
        $carrier = Carrier::toCode((string)($pass['carrier'] ?? 'n')); //통신사 코드로 변경
        $phone = preg_replace('/\D+/', '', (string)($pass['phone'] ?? ''));
        $ci = $pass['ci'] ?? null;
        $di = $pass['di'] ?? null;

        // DOB: YYYYMMDD -> YYYY-MM-DD
        $dob = preg_replace('/\D+/', '', (string)($pass['dob'] ?? ''));
        $birth = '0000-00-00';
        if (strlen($dob) >= 8) {
            $birth = substr($dob, 0, 4).'-'.substr($dob, 4, 2).'-'.substr($dob, 6, 2);
        }

        // foreigner/native/country
        $foreigner = (string)($pass['foreigner'] ?? '0'); // 값 정의는 네 PASS 규격대로
        $native = ($foreigner === '1') ? '2' : '1'; //  외국인=2, 내국인=1
        $countryCode = ($native === '1') ? '82' : '';
        $countryName = ($native === '1') ? 'Republic of Korea(대한민국)' : '';

        // gender mapping (프로젝트 규칙에 맞춰 조정)
        $sex = (string)($pass['sex'] ?? '');
        $gender = match (strtoupper($sex)) {
            '1', 'M', 'MALE' => '1',
            '0', 'F', 'FEMALE', '2' => '0',
            default => '0',
        };

        $ip = (string)($final['meta']['ip'] ?? request()->ip());
        $promotion = (string)(data_get($sessionAll, 'signup.promotion', '')) === 'on';


        return DB::transaction(function () use (
            $email, $pwPlain, $pin2,
            $name, $birth, $carrier, $phone,
            $native, $countryCode, $countryName, $gender,
            $ci, $di, $ip, $promotion, $sessionAll
        ) {
            // 1) 접근금지회원 체크(간단 버전)
            //   prohibit_access() 정확한 조건이 있으면 그 조건대로 바꾸면 됨)
            if (!empty($ci)) {
                $blocked = DB::table('mem_info')
                    ->where('ci', $ci)
                    ->whereIn('stat_3', ['3','4','5'])
                    ->exists();

                if ($blocked) {
                    throw new \RuntimeException('prohibit_access');
                }
            }

            // 2) mem_info 생성 (휴대폰 암호화 포함은 MemInfoService::register가 처리)
            $mem = $this->memInfoService->register([
                'email' => $email,
                'name'  => $name,
                'pv_sns' => 'self',
                'promotion' => $promotion,
                'ip_reg' => $ip,
                'country_code' => $countryCode,
                'country_name' => $countryName,
                'birth' => $birth,
                'cell_corp' => $carrier,
                'cell_phone' => $phone,      // 평문 → register()에서 암호화
                'native' => $native,
                'gender' => $gender,
                'ci' => $ci,
                'di' => $di,
                'ci_v' => '1',
            ]);

            $memNo = (int)$mem->mem_no;
            $now = Carbon::now()->format('Y-m-d H:i:s');

            //다날 로그 업데이트
            $danalLogSeq = (int) data_get($sessionAll, 'register.danal_log_seq', 0);
            if ($danalLogSeq > 0) {
                $this->updateDanalAuthLogMemNo($danalLogSeq, $memNo);
            }

            /*회원가입 아이피 필터 업데이트*/
            $ipfResult = (string) data_get($sessionAll, 'signup.ipf_result', '');
            $ipfSeq    = (int) data_get($sessionAll, 'signup.ipf_seq', 0);

            if ($ipfResult === 'S' && $ipfSeq > 0) {
                $this->updateJoinLogAfterSignup($ipfSeq, $memNo, $email);
            }
            /*회원가입 아이피 필터 업데이트*/

            // 3) mem_st_ring 비번 저장 (CI3 macro->pass + sha512(pin2)와 동일)
            [$str0, $str1, $str2] = CiPassword::makeAll($pwPlain);
            $passwd2 = CiPassword::makePass2($pin2);

            DB::statement(
                "INSERT INTO mem_st_ring (mem_no, str_0, str_1, str_2, dt_reg, passwd2, passwd2_reg)
             VALUES (?, ?, ?, ?, ?, ?, ?)
             ON DUPLICATE KEY UPDATE str_0=VALUES(str_0), str_1=VALUES(str_1), str_2=VALUES(str_2),
                                     dt_reg=VALUES(dt_reg), passwd2=VALUES(passwd2), passwd2_reg=VALUES(passwd2_reg)",
                [$memNo, $str0, $str1, $str2, $now, $passwd2, $now]
            );

            // 4) mem_auth 휴대폰 인증 처리
            DB::table('mem_auth')->updateOrInsert(
                ['mem_no' => $memNo, 'auth_type' => 'cell'],
                ['auth_state' => 'Y', 'auth_date' => date('Y-m-d')]
            );

            // 5) mem_auth_log 가입 로그 저장 (CI3처럼 세션 스냅샷)
            DB::table('mem_auth_log')->insert([
                'mem_no' => $memNo,
                'type'   => 'signup',
                'state'  => 'S',
                'info'   => json_encode($sessionAll, JSON_UNESCAPED_UNICODE),
                'rgdate' => $now,
            ]);

            // 6) (TODO) email_auth init + 메일 발송은 다음 단계에서 연결
            //    지금은 우선 회원 저장까지 완성

            return ['ok' => true, 'mem_no' => $memNo];
        });
    }

    public function insertDanalAuthLog(string $gubun, array $res): int
    {
        // gubun: 'J' (회원가입), 'M'(정보수정)
        try {
            return (int) DB::table('mem_danalauthtel_log')->insertGetId([
                'gubun'    => $gubun,
                'TID'      => (string)($res['TID'] ?? ''),
                'res_code' => (string)($res['RETURNCODE'] ?? ''),
                'mem_no'   => null,
                'info'     => json_encode($res, JSON_UNESCAPED_UNICODE),
                'rgdate'   => now()->format('Y-m-d H:i:s'),
            ]);
        } catch (\Throwable $e) {
            Log::error('[danal] insert log failed', [
                'err' => $e->getMessage(),
            ]);
            return 0; // 로그 실패해도 플로우는 계속
        }
    }

    public function updateDanalAuthLogMemNo(int $logSeq, int $memNo): void
    {
        if ($logSeq <= 0 || $memNo <= 0) return;

        try {
            DB::table('mem_danalauthtel_log')
                ->where('seq', $logSeq)
                ->update(['mem_no' => $memNo]);
        } catch (\Throwable $e) {
            Log::error('[danal] update log mem_no failed', [
                'seq' => $logSeq,
                'mem_no' => $memNo,
                'err' => $e->getMessage(),
            ]);
            // 여기서 throw 할지 말지는 정책인데,
            // 레거시 흐름대로면 "가입은 살리고 로그만 실패"가 맞음.
        }
    }

    public function precheckJoinIpFilterAndLog(array $userInfo): array
    {
        // $userInfo keys:
        // mem_no(없으면 0), cell_corp, cell_phone(암호화), email('-'), ip4, ip4_c(앞 3옥텟), dt_reg(optional)

        $ip4   = (string)($userInfo['ip4'] ?? '');
        $ip4c  = (string)($userInfo['ip4_c'] ?? '');
        if ($ip4 === '' || $ip4c === '') {
            return ['result' => 'P', 'seq' => 0, 'admin_phones' => []];
        }

        // join_block 우선순위 A > S
        $row = DB::table('mem_join_filter')
            ->whereIn('join_block', ['A', 'S'])
            ->whereIn('gubun_code', ['01', '02'])
            ->where(function ($q) use ($ip4, $ip4c) {
                $q->where('filter', $ip4c)
                    ->orWhere('filter', $ip4);
            })
            ->orderByRaw("CASE join_block WHEN 'A' THEN 0 WHEN 'S' THEN 1 ELSE 9 END")
            ->orderByDesc('seq')
            ->first();

        if (!$row) {
            return ['result' => 'P', 'seq' => 0, 'admin_phones' => []];
        }

        $result = (string)($row->join_block ?? 'P'); // 'A' or 'S'
        $gubun  = (string)($row->gubun_code ?? '');

        // admin_phone JSON decode
        $adminPhones = [];
        $raw = $row->admin_phone ?? null;
        if (is_string($raw) && $raw !== '') {
            $j = json_decode($raw, true);
            if (json_last_error() === JSON_ERROR_NONE && is_array($j)) {
                $adminPhones = array_values(array_filter(array_map('trim', $j)));
            }
        }

        // 관리자 SMS (S만 보낼지, A도 보낼지는 정책인데)
        // CI3 코드는 S에서만 발송했지만, 주석/정책상 A도 발송하는게 더 안전해서 A도 발송하도록 권장.
        if (in_array($result, ['S','A'], true) && !empty($adminPhones)) {
            foreach ($adminPhones as $phone) {
                $smsPayload = [
                    'from_number' => config('services.sms.from', '1833-4856'),
                    'to_number'   => $phone,
                    'message'     => '[PIN FOR YOU] 회원가입필터 IP에서 가입 시도됨! 관리자 확인요망 '.date('m-d H:i'),
                    'sms_type'    => 'sms',
                ];
                app(SmsService::class)->send($smsPayload, 'lguplus');
            }
        }

        // ✅ mem_join_log 기록 (CI3: S 또는 A 일때만 기록)
        $seq = (int) DB::table('mem_join_log')->insertGetId([
            'gubun'      => $gubun,                                    // CI3: gubun_code를 gubun에 저장
            'mem_no'     => (int)($userInfo['mem_no'] ?? 0),           // 가입 전이면 0
            'cell_corp'  => (string)($userInfo['cell_corp'] ?? 'n'),
            'cell_phone' => (string)($userInfo['cell_phone'] ?? ''),
            'email'      => (string)($userInfo['email'] ?? '-'),
            'ip4'        => $ip4,
            'ip4_c'      => $ip4c,
            'error_code' => $result,                                   // ✅ join_block을 error_code에 저장(추적용)
            'dt_reg'     => $userInfo['dt_reg'] ?? date('Y-m-d H:i:s'),
        ]);

        return [
            'result' => $result,               // 'A' or 'S'
            'seq' => $seq,
            'admin_phones' => $adminPhones,
        ];
    }

    /**
     * 가입 성공 후 mem_no/email 업데이트 (CI3 ip_check_update 대응)
     */
    public function updateJoinLogAfterSignup(int $seq, int $memNo, string $email): void
    {
        DB::table('mem_join_log')
            ->where('seq', $seq)
            ->update([
                'mem_no' => $memNo,
                'email'  => $email,
            ]);
    }

    public function findByEmailAndName(string $emailLower, string $name): ?MemInfo
    {
        // ⚠️ 성명 컬럼이 name이 아니면 여기만 바꾸면 됨
        return MemInfo::query()
            ->whereNotNull('email')->where('email', '<>', '')
            ->whereRaw('LOWER(email) = ?', [$emailLower])
            ->where('name', $name)
            ->orderByDesc('mem_no')
            ->first();
    }

    public function findByMemNo(int $memNo): ?MemInfo
    {
        return MemInfo::query()
            ->where('mem_no', $memNo)
            ->first();
    }

    public function existsByEncryptedPhone(string $phoneEnc): bool
    {
        return DB::table('mem_info')
            ->whereNotNull('email')
            ->where('email', '<>', '')
            ->where('cell_phone', $phoneEnc)
            ->exists();
    }

    /**
     * 해당 암호화 휴대폰으로 가입된 이메일 목록(중복 제거)
     */
    public function getEmailsByEncryptedPhone(string $phoneEnc): array
    {
        // 여러 mem_no에서 같은 email이 나올 수 있으니 distinct 처리
        return DB::table('mem_info')
            ->select('email')
            ->whereNotNull('email')
            ->where('email', '<>', '')
            ->where('cell_phone', $phoneEnc)
            ->orderByDesc('mem_no')
            ->distinct()
            ->pluck('email')
            ->values()
            ->all();
    }

    //비밀번호 수정
    public function updatePasswordOnly(int $memNo, string $pwPlain): void
    {
        if ($memNo <= 0 || $pwPlain === '') {
            throw new \InvalidArgumentException('invalid_payload');
        }

        [$str0, $str1, $str2] = CiPassword::makeAll($pwPlain);
        $now = now()->format('Y-m-d H:i:s');

        $affected = DB::table('mem_st_ring')
            ->where('mem_no', $memNo)
            ->update([
                'str_0' => $str0,
                'str_1' => $str1,
                'str_2' => $str2,
                'dt_reg' => $now,
            ]);

        // 기존회원인데 mem_st_ring row가 없다? 이건 데이터 이상으로 보고 명확히 실패 처리
        if ($affected <= 0) {
            throw new \RuntimeException('mem_st_ring_not_found');
        }
    }

    //회원 탈퇴 1차 비밀번호 검증
    public function verifyLegacyPassword(int $memNo, string $pwPlain): bool
    {
        $pwPlain = (string) $pwPlain;

        if ($memNo <= 0 || $pwPlain === '') {
            return false;
        }

        // ✅ 1차 비번은 mem_st_ring.str_0
        $stored = (string) (DB::table('mem_st_ring')
            ->where('mem_no', $memNo)
            ->value('str_0') ?? '');

        if ($stored === '') {
            return false;
        }

        // ✅ CI 방식(PASS_SET=0) - 기존 attemptLegacyLogin과 동일
        $try = (string) CiPassword::make($pwPlain, 0);
        if ($try === '') {
            return false;
        }

        return hash_equals($stored, $try);
    }

    // 2차 비밀번호 검증
    public function verifyPin2(int $memNo, string $pin2Plain): bool
    {
        if ($memNo <= 0) return false;

        // 4자리 숫자만 허용
        if (!preg_match('/^\d{4}$/', $pin2Plain)) return false;

        $stored = (string) DB::table('mem_st_ring')
            ->where('mem_no', $memNo)
            ->value('passwd2');

        if ($stored === '') return false;

        $hash = CiPassword::makePass2($pin2Plain);

        return hash_equals($stored, $hash);
    }


    // 2차 비밀번호 수정
    public function updatePin2Only(int $memNo, string $pin2Plain): void
    {
        if ($memNo <= 0 || $pin2Plain === '') {
            throw new \InvalidArgumentException('invalid_payload');
        }

        // 4자리 숫자 강제 (컨트롤러에서도 validate 하지만 2중 방어)
        if (!preg_match('/^\d{4}$/', $pin2Plain)) {
            throw new \InvalidArgumentException('invalid_pin2');
        }

        $passwd2 = CiPassword::makePass2($pin2Plain);
        $now = now()->format('Y-m-d H:i:s');

        // mem_st_ring 존재 전제: 없으면 데이터 이상으로 실패 처리(비번 변경과 동일 정책)
        $affected = DB::table('mem_st_ring')
            ->where('mem_no', $memNo)
            ->update([
                'passwd2' => $passwd2,
                'passwd2_reg' => $now,
            ]);

        if ($affected <= 0) {
            throw new \RuntimeException('mem_st_ring_not_found');
        }
    }


    public function logPasswordResetSuccess(int $memNo, string $ip, string $agent, string $state = 'E'): void
    {
        $now = now()->format('Y-m-d H:i:s');

        // state: E=비밀번호찾기 변경, S=직접 변경
        $state = strtoupper(trim($state));
        if (!in_array($state, ['E', 'S'], true)) {
            $state = 'E';
        }

        DB::table('mem_passwd_modify')->insert([
            'state'  => $state,
            'info'   => json_encode([
                'mem_no' => (string)$memNo,
                'redate' => $now,
                'remote_addr' => $ip,
                'agent' => substr((string)$agent, 0, 500), // 길이 방어(일관 처리)
            ], JSON_UNESCAPED_UNICODE),
            'rgdate' => $now,
        ]);
    }



}



