<?php
namespace App\Services\Admin\Mail;

use App\Repositories\Admin\Mail\AdminMailRepository;
use App\Repositories\Admin\Mail\AdminMailTemplateRepository;
use App\Services\MailService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use App\Services\Admin\AdminAuditService;

final class AdminMailService
{
    public function __construct(
        private readonly AdminMailRepository $repo,
        private readonly AdminMailTemplateRepository $tplRepo,
        private readonly MailService $mail,
        private readonly AdminAuditService $audit,
    ) {}

    public function getSkinOptions(): array
    {
        return [
            ['key'=>'hero','label'=>'Hero Classic (이벤트/마케팅)'],
            ['key'=>'newsletter','label'=>'Newsletter Card (여러 항목)'],
            ['key'=>'minimal','label'=>'Minimal Transactional (알림/인증)'],
            ['key'=>'clean','label'=>'Clean Transactional'],
            ['key'=>'dark','label'=>'Dark Transactional'],
        ];
    }

    public function getStatusLabels(): array
    {
        return [
            'scheduled'=>'예약대기',
            'queued'=>'대기',
            'sending'=>'발송중',
            'sent'=>'완료',
            'partial'=>'부분실패',
            'failed'=>'실패',
            'canceled'=>'취소',
        ];
    }

    public function getItemStatusLabels(): array
    {
        return [
            'queued'=>'대기',
            'sent'=>'성공',
            'failed'=>'실패',
            'canceled'=>'취소',
            'skipped'=>'스킵',
        ];
    }

    public function getModeLabels(): array
    {
        return [
            'one'=>'단건',
            'many'=>'여러건',
            'csv'=>'CSV 업로드',
        ];
    }

    public function getActiveTemplates(): array
    {
        return $this->tplRepo->activeAll();
    }

    public function listTemplates(array $filters, int $perPage=20)
    {
        return $this->tplRepo->list($filters, $perPage);
    }

    public function getTemplate(int $id): ?object
    {
        return $this->tplRepo->find($id);
    }

    public function createTemplate(int $adminId, array $data): array
    {
        $code = (string)($data['code'] ?? '');
        if ($this->tplRepo->existsCode($code)) {
            return ['ok'=>false,'message'=>'이미 존재하는 code 입니다.'];
        }

        $payload = [
            'code'           => $code,
            'title'          => $data['title'],
            'description'    => $data['description'] ?? '',
            'skin_key'       => $data['skin_key'],
            'subject_tpl'    => $data['subject'],
            'body_tpl'       => $data['body'],
            'hero_image_url' => $data['hero_image_url'] ?? '',
            'cta_label'      => $data['cta_label'] ?? '',
            'cta_url'        => $data['cta_url'] ?? '',
            'is_active'      => (int)(!empty($data['is_active']) ? 1 : 0),
            'created_by'     => $adminId,
            'updated_by'     => $adminId,
        ];

        $id = $this->tplRepo->create($payload);

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

        return ['ok'=>true,'id'=>$id];
    }


    public function updateTemplate(int $adminId, int $id, array $data): array
    {
        // ✅ before 스냅샷: repo에 get/find가 있으면 사용, 없으면 null로 진행(흐름 방해 X)
        $before = null;
        try {
            if (method_exists($this->tplRepo, 'find')) {
                $before = $this->tplRepo->find($id);
            } elseif (method_exists($this->tplRepo, 'get')) {
                $before = $this->tplRepo->get($id);
            }
        } catch (\Throwable $ignored) {}

        $payload = [
            'title'         => $data['title'],
            'description'   => $data['description'] ?? '',
            'skin_key'      => $data['skin_key'],
            'subject_tpl'   => $data['subject'],
            'body_tpl'      => $data['body'],
            'hero_image_url'=> $data['hero_image_url'] ?? '',
            'cta_label'     => $data['cta_label'] ?? '',
            'cta_url'       => $data['cta_url'] ?? '',
            'is_active'     => (int)(($data['is_active'] ?? 0) ? 1 : 0),
            'updated_by'    => $adminId,
        ];

        $affected = $this->tplRepo->update($id, $payload);

        $ok = ($affected >= 0);

        // ✅ 성공시에만 감사로그
        if ($ok) {
            $req = request();

            $beforeAudit = $before ? (array)$before : ['id' => $id];
            $afterAudit  = array_merge($beforeAudit, $payload);

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

        return ['ok'=>$ok];
    }


    public function listBatches(array $filters, int $perPage=20)
    {
        $page = $this->repo->listBatches($filters, $perPage);

        if (is_object($page) && method_exists($page, 'appends')) {
            $clean = array_filter($filters, fn($v) => $v !== null && $v !== '');
            $page->appends($clean);
        }

        return $page;
    }

    public function getBatchDetail(int $batchId, array $filters, int $perPage=50): array
    {
        $batch = $this->repo->findBatch($batchId);
        if (!$batch) return ['ok'=>false];

        $items = $this->repo->listItems($batchId, $filters, $perPage);

        if (is_object($items) && method_exists($items, 'appends')) {
            $clean = array_filter($filters, fn($v) => $v !== null && $v !== '');
            $items->appends($clean);
        }

        return ['ok'=>true,'batch'=>$batch,'items'=>$items];
    }

    public function cancelBatch(int $adminId, int $batchId): array
    {
        $b = $this->repo->findBatch($batchId);
        if (!$b) return ['ok'=>false,'message'=>'배치를 찾을 수 없습니다.'];

        if (in_array((string)$b->status, ['sent','failed','partial','canceled'], true)) {
            return ['ok'=>false,'message'=>'이미 종료된 배치입니다.'];
        }

        $this->repo->cancelBatch($batchId);
        return ['ok'=>true,'message'=>'취소 처리했습니다.'];
    }

    public function retryFailed(int $adminId, int $batchId): array
    {
        $b = $this->repo->findBatch($batchId);
        if (!$b) return ['ok'=>false,'message'=>'배치를 찾을 수 없습니다.'];

        $n = $this->repo->resetFailedToQueued($batchId);
        $this->repo->updateBatch($batchId, ['status'=>'queued','last_error'=>'']);

        \App\Jobs\Admin\Mail\DispatchMailBatchJob::dispatch($batchId)->onQueue('mail');

        return ['ok'=>true,'message'=>"실패 {$n}건을 재시도 대기열로 전환했습니다."];
    }

    public function createBatch(int $adminId, array $data, Request $request, array $ctx): array
    {
        $mode = (string)$data['send_mode'];

        // ✅ (선택) 템플릿을 서버에서 강제 적용하고 싶으면 template_id가 넘어왔을 때 덮어쓰기
        // Blade에서 tplSelect에 name="template_id" 꼭 넣어야 들어옴
        $tplId = (int)($data['template_id'] ?? 0);
        if ($tplId > 0) {
            $tpl = $this->tplRepo->find($tplId);
            if (!$tpl) return ['ok'=>false,'message'=>'템플릿을 찾을 수 없습니다.'];

            $data['skin_key']       = (string)($tpl->skin_key ?? $data['skin_key']);
            $data['subject']        = (string)($tpl->subject_tpl ?? $data['subject']);
            $data['body']           = (string)($tpl->body_tpl ?? $data['body']);
            $data['hero_image_url'] = (string)($tpl->hero_image_url ?? ($data['hero_image_url'] ?? ''));
            $data['cta_label']      = (string)($tpl->cta_label ?? ($data['cta_label'] ?? ''));
            $data['cta_url']        = (string)($tpl->cta_url ?? ($data['cta_url'] ?? ''));
        }

        // 1) 수신자 수집
        $recipients = $this->collectRecipients($mode, $data, $request);
        if (!$recipients['ok']) return $recipients;

        $list  = $recipients['list'];   // [ ['email'=>..., 'name'=>..., 'tokens'=>[...]], ... ]
        $total = $recipients['total'];
        $valid = count($list);

        if ($valid < 1) {
            return ['ok'=>false,'message'=>'유효한 수신자가 없습니다.'];
        }

        // 2) 배치 생성
        $scheduledAt = !empty($data['scheduled_at']) ? (string)$data['scheduled_at'].':00' : null;
        $status = $scheduledAt ? 'scheduled' : 'queued';

        $ipBin = null;
        try { $ipBin = @inet_pton($ctx['ip'] ?? '') ?: null; } catch (\Throwable $e) {}

        $batchId = $this->repo->createBatch([
            'admin_user_id'=>$adminId,
            'admin_name'=>$ctx['admin_name'] ?? '',
            'created_ip'=>$ipBin,
            'created_ua'=>mb_substr((string)($ctx['ua'] ?? ''),0,255),

            'send_mode'=>$mode,
            'from_email'=>$data['from_email'],
            'from_name'=>$data['from_name'] ?? '',
            'reply_to'=>$data['reply_to'] ?? '',

            'skin_key'=>$data['skin_key'],
            'subject_raw'=>$data['subject'],
            'body_raw'=>$data['body'],
            'hero_image_url'=>$data['hero_image_url'] ?? '',
            'cta_label'=>$data['cta_label'] ?? '',
            'cta_url'=>$data['cta_url'] ?? '',

            'scheduled_at'=>$scheduledAt,
            'status'=>$status,

            'total_count'=>$total,
            'valid_count'=>$valid,
            'sent_count'=>0,
            'failed_count'=>0,
            'canceled_count'=>0,
            'last_error'=>'',
        ]);

        // 3) 아이템 insert (토큰 치환)
        $rows = [];
        $seq = 1;
        foreach ($list as $r) {
            $tokens = $r['tokens'] ?? [];

            // ✅ strtr로 치환(키-값 배열)
            $subjectFinal = $this->applyTokens((string)$data['subject'], $tokens);
            $bodyFinal    = $this->applyTokens((string)$data['body'], $tokens);

            $rows[] = [
                'batch_id'=>$batchId,
                'seq'=>$seq++,
                'to_email'=>$r['email'],
                'to_name'=>$r['name'] ?? '',
                'token_json'=>json_encode($tokens, JSON_UNESCAPED_UNICODE),
                'subject_final'=>$subjectFinal,
                'body_final'=>$bodyFinal,
                'status'=>'queued',
                'attempts'=>0,
                'last_error'=>'',
            ];
        }

        foreach (array_chunk($rows, 500) as $chunk) {
            $this->repo->bulkInsertItems($batchId, $chunk);
        }

        if (!$scheduledAt) {
            \App\Jobs\Admin\Mail\DispatchMailBatchJob::dispatch($batchId)->onQueue('mail');
        }

        return [
            'ok'=>true,
            'batch_id'=>$batchId,
            'total'=>$total,
            'valid'=>$valid,
            'scheduled'=>(bool)$scheduledAt,
        ];
    }

    private function collectRecipients(string $mode, array $data, Request $request): array
    {
        $raw = [];
        if ($mode === 'one') {
            if (empty($data['to_email'])) return ['ok'=>false,'message'=>'수신자 이메일을 입력하세요.'];
            $raw[] = ['email'=>$data['to_email'], 'name'=>$data['to_name'] ?? '', 'tokens'=>[]];
        }
        elseif ($mode === 'many') {
            $text = (string)($data['to_emails_text'] ?? '');
            $raw = array_merge($raw, $this->parseEmailsText($text));
        }
        elseif ($mode === 'csv') {
            $file = $request->file('to_emails_csv');
            if (!$file) return ['ok'=>false,'message'=>'CSV 파일을 업로드하세요.'];
            $raw = array_merge($raw, $this->parseEmailsCsv($file->getRealPath()));
        }

        $total = count($raw);
        $seen = [];
        $list = [];

        foreach ($raw as $r) {
            $email = mb_strtolower(trim((string)($r['email'] ?? '')));
            if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) continue;

            if (isset($seen[$email])) continue;
            $seen[$email] = true;

            $list[] = [
                'email'=>$email,
                'name'=>trim((string)($r['name'] ?? '')),
                'tokens'=>is_array($r['tokens'] ?? null) ? $r['tokens'] : [],
            ];
        }

        return ['ok'=>true,'total'=>$total,'list'=>$list];
    }

    private function parseEmailsText(string $text): array
    {
        // 형식:
        // email,token1,token2,...
        // ✅ 1열=email, 2열={_text_02_}, 3열={_text_03_} ...
        $lines = preg_split('/\r\n|\r|\n/', $text) ?: [];
        $out = [];

        foreach ($lines as $line) {
            $line = trim((string)$line);
            if ($line === '') continue;

            // ✅ 빈열 유지(열 밀림 방지) => filter 하지 않음
            $cols = array_map('trim', explode(',', $line));

            $email = $cols[0] ?? '';
            $name  = $cols[1] ?? ''; // 로그용으로만(원하면 비워도 됨)

            $tokens = $this->colsToTokens($cols);
            $out[] = ['email'=>$email,'name'=>$name,'tokens'=>$tokens];
        }

        return $out;
    }

    private function parseEmailsCsv(string $path): array
    {
        $out = [];
        $fh = fopen($path, 'r');
        if (!$fh) return $out;

        while (($row = fgetcsv($fh)) !== false) {
            if (!$row) continue;

            // fgetcsv는 빈값도 배열에 포함됨(열 밀림 방지에 유리)
            $cols = array_map(fn($v)=>trim((string)$v), $row);

            $email  = $cols[0] ?? '';
            $name   = $cols[1] ?? ''; // 로그용

            $tokens = $this->colsToTokens($cols);
            $out[] = ['email'=>$email,'name'=>$name,'tokens'=>$tokens];
        }

        fclose($fh);
        return $out;
    }

    private function colsToTokens(array $cols): array
    {
        $tokens = [];
        if (!empty($cols[1])) $tokens['{_name_}'] = $cols[1];
        for ($i = 1; $i < count($cols) && $i <= 8; $i++) { // 최대 {_text_09_} 까지
            $tokenNo = str_pad((string)($i + 1), 2, '0', STR_PAD_LEFT); // 02..09
            $tokens['{_text_'.$tokenNo.'_}'] = (string)($cols[$i] ?? '');
        }

        return $tokens;
    }

    private function applyTokens(string $text, array $tokens): string
    {
        if ($text === '' || empty($tokens)) return $text;
        return strtr($text, $tokens);
    }

    public function dispatchDueScheduledBatches(int $limit=20): int
    {
        $rows = $this->repo->pickDueScheduledBatches($limit);
        $n = 0;
        foreach ($rows as $b) {
            $this->repo->setBatchQueued((int)$b->id);
            \App\Jobs\Admin\Mail\DispatchMailBatchJob::dispatch((int)$b->id)->onQueue('mail');
            $n++;
        }
        return $n;
    }

    public function sendItem(object $batch, object $item): array
    {
        if (in_array((string)$batch->status, ['canceled'], true)) {
            $this->repo->updateItem((int)$item->id, [
                'status'=>'skipped','last_error'=>'batch_canceled'
            ]);
            return ['ok'=>false,'skipped'=>true];
        }

        $view = match ((string)$batch->skin_key) {
            'newsletter' => 'mail.skins.newsletter',
            'minimal'    => 'mail.skins.minimal',
            'clean'      => 'mail.skins.clean',
            'dark'       => 'mail.skins.dark',
            default      => 'mail.skins.hero',
        };

        $subject = (string)($item->subject_final ?? '');
        $bodyText = (string)($item->body_final ?? '');

        // 핵심: preview와 동일한 변수명으로 통일
        $payload = [
            'brand'     => config('app.name'),
            'subtitle'  => '공지/이벤트/안내 메일', // 스킨에서 쓰면 보이고, 안 쓰면 무시됨
            'logoUrl'   => config('services.brand.logo_url', ''),
            'subject'   => $subject,
            'body_html' => nl2br(e($bodyText)),
            'body_text' => $bodyText,
            'heroUrl'   => (string)($batch->hero_image_url ?? ''),
            'siteUrl'   => config('app.url'),
            'year'      => date('Y'),
            'sns' => [
                ['label'=>'Instagram', 'url'=>config('services.brand.sns.instagram','')],
                ['label'=>'YouTube',    'url'=>config('services.brand.sns.youtube','')],
                ['label'=>'Kakao',      'url'=>config('services.brand.sns.kakao','')],
            ],
        ];


        try {
            $this->mail->sendTemplate(
                $item->to_email,
                $subject,
                $view,
                $payload,
                queue: false
            );

            $this->repo->updateItem((int)$item->id, [
                'status'=>'sent',
                'attempts'=> (int)$item->attempts + 1,
                'last_error'=>'',
                'sent_at'=> now()->format('Y-m-d H:i:s'),
            ]);

            return ['ok'=>true];

        } catch (\Throwable $e) {
            $msg = mb_substr($e->getMessage(), 0, 500);

            $this->repo->updateItem((int)$item->id, [
                'status'=>'failed',
                'attempts'=> (int)$item->attempts + 1,
                'last_error'=>$msg,
            ]);

            Log::warning('[admin-mail] send failed', [
                'batch_id'=>$batch->id,
                'item_id'=>$item->id,
                'to'=>$item->to_email,
                'err'=>$msg,
            ]);

            return ['ok'=>false,'error'=>$msg];
        }
    }

    public function refreshBatchProgress(int $batchId): void
    {
        $b = $this->repo->findBatch($batchId);
        if (!$b) return;

        $cnt = $this->repo->countItemsByStatus($batchId);

        $valid = (int)$b->valid_count;
        $sent  = (int)($cnt['sent'] ?? 0);
        $failed= (int)($cnt['failed'] ?? 0);
        $queued= (int)($cnt['queued'] ?? 0);

        $status = (string)$b->status;

        if ($status !== 'canceled') {
            if ($queued > 0) $status = 'sending';
            else {
                if ($failed === 0 && $sent >= $valid) $status = 'sent';
                elseif ($sent > 0 && $failed > 0) $status = 'partial';
                else $status = 'failed';
            }
        }

        $this->repo->updateBatchCounts($batchId, $cnt, $status);
    }
}
