<?php

namespace App\Services\Admin\Member;

use App\Repositories\Admin\Member\AdminMemberRepository;
use App\Repositories\Admin\AdminUserRepository;
use App\Repositories\Member\MemberAuthRepository;
use App\Services\MailService;
use App\Support\LegacyCrypto\CiSeedCrypto;
use Illuminate\Support\Facades\DB;
use App\Services\Admin\AdminAuditService;

final class AdminMemberService
{
    private const AUTH_TYPES_SHOW = ['email','cell','account','vow'];

    public function __construct(
        private readonly AdminMemberRepository $repo,
        private readonly AdminUserRepository $adminRepo,
        private readonly MemberAuthRepository $members,
        private readonly MailService $mail,
        private readonly AdminAuditService $audit,
    ) {}

    public function list(array $filters): array
    {
        // q가 휴대폰이면 동등검색(암호문 비교)
        $keyword = trim((string)($filters['q'] ?? ''));
        if ($keyword !== '') {
            $digits = preg_replace('/\D+/', '', $keyword) ?? '';
            if (preg_match('/^\d{10,11}$/', $digits)) {
                $phone = $this->normalizeKrPhone($digits);
                if ($phone !== '') {
                     $filters['phone_enc'] = $this->encryptPhone($phone);
                }
            }
        }
        $page = $this->repo->paginateMembers($filters, 20);

        $memNos = $page->getCollection()->pluck('mem_no')->map(fn($v)=>(int)$v)->all();
        $authMapAll = $this->repo->getAuthMapForMembers($memNos);

        $authMap = [];
        foreach ($authMapAll as $no => $types) {
            foreach (self::AUTH_TYPES_SHOW as $t) {
                if (isset($types[$t])) $authMap[(int)$no][$t] = $types[$t];
            }
        }

        // 표시용 맵
        $phoneMap = [];
        $corpMap = $this->corpMap();
        foreach ($page as $m) {
            $no = (int)$m->mem_no;
            $plain = $this->plainPhone((string)($m->cell_phone ?? ''));
            $phoneMap[$no] = $this->formatPhone($plain);
        }

        return [
            'page' => $page,
            'filters' => $filters,
            'authMap' => $authMap,

            'stat3Map' => $this->stat3Map(),
            'genderMap' => $this->genderMap(),
            'nativeMap' => $this->nativeMap(),
            'corpMap' => $corpMap,
            'phoneMap' => $phoneMap,
        ];
    }

    public function showData(int $memNo): array
    {
        $m = $this->repo->findMember($memNo);
        if (!$m) return ['member' => null];

        $member = (object)((array)$m);

        $corpLabel = $this->corpMap()[(string)($member->cell_corp ?? 'n')] ?? '-';
        $plainPhone = $this->plainPhone((string)($member->cell_phone ?? ''));
        $phoneDisplay = $this->formatPhone($plainPhone);

        // ✅ 레거시 JSON 파싱 (admin_memo / modify_log)
        $adminMemoList = $this->legacyAdminMemoList($member->admin_memo ?? null);      // old -> new normalize
        $stateLogList  = $this->legacyStateLogList($member->modify_log ?? null);       // old -> new normalize

        // 화면은 최신이 위로 오게
        $adminMemo = array_reverse($adminMemoList);
        $modifyLog = array_reverse($stateLogList);

        // ✅ adminMap 대상 admin_num 수집
        $adminSet = [];

        foreach ($adminMemo as $it) {
            $aid = (int)($it['admin_num'] ?? 0);
            if ($aid > 0) $adminSet[$aid] = true;
        }
        foreach ($modifyLog as $it) {
            $aid = (int)($it['admin_num'] ?? 0);
            if ($aid > 0) $adminSet[$aid] = true;
        }

        $adminIds = array_keys($adminSet);

        // 인증/주소/로그 (기존 그대로)
        $authRows = array_values(array_filter(
            $this->repo->getAuthRowsForMember($memNo),
            fn($r)=> in_array((string)($r['auth_type'] ?? ''), self::AUTH_TYPES_SHOW, true)
        ));

        $authInfo = $this->repo->getAuthInfo($memNo);
        $authLogs = $this->repo->getAuthLogs($memNo, 30);
        $addresses = $this->repo->getAddresses($memNo);

        // 계좌 표시(수정 불가 / 표시만)
        $bank = $this->buildBankDisplay($member, $authInfo);

        $adminMap = $this->adminRepo->getMetaMapByIds($adminIds);

        return [
            'member' => $member,
            'corpLabel' => $corpLabel,
            'plainPhone' => $plainPhone,
            'phoneDisplay' => $phoneDisplay,

            // ✅ 레거시 기반 결과
            'adminMemo' => $adminMemo,
            'modifyLog' => $modifyLog,
            'adminMap'  => $adminMap,

            // 기존 그대로
            'authRows' => $authRows,
            'authInfo' => $authInfo,
            'authLogs' => $authLogs,
            'addresses' => $addresses,

            'stat3Map' => $this->stat3Map(),
            'genderMap' => $this->genderMap(),
            'nativeMap' => $this->nativeMap(),
            'corpMap' => $this->corpMap(),
            'bank' => $bank,
        ];
    }


    /**
     * 업데이트 허용: stat_3(1~3만), cell_corp, cell_phone
     * 금지: 이름/이메일/수신동의/계좌/기타
     */
    public function updateMember(int $memNo, array $input, int $actorAdminId, string $ip = '', string $ua = ''): array
    {
        try {
            return DB::transaction(function () use ($memNo, $input, $actorAdminId, $ip, $ua) {

                $before = $this->repo->lockMemberForUpdate($memNo);
                if (!$before) return $this->fail('회원을 찾을 수 없습니다.');

                $now = now();
                $nowStr = $now->format('Y-m-d H:i:s');
                $legacyWhen = $now->format('y-m-d H:i:s');

                $data = [];

                // ✅ 기존 modify_log에서 레거시 state_log[] 뽑기
                $stateLog = $this->legacyStateLogList($before->modify_log ?? null);
                $beforeStateLogLast = empty($stateLog) ? null : $stateLog[count($stateLog) - 1];

                // (audit용) before 스냅샷: 민감/대용량 제외 + phone 마스킹
                $beforePlainPhone = $this->plainPhone((string)($before->cell_phone ?? ''));
                $beforeAudit = [
                    'mem_no'     => $memNo,
                    'stat_3'     => (string)($before->stat_3 ?? ''),
                    'dt_stat_3'  => $before->dt_stat_3 ?? null,
                    'cell_corp'  => (string)($before->cell_corp ?? 'n'),
                    'cell_phone' => $this->maskPhoneForLog($beforePlainPhone),
                    'dt_mod'     => $before->dt_mod ?? null,
                    'state_log_last' => $beforeStateLogLast,
                ];

                // (audit용) after 스냅샷은 before에서 변경분만 덮어쓰기
                $afterAudit = $beforeAudit;

                // ✅ stat_3 변경 (1~3만 변경 허용, 4~6 금지 정책 유지)
                if (array_key_exists('stat_3', $input)) {
                    $s3 = (string)($input['stat_3'] ?? '');
                    if (!in_array($s3, ['1','2','3','4','5','6'], true)) {
                        return $this->fail('회원상태(stat_3)는 1~6만 유효합니다.');
                    }
                    if (in_array($s3, ['4','5','6'], true)) {
                        return $this->fail('4~6 상태는 시스템 상태로 관리자 변경이 불가합니다.');
                    }

                    $beforeS3 = (string)($before->stat_3 ?? '');
                    if ($s3 !== $beforeS3) {
                        $data['stat_3'] = $s3;
                        $data['dt_stat_3'] = $nowStr;

                        $stateLog[] = [
                            'when'      => $legacyWhen,
                            'after'     => $s3,
                            'title'     => '회원상태 접근권한 변경',
                            'before'    => $beforeS3,
                            'admin_num' => (string)$actorAdminId,
                        ];

                        $afterAudit['stat_3'] = $s3;
                        $afterAudit['dt_stat_3'] = $nowStr;
                    }
                }

                // ✅ 통신사 변경
                if (array_key_exists('cell_corp', $input)) {
                    $corp = (string)($input['cell_corp'] ?? 'n');
                    $allowed = ['n','01','02','03','04','05','06'];
                    if (!in_array($corp, $allowed, true)) return $this->fail('통신사 코드가 올바르지 않습니다.');

                    $beforeCorp = (string)($before->cell_corp ?? 'n');
                    if ($corp !== $beforeCorp) {
                        $data['cell_corp'] = $corp;

                        $stateLog[] = [
                            'when'      => $legacyWhen,
                            'after'     => $corp,
                            'title'     => '통신사 변경',
                            'before'    => $beforeCorp,
                            'admin_num' => (string)$actorAdminId,
                        ];

                        $afterAudit['cell_corp'] = $corp;
                    }
                }

                // ✅ 휴대폰 변경(암호화 저장)
                $afterPhoneMasked = null;
                if (array_key_exists('cell_phone', $input)) {
                    $raw = trim((string)($input['cell_phone'] ?? ''));

                    $enc = '';
                    $afterPlain = '';
                    if ($raw !== '') {
                        $phone = $this->normalizeKrPhone($raw);
                        if ($phone === '') return $this->fail('휴대폰 번호 형식이 올바르지 않습니다.');
                        $enc = $this->encryptPhone($phone);
                        $afterPlain = $phone;
                    }

                    $beforeEnc = (string)($before->cell_phone ?? '');
                    if ($beforeEnc !== $enc) {
                        $data['cell_phone'] = $enc;

                        // 로그는 마스킹(평문 full 저장 원하면 여기만 변경)
                        $stateLog[] = [
                            'when'      => $legacyWhen,
                            'after'     => $this->maskPhoneForLog($afterPlain),
                            'title'     => '휴대폰번호 변경',
                            'before'    => $this->maskPhoneForLog($beforePlainPhone),
                            'admin_num' => (string)$actorAdminId,
                        ];

                        $afterPhoneMasked = $this->maskPhoneForLog($afterPlain);
                        $afterAudit['cell_phone'] = $afterPhoneMasked;
                    }
                }

                if (empty($data)) {
                    return $this->ok('변경사항이 없습니다.');
                }

                // ✅ 레거시 modify_log 저장: 반드시 {"state_log":[...]}
                $stateLog = $this->trimLegacyList($stateLog, 300);
                $data['modify_log'] = $this->encodeJsonObjectOrNull(['state_log' => $stateLog]);

                // 최근정보변경일시
                $data['dt_mod'] = $nowStr;

                $ok = $this->repo->updateMember($memNo, $data);
                if (!$ok) return $this->fail('저장에 실패했습니다.');

                // ✅ audit log (update 성공 후, 같은 트랜잭션 안에서)
                $afterStateLogLast = empty($stateLog) ? null : $stateLog[count($stateLog) - 1];
                $afterAudit['dt_mod'] = $nowStr;
                $afterAudit['state_log_last'] = $afterStateLogLast;

                $this->audit->log(
                    actorAdminId: $actorAdminId,
                    action: 'admin.member.update',
                    targetType: 'member',
                    targetId: $memNo,
                    before: $beforeAudit,
                    after: $afterAudit,
                    ip: $ip,
                    ua: $ua,
                );

                return $this->ok('변경되었습니다.');
            });
        } catch (\Throwable $e) {
            return $this->fail('저장 중 오류가 발생했습니다. (DB)');
        }
    }



    // -------------------------
    // Maps
    // -------------------------
    private function stat3Map(): array
    {
        return [
            '1' => '1. 로그인정상',
            '2' => '2. 로그인만가능',
            '3' => '3. 로그인불가 (접근금지)',
            '4' => '4. 탈퇴완료(아이디보관)',
            '5' => '5. 탈퇴완료',
            '6' => '6. 휴면회원',
        ];
    }

    private function genderMap(): array
    {
        return ['1' => '남자', '0' => '여자', 'n' => '-'];
    }

    private function nativeMap(): array
    {
        return ['1' => '내국인', '2' => '외국인', 'n' => '-'];
    }

    private function corpMap(): array
    {
        return [
            'n'  => '-',
            '01' => 'SKT',
            '02' => 'KT',
            '03' => 'LGU+',
            '04' => 'SKT(알뜰폰)',
            '05' => 'KT(알뜰폰)',
            '06' => 'LGU+(알뜰폰)',
        ];
    }

    // -------------------------
    // Phone helpers
    // -------------------------
    private function normalizeKrPhone(string $raw): string
    {
        $n = preg_replace('/\D+/', '', $raw) ?? '';
        if ($n === '') return '';
        if (str_starts_with($n, '82')) $n = '0'.substr($n, 2);
        if (!preg_match('/^01[016789]\d{7,8}$/', $n)) return '';
        return $n;
    }

    private function encryptPhone(string $raw010): string
    {
        $seed = app(CiSeedCrypto::class);
        return (string) $seed->encrypt($raw010);
    }

    // DB 컬럼이 평문/암호문 섞여있을 수 있어 “plain digits”로 뽑아줌
    private function plainPhone(string $cellPhoneCol): string
    {
        $v = trim($cellPhoneCol);
        if ($v === '') return '';

        $digits = preg_replace('/\D+/', '', $v) ?? '';
        if (preg_match('/^\d{10,11}$/', $digits)) return $digits;

        try {
            $seed = app(CiSeedCrypto::class);
            $plain = (string) $seed->decrypt($v);
            $plainDigits = preg_replace('/\D+/', '', $plain) ?? '';
            if (preg_match('/^\d{10,11}$/', $plainDigits)) return $plainDigits;
        } catch (\Throwable $e) {
            // ignore
        }

        return '';
    }

    private function formatPhone(string $digits): string
    {
        if (!preg_match('/^\d{10,11}$/', $digits)) return '-';
        if (strlen($digits) === 11) {
            return substr($digits,0,3).'-'.substr($digits,3,4).'-'.substr($digits,7,4);
        }
        // 10자리
        return substr($digits,0,3).'-'.substr($digits,3,3).'-'.substr($digits,6,4);
    }

    // -------------------------
    // Bank display (readonly)
    // -------------------------
    private function buildBankDisplay(object $member, ?array $authInfo): array
    {
        $bankName = trim((string)($member->bank_name ?? ''));
        $acc = trim((string)($member->bank_act_num ?? ''));
        $holder = trim((string)($member->name ?? ''));

        // authInfo 쪽에 계좌인증 정보가 있으면 우선 사용(키는 프로젝트마다 다르니 방어적으로)
        if (is_array($authInfo)) {
            $maybe = $authInfo['account'] ?? $authInfo['bank'] ?? null;
            if (is_array($maybe)) {
                $bankName = trim((string)($maybe['bank_name'] ?? $maybe['bankName'] ?? $bankName));
                $acc = trim((string)($maybe['bank_act_num'] ?? $maybe['account'] ?? $maybe['account_number'] ?? $acc));
                $holder = trim((string)($maybe['holder'] ?? $maybe['name'] ?? $holder));
            }
        }

        $has = ($bankName !== '' && $acc !== '');
        $masked = $has ? $this->maskAccount($acc) : '';

        return [
            'has' => $has,
            'bank_name' => $bankName,
            'account' => $masked,
            'holder' => ($holder !== '' ? $holder : '-'),
        ];
    }

    private function maskAccount(string $acc): string
    {
        // 암호문/특수문자 섞여도 최소 마스킹은 해줌
        $d = preg_replace('/\s+/', '', $acc);
        if (mb_strlen($d) <= 4) return $d;
        return str_repeat('*', max(0, mb_strlen($d)-4)).mb_substr($d, -4);
    }

    public function addMemo(int $memNo, string $memo, int $actorAdminId, string $ip = '', string $ua = ''): array
    {
        $memo = trim($memo);
        if ($memo === '') return $this->fail('메모를 입력해 주세요.');
        if (mb_strlen($memo) > 1000) return $this->fail('메모는 1000자 이내로 입력해 주세요.');

        try {
            return DB::transaction(function () use ($memNo, $memo, $actorAdminId, $ip, $ua) {

                $before = $this->repo->lockMemberForUpdate($memNo);
                if (!$before) return $this->fail('회원을 찾을 수 없습니다.');

                $now = now();
                $nowStr = $now->format('Y-m-d H:i:s');
                $legacyWhen = $now->format('y-m-d H:i:s'); // 레거시(2자리년도)

                // ✅ 기존 admin_memo에서 레거시 memo[] 뽑기
                $list = $this->legacyAdminMemoList($before->admin_memo ?? null);
                $beforeCount = is_array($list) ? count($list) : 0;
                $beforeLast  = $beforeCount > 0 ? $list[$beforeCount - 1] : null;

                // ✅ append (레거시 키 유지)
                $newItem = [
                    'memo'      => $memo,
                    'when'      => $legacyWhen,
                    'admin_num' => (string)$actorAdminId,
                ];
                $list[] = $newItem;
                $list = $this->trimLegacyList($list, 300);

                // ✅ admin_memo는 반드시 {"memo":[...]} 형태
                $obj = ['memo' => $list];

                $data = [
                    'admin_memo' => $this->encodeJsonObjectOrNull($obj),
                    'dt_mod'     => $nowStr,
                ];

                $ok = $this->repo->updateMember($memNo, $data);
                if (!$ok) return $this->fail('메모 저장에 실패했습니다.');

                // ✅ audit (성공 후)
                $afterCount = count($list);
                $afterLast  = $afterCount > 0 ? $list[$afterCount - 1] : null;

                // 감사로그에는 메모 원문을 다 넣지 말고(유출/개인정보 위험),
                // "추가됨" + 길이/해시/마지막 항목 일부 정도만 남기는 게 안전함.
                $beforeAudit = [
                    'mem_no'         => $memNo,
                    'dt_mod'         => $before->dt_mod ?? null,
                    'admin_memo_cnt' => $beforeCount,
                    'admin_memo_last'=> $beforeLast,
                ];

                $afterAudit = [
                    'mem_no'         => $memNo,
                    'dt_mod'         => $nowStr,
                    'admin_memo_cnt' => $afterCount,
                    'admin_memo_last'=> $afterLast,
                    'added' => [
                        'when'      => $newItem['when'],
                        'admin_num' => $newItem['admin_num'],
                        'memo_len'  => mb_strlen($memo),
                        // 필요하면 아래처럼 해시만 남겨도 됨(원문 미저장)
                        // 'memo_sha256' => hash('sha256', $memo),
                    ],
                ];

                $this->audit->log(
                    actorAdminId: $actorAdminId,
                    action: 'admin.member.memo.add',
                    targetType: 'member',
                    targetId: $memNo,
                    before: $beforeAudit,
                    after: $afterAudit,
                    ip: $ip,
                    ua: $ua,
                );

                return $this->ok('메모가 추가되었습니다.');
            });
        } catch (\Throwable $e) {
            return $this->fail('메모 저장 중 오류가 발생했습니다. (DB)');
        }
    }




    private function ok(string $msg): array { return ['ok'=>true,'message'=>$msg]; }
    private function fail(string $msg): array { return ['ok'=>false,'message'=>$msg]; }

    private function decodeJsonObject($jsonOrNull): array
    {
        if ($jsonOrNull === null) return [];
        $s = trim((string)$jsonOrNull);
        if ($s === '') return [];
        $arr = json_decode($s, true);
        return is_array($arr) ? $arr : [];
    }

    private function encodeJsonObjectOrNull(array $obj): ?string
    {
        if (empty($obj)) return null;
        return json_encode($obj, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    }

// ✅ admin_memo: {"memo":[{memo,when,admin_num}, ...]}
    private function legacyAdminMemoList($adminMemoJson): array
    {
        $obj = $this->decodeJsonObject($adminMemoJson);

        if (isset($obj['memo']) && is_array($obj['memo'])) {
            return $obj['memo'];
        }

        // 혹시 예전에 잘못 저장된 list 형태도 최대한 복구해서 보여주기
        if (is_array($obj) && array_is_list($obj)) {
            $out = [];
            foreach ($obj as $it) {
                if (!is_array($it)) continue;
                $memo = (string)($it['memo'] ?? '');
                if ($memo === '') continue;

                $out[] = [
                    'memo' => $memo,
                    'when' => $this->legacyWhen((string)($it['when'] ?? $it['ts'] ?? '')),
                    'admin_num' => (string)($it['admin_num'] ?? $it['actor_admin_id'] ?? ''),
                ];
            }
            return $out;
        }

        return [];
    }

// ✅ modify_log: {"state_log":[{when,after,title,before,admin_num}, ...]}
    private function legacyStateLogList($modifyLogJson): array
    {
        $obj = $this->decodeJsonObject($modifyLogJson);

        if (isset($obj['state_log']) && is_array($obj['state_log'])) {
            return $obj['state_log'];
        }

        // 혹시 예전에 잘못 저장된 list 형태도 복구 시도
        if (is_array($obj) && array_is_list($obj)) {
            $out = [];
            foreach ($obj as $it) {
                if (!is_array($it)) continue;

                $out[] = [
                    'when' => $this->legacyWhen((string)($it['when'] ?? $it['ts'] ?? '')),
                    'after' => (string)($it['after'] ?? ''),
                    'title' => (string)($it['title'] ?? $it['action'] ?? '변경'),
                    'before' => (string)($it['before'] ?? ''),
                    'admin_num' => (string)($it['admin_num'] ?? $it['actor_admin_id'] ?? ''),
                ];
            }
            return $out;
        }

        return [];
    }

    private function legacyWhen(string $any): string
    {
        $any = trim($any);
        if ($any === '') return now()->format('y-m-d H:i:s');

        if (preg_match('/^\d{2}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}$/', $any)) return $any;

        if (preg_match('/^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}$/', $any)) {
            try { return \Carbon\Carbon::parse($any)->format('y-m-d H:i:s'); }
            catch (\Throwable $e) { return now()->format('y-m-d H:i:s'); }
        }

        try { return \Carbon\Carbon::parse($any)->format('y-m-d H:i:s'); }
        catch (\Throwable $e) { return now()->format('y-m-d H:i:s'); }
    }

    private function trimLegacyList(array $list, int $max = 300): array
    {
        $n = count($list);
        if ($n <= $max) return $list;
        return array_slice($list, $n - $max);
    }

    private function maskPhoneForLog(string $digits): string
    {
        $d = preg_replace('/\D+/', '', $digits) ?? '';
        if ($d === '') return '';
        if (strlen($d) <= 4) return str_repeat('*', strlen($d));
        return substr($d, 0, 3) . str_repeat('*', max(0, strlen($d) - 7)) . substr($d, -4);
    }



}
