<?php

namespace App\Services\Admin\Member;

use App\Repositories\Admin\Member\AdminMemberJoinFilterRepository;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Services\Admin\AdminAuditService;

final class AdminMemberJoinFilterService
{
    public function __construct(
        private readonly AdminMemberJoinFilterRepository $repo,
        private readonly AdminAuditService $audit,
    ) {}

    public function indexData(array $query = []): array
    {
        $filters = [
            'gubun_code' => (string)($query['gubun_code'] ?? request()->query('gubun_code', '')),
            'join_block' => (string)($query['join_block'] ?? request()->query('join_block', '')),
            'filter_ip'  => trim((string)($query['filter_ip'] ?? request()->query('filter_ip', ''))),
        ];

        if ($filters['filter_ip'] !== '' && !preg_match('/^[0-9.]+$/', $filters['filter_ip'])) {
            $filters['filter_ip'] = '';
        }

        $page = $this->repo->paginateFilters($filters, 20);

        $rows = [];
        foreach ($page->items() as $item) {
            $r = is_array($item) ? $item : (array)$item;

            $arr = json_decode((string)($r['admin_phone'] ?? '[]'), true);
            $r['admin_phone_arr'] = is_array($arr) ? $arr : [];

            $rows[] = $r;
        }

        $adminsRaw = $this->repo->listActiveAdminsForSms();
        $admins = [];

        foreach ($adminsRaw as $a) {
            $id  = (int)($a['id'] ?? 0);
            $enc = trim((string)($a['phone_enc'] ?? ''));

            $digits = ($enc !== '') ? $this->decryptPhoneEncToDigits($enc, $id) : '';
            $plain010 = ($digits !== '') ? $this->normalizeKrPhone($digits) : '';
            $value = ($plain010 !== '') ? $plain010 : $digits;

            $admins[] = [
                'id' => $id,
                'email' => (string)($a['email'] ?? ''),
                'name' => (string)($a['name'] ?? ''),
                'phone_plain' => $value,
                'phone_display' => ($plain010 !== '' ? $this->formatPhone($plain010) : ($digits !== '' ? $digits : '')),
                'has_phone' => ($value !== ''),
            ];
        }

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

            'joinBlockCode'  => $this->joinBlockCode(),
            'joinBlockStyle' => $this->joinBlockStyle(),
            'gubunCodeMap'   => $this->gubunCodeMap(),

            'admins' => $admins,
        ];
    }

    public function getRow(int $seq): ?array
    {
        $row = $this->repo->find($seq);
        if (!$row) return null;

        $arr = json_decode((string)($row['admin_phone'] ?? '[]'), true);
        $row['admin_phone_arr'] = is_array($arr) ? $arr : [];

        return $row;
    }

    public function store(array $input, string $actorName, ?int $actorAdminId = null, string $ip = '', string $ua = ''): array
    {
        try {
            return DB::transaction(function () use ($input, $actorName, $actorAdminId, $ip, $ua) {
                $data = $this->buildSaveData($input, $actorName);

                $seq = $this->repo->insert($data);
                if ($seq <= 0) return $this->fail('등록에 실패했습니다.');

                // ✅ 감사로그: 성공시에만 (기존 흐름 방해 X)
                $aid = (int)($actorAdminId ?? auth('admin')->id() ?? 0);
                if ($aid > 0) {
                    $req = request();
                    $this->audit->log(
                        actorAdminId: $aid,
                        action: 'admin.join_filter.create',
                        targetType: 'join_filter',
                        targetId: (int)$seq,
                        before: null,
                        after: array_merge(['seq' => (int)$seq], $data),
                        ip: $ip ?: (string)$req->ip(),
                        ua: $ua ?: (string)$req->userAgent(),
                    );
                }

                return $this->ok('정상적으로 등록되었습니다.', ['seq' => $seq]);
            });
        } catch (\Throwable $e) {
            Log::error('[join-filter] store failed', [
                'err' => $e->getMessage(),
            ]);

            $msg = ($e instanceof \RuntimeException) ? $e->getMessage() : '등록 중 오류가 발생했습니다.';
            return $this->fail($msg);
        }
    }


    public function update(int $seq, array $input, string $actorName, ?int $actorAdminId = null, string $ip = '', string $ua = ''): array
    {
        try {
            return DB::transaction(function () use ($seq, $input, $actorName, $actorAdminId, $ip, $ua) {
                $before = $this->repo->lockForUpdate($seq);
                if (!$before) return $this->fail('대상을 찾을 수 없습니다.');

                $data = $this->buildSaveData($input, $actorName);

                $affected = $this->repo->update($seq, $data);

                // ✅ 0건(변경 없음)도 성공 처리 (기존 정책 유지)
                if ($affected === 0) return $this->ok('변경사항이 없습니다. (저장 완료)');

                if ($affected > 0) {
                    // ✅ 감사로그: 성공시에만
                    $aid = (int)($actorAdminId ?? auth('admin')->id() ?? 0);
                    if ($aid > 0) {
                        $req = request();

                        // before/after는 "필드 덮어쓰기" 방식으로 안전하게 구성 (repo->find() 의존 X)
                        $beforeAudit = (array)$before;
                        $afterAudit  = array_merge($beforeAudit, $data);

                        $this->audit->log(
                            actorAdminId: $aid,
                            action: 'admin.join_filter.update',
                            targetType: 'join_filter',
                            targetId: (int)$seq,
                            before: $beforeAudit,
                            after: $afterAudit,
                            ip: $ip ?: (string)$req->ip(),
                            ua: $ua ?: (string)$req->userAgent(),
                        );
                    }

                    return $this->ok('정상적으로 수정되었습니다.');
                }

                return $this->fail('수정에 실패했습니다.');
            });
        } catch (\Throwable $e) {
            Log::error('[join-filter] update failed', [
                'seq' => $seq,
                'err' => $e->getMessage(),
            ]);

            $msg = ($e instanceof \RuntimeException) ? $e->getMessage() : '수정 중 오류가 발생했습니다.';
            return $this->fail($msg);
        }
    }


    public function destroy(int $seq, ?int $actorAdminId = null, string $ip = '', string $ua = ''): array
    {
        try {
            return DB::transaction(function () use ($seq, $actorAdminId, $ip, $ua) {
                $before = $this->repo->lockForUpdate($seq);
                if (!$before) return $this->fail('대상을 찾을 수 없습니다.');

                $affected = $this->repo->delete($seq);
                if ($affected <= 0) return $this->fail('삭제에 실패했습니다.');

                // ✅ 감사로그: 성공시에만
                $aid = (int)($actorAdminId ?? auth('admin')->id() ?? 0);
                if ($aid > 0) {
                    $req = request();

                    $this->audit->log(
                        actorAdminId: $aid,
                        action: 'admin.join_filter.delete',
                        targetType: 'join_filter',
                        targetId: (int)$seq,
                        before: (array)$before,
                        after: null,
                        ip: $ip ?: (string)$req->ip(),
                        ua: $ua ?: (string)$req->userAgent(),
                    );
                }

                return $this->ok('정상적으로 삭제되었습니다.');
            });
        } catch (\Throwable $e) {
            Log::error('[join-filter] destroy failed', [
                'seq' => $seq,
                'err' => $e->getMessage(),
            ]);
            return $this->fail('삭제 중 오류가 발생했습니다.');
        }
    }


    private function buildSaveData(array $input, string $actorName): array
    {
        $gubunCode = (string)($input['gubun_code'] ?? '');
        if (!in_array($gubunCode, ['01', '02'], true)) $gubunCode = '01';

        $gubun = $this->gubunCodeMap()[$gubunCode] ?? 'IP 필터';

        $joinBlock = (string)($input['join_block'] ?? 'N');
        if (!in_array($joinBlock, ['S', 'A', 'N'], true)) $joinBlock = 'N';

        $filter = trim((string)($input['filter'] ?? ''));
        if ($filter === '' || !$this->validateIpByGubun($filter, $gubunCode)) {
            throw new \RuntimeException('아이피 형식이 올바르지 않습니다. (C=3옥텟, D=4옥텟)');
        }

        $phones = $input['admin_phone'] ?? [];
        if (!is_array($phones) || empty($phones)) {
            $phones = ['SMS발송없음'];
        } else {
            $out = [];
            foreach ($phones as $p) {
                $d = preg_replace('/\D+/', '', (string)$p) ?: '';
                $n = $this->normalizeKrPhone($d);
                $out[] = ($n !== '' ? $n : $d);
            }
            $out = array_values(array_unique(array_filter($out, fn($v)=>$v !== '')));
            $phones = !empty($out) ? $out : ['SMS발송없음'];
        }

        return [
            'gubun'       => $gubun,
            'gubun_code'  => $gubunCode,
            'filter'      => $filter,
            'join_block'  => $joinBlock,
            'admin_phone' => json_encode($phones, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
            'admin'       => $actorName,
            'dt_reg'      => now()->format('Y-m-d H:i:s'),
        ];
    }

    private function validateIpByGubun(string $ip, string $gubunCode): bool
    {
        $ip = trim($ip);
        if ($ip === '' || !preg_match('/^[0-9.]+$/', $ip)) return false;

        $parts = explode('.', $ip);
        if ($gubunCode === '01' && count($parts) !== 3) return false;
        if ($gubunCode === '02' && count($parts) !== 4) return false;

        foreach ($parts as $p) {
            if ($p === '' || !ctype_digit($p)) return false;
            $n = (int)$p;
            if ($n < 0 || $n > 255) return false;
        }
        return true;
    }

    private function gubunCodeMap(): array
    {
        return ['01' => 'IP C-class 필터', '02' => 'IP D-class 필터'];
    }

    private function joinBlockCode(): array
    {
        return ['S' => '관리자SMS알림', 'A' => '회원가입차단', 'N' => '필터끔'];
    }

    private function joinBlockStyle(): array
    {
        return ['S' => 'pill--warn', 'A' => 'pill--bad', 'N' => 'pill--muted'];
    }

    private function decryptPhoneEncToDigits(string $enc, int $adminId = 0): string
    {
        $enc = (string)$enc;
        if ($enc === '') return '';

        try {
            $raw = Crypt::decryptString($enc);
            $digits = preg_replace('/\D+/', '', (string)$raw) ?: '';
            return preg_match('/^\d{9,15}$/', $digits) ? $digits : '';
        } catch (\Throwable $e1) {
            try {
                $raw = decrypt($enc, false);
                $digits = preg_replace('/\D+/', '', (string)$raw) ?: '';
                return preg_match('/^\d{9,15}$/', $digits) ? $digits : '';
            } catch (\Throwable $e2) {
                if (preg_match('/^\d{9,15}$/', $enc)) return $enc;

                Log::warning('[join-filter] admin phone decrypt failed', [
                    'admin_id' => $adminId,
                    'enc_len'  => strlen($enc),
                    'enc_head' => substr($enc, 0, 16),
                    'e1' => $e1->getMessage(),
                    'e2' => $e2->getMessage(),
                ]);
                return '';
            }
        }
    }

    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 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);
        return substr($digits,0,3).'-'.substr($digits,3,3).'-'.substr($digits,6,4);
    }

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

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