<?php

namespace App\Http\Controllers\ESign;

use App\Http\Controllers\Controller;
use App\Mail\EsignRequestMail;
use App\Models\Barobill\BarobillMember;
use App\Models\ESign\EsignAuditLog;
use App\Models\ESign\EsignContract;
use App\Models\ESign\EsignFieldTemplate;
use App\Models\ESign\EsignFieldTemplateItem;
use App\Models\ESign\EsignSigner;
use App\Models\ESign\EsignSignField;
use App\Models\Sales\TenantProspect;
use App\Models\Tenants\TenantSetting;
use App\Models\User;
use App\Services\Barobill\BarobillService;
use App\Services\ESign\DocxToPdfConverter;
use App\Services\GoogleCloudStorageService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;

class EsignApiController extends Controller
{
    /**
     * 영업파트너 검색
     */
    public function searchPartners(Request $request): JsonResponse
    {
        $q = trim($request->input('q', ''));

        $tenantId = session('selected_tenant_id', 1);

        $query = User::where('is_active', true)
            ->whereHas('userRoles', function ($w) use ($tenantId) {
                $w->where('tenant_id', $tenantId)
                    ->whereHas('role', fn ($r) => $r->whereIn('name', ['sales', 'manager']));
            })
            ->with(['salesPartner', 'userRoles' => function ($q) use ($tenantId) {
                $q->where('tenant_id', $tenantId)->with('role');
            }]);

        if ($q !== '') {
            $query->where(function ($w) use ($q) {
                $w->where('name', 'like', "%{$q}%")
                    ->orWhere('email', 'like', "%{$q}%")
                    ->orWhere('phone', 'like', "%{$q}%");
            });
        }

        $users = $query->orderBy('name')->limit(20)->get();

        $roleLabels = ['sales' => '영업파트너', 'manager' => '상담매니저'];

        $data = $users->map(function ($user) use ($roleLabels) {
            $sp = $user->salesPartner;
            $roles = $user->userRoles->map(fn ($ur) => $roleLabels[$ur->role?->name] ?? null)->filter()->values();

            return [
                'id' => $user->id,
                'name' => $user->name,
                'phone' => $user->phone,
                'email' => $user->email,
                'company_name' => $sp?->company_name,
                'biz_no' => $sp?->biz_no,
                'address' => $sp?->address,
                'position' => $roles->implode('/'),
            ];
        });

        return response()->json(['success' => true, 'data' => $data]);
    }

    /**
     * 고객(명함 등록 고객) 검색
     */
    public function searchTenants(Request $request): JsonResponse
    {
        $q = trim($request->input('q', ''));

        $query = TenantProspect::query();

        if ($q !== '') {
            $query->where(function ($w) use ($q) {
                $w->where('company_name', 'like', "%{$q}%")
                    ->orWhere('business_number', 'like', "%{$q}%")
                    ->orWhere('ceo_name', 'like', "%{$q}%")
                    ->orWhere('contact_phone', 'like', "%{$q}%");
            });
        }

        $prospects = $query->orderBy('company_name')->limit(20)->get();

        $data = $prospects->map(fn ($p) => [
            'id' => $p->id,
            'company_name' => $p->company_name,
            'business_number' => $p->business_number,
            'ceo_name' => $p->ceo_name,
            'address' => $p->address,
            'phone' => $p->contact_phone,
            'email' => $p->contact_email,
        ]);

        return response()->json(['success' => true, 'data' => $data]);
    }

    /**
     * 계약번호 자동 채번 (CONTRACT-YYYY-MMDD-N)
     */
    public function generateContractNumber(): JsonResponse
    {
        $tenantId = session('selected_tenant_id', 1);
        $today = now()->format('Ymd');
        $prefix = "CONTRACT-{$today}-";

        $lastContract = EsignContract::where('tenant_id', $tenantId)
            ->where('contract_code', 'like', "{$prefix}%")
            ->orderByRaw('CAST(SUBSTRING(contract_code, ?) AS UNSIGNED) DESC', [strlen($prefix) + 1])
            ->first();

        $seq = 1;
        if ($lastContract) {
            $lastSeq = (int) str_replace($prefix, '', $lastContract->contract_code);
            $seq = $lastSeq + 1;
        }

        return response()->json([
            'success' => true,
            'data' => ['contract_number' => $prefix.$seq],
        ]);
    }

    /**
     * 법인도장 조회
     */
    public function getStamp(): JsonResponse
    {
        $tenantId = session('selected_tenant_id', 1);
        $setting = TenantSetting::where('tenant_id', $tenantId)
            ->where('setting_group', 'esign')
            ->where('setting_key', 'company_stamp')
            ->first();

        if (! $setting) {
            return response()->json(['success' => true, 'data' => null]);
        }

        $value = $setting->setting_value;

        // 로컬 스토리지
        if (! empty($value['local_path'])) {
            if (Storage::disk('local')->exists($value['local_path'])) {
                $imageUrl = route('esign.contracts.stamp.image', ['tenant' => $tenantId]);

                return response()->json([
                    'success' => true,
                    'data' => ['image_url' => $imageUrl],
                ]);
            }

            return response()->json(['success' => true, 'data' => null]);
        }

        // GCS 스토리지 (레거시)
        if (! empty($value['gcs_object'])) {
            $gcs = app(GoogleCloudStorageService::class);
            $signedUrl = $gcs->getSignedUrl($value['gcs_object'], 60);
            if ($signedUrl) {
                return response()->json([
                    'success' => true,
                    'data' => ['image_url' => $signedUrl],
                ]);
            }
        }

        return response()->json(['success' => true, 'data' => null]);
    }

    /**
     * 법인도장 이미지 서빙 (로컬 스토리지용)
     */
    public function serveStampImage(int $tenant)
    {
        $setting = TenantSetting::where('tenant_id', $tenant)
            ->where('setting_group', 'esign')
            ->where('setting_key', 'company_stamp')
            ->first();

        if (! $setting || empty($setting->setting_value['local_path'])) {
            abort(404);
        }

        $path = $setting->setting_value['local_path'];
        if (! Storage::disk('local')->exists($path)) {
            abort(404);
        }

        return response(Storage::disk('local')->get($path))
            ->header('Content-Type', 'image/png')
            ->header('Cache-Control', 'private, max-age=3600');
    }

    /**
     * 법인도장 업로드
     */
    public function uploadStamp(Request $request): JsonResponse
    {
        $request->validate([
            'stamp_image_data' => 'required|string',
        ]);

        $tenantId = session('selected_tenant_id', 1);
        $imageData = base64_decode($request->input('stamp_image_data'));

        if (! $imageData) {
            return response()->json(['success' => false, 'message' => '이미지 데이터가 올바르지 않습니다.'], 422);
        }

        $gcs = app(GoogleCloudStorageService::class);

        // 기존 파일 삭제
        $existing = TenantSetting::where('tenant_id', $tenantId)
            ->where('setting_group', 'esign')
            ->where('setting_key', 'company_stamp')
            ->first();
        if ($existing) {
            $val = $existing->setting_value;
            if (! empty($val['gcs_object']) && $gcs->isAvailable()) {
                $gcs->delete($val['gcs_object']);
            }
            if (! empty($val['local_path'])) {
                Storage::disk('local')->delete($val['local_path']);
            }
        }

        // GCS 사용 가능하면 GCS에 업로드
        if ($gcs->isAvailable()) {
            $tmpFile = tempnam(sys_get_temp_dir(), 'stamp_');
            file_put_contents($tmpFile, $imageData);

            $gcsObject = "esign/{$tenantId}/stamps/company_stamp.png";
            $gcsUri = $gcs->upload($tmpFile, $gcsObject);
            unlink($tmpFile);

            if ($gcsUri) {
                TenantSetting::updateOrCreate(
                    ['tenant_id' => $tenantId, 'setting_group' => 'esign', 'setting_key' => 'company_stamp'],
                    ['setting_value' => ['gcs_object' => $gcsObject], 'updated_by' => auth()->id()]
                );

                return response()->json([
                    'success' => true,
                    'message' => '법인도장이 등록되었습니다.',
                    'data' => ['image_url' => $gcs->getSignedUrl($gcsObject, 60)],
                ]);
            }
        }

        // GCS 미사용 → 로컬 스토리지
        $localPath = "esign/stamps/{$tenantId}/company_stamp.png";
        Storage::disk('local')->put($localPath, $imageData);

        TenantSetting::updateOrCreate(
            ['tenant_id' => $tenantId, 'setting_group' => 'esign', 'setting_key' => 'company_stamp'],
            ['setting_value' => ['local_path' => $localPath], 'updated_by' => auth()->id()]
        );

        $imageUrl = route('esign.contracts.stamp.image', ['tenant' => $tenantId]);

        return response()->json([
            'success' => true,
            'message' => '법인도장이 등록되었습니다.',
            'data' => ['image_url' => $imageUrl],
        ]);
    }

    /**
     * 법인도장 삭제
     */
    public function deleteStamp(): JsonResponse
    {
        $tenantId = session('selected_tenant_id', 1);
        $setting = TenantSetting::where('tenant_id', $tenantId)
            ->where('setting_group', 'esign')
            ->where('setting_key', 'company_stamp')
            ->first();

        if ($setting) {
            $val = $setting->setting_value;
            if (! empty($val['gcs_object'])) {
                $gcs = app(GoogleCloudStorageService::class);
                if ($gcs->isAvailable()) {
                    $gcs->delete($val['gcs_object']);
                }
            }
            if (! empty($val['local_path'])) {
                Storage::disk('local')->delete($val['local_path']);
            }
            $setting->delete();
        }

        return response()->json([
            'success' => true,
            'message' => '법인도장이 삭제되었습니다.',
        ]);
    }

    /**
     * 상태별 통계
     */
    public function stats(): JsonResponse
    {
        $tenantId = session('selected_tenant_id', 1);
        $contracts = EsignContract::forTenant($tenantId)->get();

        $stats = [
            'total' => $contracts->count(),
            'draft' => $contracts->where('status', 'draft')->count(),
            'pending' => $contracts->where('status', 'pending')->count(),
            'partially_signed' => $contracts->where('status', 'partially_signed')->count(),
            'completed' => $contracts->where('status', 'completed')->count(),
            'expired' => $contracts->where('status', 'expired')->count(),
            'cancelled' => $contracts->where('status', 'cancelled')->count(),
            'rejected' => $contracts->where('status', 'rejected')->count(),
        ];

        return response()->json(['success' => true, 'data' => $stats]);
    }

    /**
     * 계약 목록 (페이지네이션)
     */
    public function index(Request $request): JsonResponse
    {
        $tenantId = session('selected_tenant_id', 1);
        $query = EsignContract::forTenant($tenantId)->with(['signers:id,contract_id,name,role,status']);

        if ($status = $request->input('status')) {
            $query->where('status', $status);
        }
        if ($search = $request->input('search')) {
            $query->where(function ($q) use ($search) {
                $q->where('title', 'like', "%{$search}%")
                    ->orWhere('contract_code', 'like', "%{$search}%");
            });
        }

        $perPage = $request->input('per_page', 20);
        $data = $query->orderBy('created_at', 'desc')->paginate($perPage);

        return response()->json(['success' => true, 'data' => $data]);
    }

    /**
     * 계약 상세 조회
     */
    public function show(int $id): JsonResponse
    {
        $tenantId = session('selected_tenant_id', 1);
        $contract = EsignContract::forTenant($tenantId)
            ->with(['signers', 'signFields', 'auditLogs' => fn ($q) => $q->orderBy('created_at', 'desc')])
            ->findOrFail($id);

        return response()->json(['success' => true, 'data' => $contract]);
    }

    /**
     * 계약 생성
     */
    public function store(Request $request): JsonResponse
    {
        $request->validate([
            'title' => 'required|string|max:200',
            'description' => 'nullable|string',
            'sign_order_type' => 'required|in:counterpart_first,creator_first',
            'expires_at' => 'nullable|date',
            'expires_days' => 'nullable|integer|min:1|max:365',
            'template_id' => 'nullable|integer',
            'signers' => 'required|array|size:2',
            'signers.*.name' => 'required|string|max:100',
            'signers.*.email' => 'required|email|max:200',
            'signers.*.phone' => 'nullable|string|max:20',
            'signers.*.role' => 'required|in:creator,counterpart',
            'metadata' => 'nullable|array',
            'metadata.*' => 'nullable|string|max:500',
            'file' => 'nullable|file|mimes:pdf,doc,docx|max:20480',
        ]);

        $tenantId = session('selected_tenant_id', 1);
        $userId = auth()->id();

        // 계약 코드 생성
        $contractCode = 'ES-'.date('Ymd').'-'.strtoupper(Str::random(6));

        // PDF 파일 처리
        $filePath = null;
        $fileName = null;
        $fileHash = null;
        $fileSize = null;

        if ($request->hasFile('file')) {
            // 사용자가 직접 업로드한 파일 우선 사용 (Word면 PDF 자동 변환)
            $file = $request->file('file');
            $converter = new DocxToPdfConverter;
            $result = $converter->convertAndStore($file, "esign/{$tenantId}/contracts");
            $filePath = $result['path'];
            $fileName = $result['name'];
            $fileHash = $result['hash'];
            $fileSize = $result['size'];
        } elseif ($request->input('template_id')) {
            // 템플릿에 PDF가 있으면 복사
            $template = EsignFieldTemplate::forTenant($tenantId)
                ->where('is_active', true)
                ->find($request->input('template_id'));

            if ($template && $template->file_path && Storage::disk('local')->exists($template->file_path)) {
                $ext = pathinfo($template->file_path, PATHINFO_EXTENSION) ?: 'pdf';
                $newPath = "esign/{$tenantId}/contracts/".Str::random(40).".{$ext}";
                Storage::disk('local')->copy($template->file_path, $newPath);

                $filePath = $newPath;
                $fileName = $template->file_name;
                $fileHash = $template->file_hash;
                $fileSize = $template->file_size;
            }
        }

        $contract = EsignContract::create([
            'tenant_id' => $tenantId,
            'contract_code' => $contractCode,
            'title' => $request->input('title'),
            'description' => $request->input('description'),
            'sign_order_type' => $request->input('sign_order_type'),
            'original_file_path' => $filePath,
            'original_file_name' => $fileName,
            'original_file_hash' => $fileHash,
            'original_file_size' => $fileSize,
            'status' => 'draft',
            'metadata' => $request->input('metadata'),
            'expires_at' => $request->input('expires_at')
                ? \Carbon\Carbon::parse($request->input('expires_at'))
                : now()->addDays($request->input('expires_days', 30)),
            'created_by' => $userId,
            'updated_by' => $userId,
        ]);

        // 서명자 생성
        $signers = $request->input('signers');
        foreach ($signers as $i => $signerData) {
            EsignSigner::create([
                'tenant_id' => $tenantId,
                'contract_id' => $contract->id,
                'role' => $signerData['role'],
                'sign_order' => $signerData['role'] === 'creator'
                    ? ($request->input('sign_order_type') === 'creator_first' ? 1 : 2)
                    : ($request->input('sign_order_type') === 'counterpart_first' ? 1 : 2),
                'name' => $signerData['name'],
                'email' => $signerData['email'],
                'phone' => $signerData['phone'] ?? null,
                'access_token' => Str::random(128),
                'token_expires_at' => now()->addDays($request->input('expires_days', 30)),
                'status' => 'waiting',
            ]);
        }

        // 법인도장 자동 적용: 설정에서 도장 이미지를 읽어 signer에 복사
        $stampSetting = TenantSetting::where('tenant_id', $tenantId)
            ->where('setting_group', 'esign')
            ->where('setting_key', 'company_stamp')
            ->first();

        if ($stampSetting) {
            $creatorSigner = EsignSigner::withoutGlobalScopes()
                ->where('contract_id', $contract->id)
                ->where('role', 'creator')
                ->first();

            if ($creatorSigner) {
                $val = $stampSetting->setting_value;
                $imageData = null;

                if (! empty($val['gcs_object'])) {
                    $gcs = app(GoogleCloudStorageService::class);
                    $signedUrl = $gcs->getSignedUrl($val['gcs_object'], 5);
                    if ($signedUrl) {
                        $imageData = @file_get_contents($signedUrl);
                    }
                } elseif (! empty($val['local_path']) && Storage::disk('local')->exists($val['local_path'])) {
                    $imageData = Storage::disk('local')->get($val['local_path']);
                }

                if ($imageData) {
                    $localPath = "esign/{$tenantId}/signatures/{$contract->id}_{$creatorSigner->id}_stamp.png";
                    Storage::disk('local')->put($localPath, $imageData);
                    $creatorSigner->update(['signature_image_path' => $localPath]);
                }
            }
        }

        // 템플릿 자동 적용 (template_id가 있으면 필드 자동 생성)
        $autoApplied = false;
        if ($request->input('template_id')) {
            $template = EsignFieldTemplate::forTenant($tenantId)
                ->where('is_active', true)->with('items')
                ->find($request->input('template_id'));

            if ($template && $template->items->isNotEmpty()) {
                $contract->loadMissing('signers');
                // 템플릿 signer_order를 역할(role)로 매핑: 1=creator(갑/회사), 2=counterpart(을/파트너)
                $signerMap = [];
                foreach ($contract->signers as $signer) {
                    $templateOrder = $signer->role === 'creator' ? 1 : 2;
                    $signerMap[$templateOrder] = $signer->id;
                }

                $variableValues = $this->buildVariableMap($contract, $template);

                foreach ($template->items as $item) {
                    $signerId = $signerMap[$item->signer_order] ?? null;
                    if (! $signerId) {
                        continue;
                    }

                    $fieldValue = null;
                    if ($item->field_variable && isset($variableValues[$item->field_variable])) {
                        $fieldValue = $variableValues[$item->field_variable];
                    }

                    EsignSignField::create([
                        'tenant_id' => $tenantId,
                        'contract_id' => $contract->id,
                        'signer_id' => $signerId,
                        'page_number' => $item->page_number,
                        'position_x' => $item->position_x,
                        'position_y' => $item->position_y,
                        'width' => $item->width,
                        'height' => $item->height,
                        'field_type' => $item->field_type,
                        'field_label' => $item->field_label,
                        'field_variable' => $item->field_variable,
                        'font_size' => $item->font_size,
                        'text_align' => $item->text_align ?? 'L',
                        'field_value' => $fieldValue,
                        'is_required' => $item->is_required,
                        'sort_order' => $item->sort_order,
                    ]);
                }
                $autoApplied = true;
            }
        }

        // 감사 로그
        EsignAuditLog::create([
            'tenant_id' => $tenantId,
            'contract_id' => $contract->id,
            'action' => 'contract_created',
            'ip_address' => $request->ip(),
            'user_agent' => $request->userAgent(),
            'metadata' => ['created_by' => $userId],
            'created_at' => now(),
        ]);

        return response()->json([
            'success' => true,
            'message' => '계약이 생성되었습니다.',
            'data' => $contract->load('signers'),
            'auto_applied' => $autoApplied,
        ]);
    }

    /**
     * 계약 취소
     */
    public function cancel(Request $request, int $id): JsonResponse
    {
        $tenantId = session('selected_tenant_id', 1);
        $contract = EsignContract::forTenant($tenantId)->findOrFail($id);

        if (in_array($contract->status, ['completed', 'cancelled'])) {
            return response()->json(['success' => false, 'message' => '취소할 수 없는 상태입니다.'], 422);
        }

        $contract->update([
            'status' => 'cancelled',
            'updated_by' => auth()->id(),
        ]);

        EsignAuditLog::create([
            'tenant_id' => $tenantId,
            'contract_id' => $contract->id,
            'action' => 'contract_cancelled',
            'ip_address' => $request->ip(),
            'user_agent' => $request->userAgent(),
            'metadata' => ['cancelled_by' => auth()->id()],
            'created_at' => now(),
        ]);

        return response()->json(['success' => true, 'message' => '계약이 취소되었습니다.']);
    }

    /**
     * 계약 삭제 - 휴지통으로 이동 (SoftDelete)
     */
    public function destroy(Request $request): JsonResponse
    {
        $request->validate([
            'ids' => 'required|array|min:1',
            'ids.*' => 'required|integer',
        ]);

        $tenantId = session('selected_tenant_id', 1);
        $ids = $request->input('ids');

        $contracts = EsignContract::forTenant($tenantId)->whereIn('id', $ids)->get();

        if ($contracts->isEmpty()) {
            return response()->json(['success' => false, 'message' => '삭제할 계약을 찾을 수 없습니다.'], 404);
        }

        // 진행 중인 계약(pending, partially_signed) 차단
        $activeContracts = $contracts->filter(fn ($c) => in_array($c->status, ['pending', 'partially_signed']));
        if ($activeContracts->isNotEmpty()) {
            return response()->json([
                'success' => false,
                'message' => '서명 진행 중인 계약은 삭제할 수 없습니다. 먼저 취소해 주세요.',
            ], 422);
        }

        $deletedCount = 0;
        foreach ($contracts as $contract) {
            $contract->update(['deleted_by' => auth()->id()]);
            $contract->delete(); // SoftDelete → deleted_at 설정
            $deletedCount++;
        }

        return response()->json([
            'success' => true,
            'message' => "{$deletedCount}건의 계약이 휴지통으로 이동되었습니다.",
        ]);
    }

    /**
     * 휴지통 목록
     */
    public function trashed(Request $request): JsonResponse
    {
        $tenantId = session('selected_tenant_id', 1);
        $query = EsignContract::onlyTrashed()
            ->where('tenant_id', $tenantId)
            ->with(['signers:id,contract_id,name,role,status']);

        if ($search = $request->input('search')) {
            $query->where(function ($q) use ($search) {
                $q->where('title', 'like', "%{$search}%")
                    ->orWhere('contract_code', 'like', "%{$search}%");
            });
        }

        $perPage = $request->input('per_page', 20);
        $data = $query->orderBy('deleted_at', 'desc')->paginate($perPage);

        return response()->json(['success' => true, 'data' => $data]);
    }

    /**
     * 휴지통에서 복구
     */
    public function restore(Request $request): JsonResponse
    {
        $request->validate([
            'ids' => 'required|array|min:1',
            'ids.*' => 'required|integer',
        ]);

        $tenantId = session('selected_tenant_id', 1);
        $contracts = EsignContract::onlyTrashed()
            ->where('tenant_id', $tenantId)
            ->whereIn('id', $request->input('ids'))
            ->get();

        if ($contracts->isEmpty()) {
            return response()->json(['success' => false, 'message' => '복구할 계약을 찾을 수 없습니다.'], 404);
        }

        $restoredCount = 0;
        foreach ($contracts as $contract) {
            $contract->update(['deleted_by' => null]);
            $contract->restore();
            $restoredCount++;
        }

        return response()->json([
            'success' => true,
            'message' => "{$restoredCount}건의 계약이 복구되었습니다.",
        ]);
    }

    /**
     * 영구 삭제
     */
    public function forceDestroy(Request $request): JsonResponse
    {
        $request->validate([
            'ids' => 'required|array|min:1',
            'ids.*' => 'required|integer',
        ]);

        $tenantId = session('selected_tenant_id', 1);
        $contracts = EsignContract::onlyTrashed()
            ->where('tenant_id', $tenantId)
            ->whereIn('id', $request->input('ids'))
            ->get();

        if ($contracts->isEmpty()) {
            return response()->json(['success' => false, 'message' => '삭제할 계약을 찾을 수 없습니다.'], 404);
        }

        $deletedCount = 0;
        foreach ($contracts as $contract) {
            // 관련 파일 삭제
            if ($contract->original_file_path && Storage::disk('local')->exists($contract->original_file_path)) {
                Storage::disk('local')->delete($contract->original_file_path);
            }
            if ($contract->signed_file_path && Storage::disk('local')->exists($contract->signed_file_path)) {
                Storage::disk('local')->delete($contract->signed_file_path);
            }

            // 서명 이미지 파일 삭제
            $signers = EsignSigner::withoutGlobalScopes()->where('contract_id', $contract->id)->get();
            foreach ($signers as $signer) {
                if ($signer->signature_image_path && Storage::disk('local')->exists($signer->signature_image_path)) {
                    Storage::disk('local')->delete($signer->signature_image_path);
                }
            }

            // 관련 레코드 영구 삭제
            EsignSignField::where('contract_id', $contract->id)->delete();
            EsignAuditLog::where('contract_id', $contract->id)->delete();
            EsignSigner::withoutGlobalScopes()->where('contract_id', $contract->id)->delete();
            $contract->forceDelete();
            $deletedCount++;
        }

        return response()->json([
            'success' => true,
            'message' => "{$deletedCount}건의 계약이 영구 삭제되었습니다.",
        ]);
    }

    /**
     * 서명 위치 설정
     */
    public function configureFields(Request $request, int $id): JsonResponse
    {
        $request->validate([
            'fields' => 'required|array',
            'fields.*.signer_id' => 'required|integer',
            'fields.*.page_number' => 'required|integer|min:1',
            'fields.*.position_x' => 'required|numeric',
            'fields.*.position_y' => 'required|numeric',
            'fields.*.width' => 'required|numeric',
            'fields.*.height' => 'required|numeric',
            'fields.*.field_type' => 'required|in:signature,stamp,text,date,checkbox',
            'fields.*.field_label' => 'nullable|string|max:100',
            'fields.*.field_variable' => 'nullable|string|max:50',
            'fields.*.font_size' => 'nullable|integer|min:6|max:72',
            'fields.*.text_align' => 'nullable|string|in:L,C,R',
            'fields.*.is_required' => 'nullable|boolean',
        ]);

        $tenantId = session('selected_tenant_id', 1);
        $contract = EsignContract::forTenant($tenantId)->with('signers')->findOrFail($id);

        // 변수 맵 구성 (field_variable → 실제 값 매핑)
        $variableValues = $this->buildVariableMap($contract);

        // 기존 필드 삭제 후 새로 생성
        EsignSignField::where('contract_id', $contract->id)->delete();

        foreach ($request->input('fields') as $i => $field) {
            // field_variable이 있으면 변수 맵에서 값 조회
            $fieldValue = null;
            $fieldVariable = $field['field_variable'] ?? null;
            if ($fieldVariable && isset($variableValues[$fieldVariable])) {
                $fieldValue = $variableValues[$fieldVariable];
            }

            EsignSignField::create([
                'tenant_id' => $tenantId,
                'contract_id' => $contract->id,
                'signer_id' => $field['signer_id'],
                'page_number' => $field['page_number'],
                'position_x' => $field['position_x'],
                'position_y' => $field['position_y'],
                'width' => $field['width'],
                'height' => $field['height'],
                'field_type' => $field['field_type'],
                'field_label' => $field['field_label'] ?? null,
                'field_variable' => $fieldVariable,
                'font_size' => $field['font_size'] ?? null,
                'text_align' => $field['text_align'] ?? 'L',
                'field_value' => $fieldValue,
                'is_required' => $field['is_required'] ?? true,
                'sort_order' => $i,
            ]);
        }

        return response()->json(['success' => true, 'message' => '서명 위치가 설정되었습니다.']);
    }

    /**
     * 서명 요청 발송 (상태 변경 + 이메일은 추후)
     */
    public function send(Request $request, int $id): JsonResponse
    {
        $tenantId = session('selected_tenant_id', 1);
        $contract = EsignContract::forTenant($tenantId)->with('signers')->findOrFail($id);

        if ($contract->status !== 'draft') {
            return response()->json(['success' => false, 'message' => '초안 상태에서만 발송할 수 있습니다.'], 422);
        }

        $sendMethod = $request->input('send_method', 'email');
        $smsFallback = $request->boolean('sms_fallback', true);
        $templateName = $request->input('template_name');

        $contract->update([
            'status' => 'pending',
            'send_method' => $sendMethod,
            'sms_fallback' => $smsFallback,
            'updated_by' => auth()->id(),
        ]);

        // 발송 대상 서명자 결정
        if ($contract->sign_order_type === 'parallel') {
            $targetSigners = $contract->signers;
        } else {
            $first = $contract->signers()->orderBy('sign_order')->first();
            $targetSigners = $first ? collect([$first]) : collect();
        }

        $notificationResults = [];
        foreach ($targetSigners as $signer) {
            $signer->update(['status' => 'notified']);
            $results = $this->dispatchNotification($contract, $signer, $sendMethod, $smsFallback, templateName: $templateName);
            $notificationResults[] = [
                'signer_id' => $signer->id,
                'signer_name' => $signer->name,
                'results' => $results,
            ];
        }

        EsignAuditLog::create([
            'tenant_id' => $tenantId,
            'contract_id' => $contract->id,
            'action' => 'sign_request_sent',
            'ip_address' => $request->ip(),
            'user_agent' => $request->userAgent(),
            'metadata' => [
                'sent_by' => auth()->id(),
                'send_method' => $sendMethod,
                'notification_results' => $notificationResults,
            ],
            'created_at' => now(),
        ]);

        // 실패한 알림 확인
        $failures = [];
        foreach ($notificationResults as $nr) {
            foreach ($nr['results'] as $r) {
                if (! $r['success']) {
                    $failures[] = "{$nr['signer_name']}: {$r['channel']} 실패 ({$r['error']})";
                }
            }
        }

        $message = '서명 요청이 발송되었습니다.';
        if (! empty($failures)) {
            $message .= ' (일부 알림 실패: '.implode(', ', $failures).')';
        }

        return response()->json([
            'success' => true,
            'message' => $message,
            'notification_results' => $notificationResults,
        ]);
    }

    /**
     * 리마인더 발송
     */
    public function remind(Request $request, int $id): JsonResponse
    {
        $tenantId = session('selected_tenant_id', 1);
        $contract = EsignContract::forTenant($tenantId)->with('signers')->findOrFail($id);

        if (! in_array($contract->status, ['pending', 'partially_signed'])) {
            return response()->json(['success' => false, 'message' => '리마인더를 발송할 수 없는 상태입니다.'], 422);
        }

        // 다음 서명 대상자 찾기
        $nextSigner = $contract->signers()
            ->whereIn('status', ['waiting', 'notified'])
            ->orderBy('sign_order')
            ->first();

        // 요청에서 발송 방식 지정이 있으면 우선 사용, 없으면 계약 저장값, 최종 기본값은 email
        $sendMethod = $request->input('send_method') ?: ($contract->send_method ?? 'email');

        $notificationResults = [];
        if ($nextSigner) {
            $nextSigner->update(['status' => 'notified']);
            $results = $this->dispatchNotification(
                $contract, $nextSigner,
                $sendMethod,
                $contract->sms_fallback ?? true,
                isReminder: true,
            );
            $notificationResults[] = [
                'signer_id' => $nextSigner->id,
                'signer_name' => $nextSigner->name,
                'results' => $results,
            ];
        }

        EsignAuditLog::create([
            'tenant_id' => $tenantId,
            'contract_id' => $contract->id,
            'action' => 'reminded',
            'ip_address' => $request->ip(),
            'user_agent' => $request->userAgent(),
            'metadata' => [
                'reminded_by' => auth()->id(),
                'target_signer_id' => $nextSigner?->id,
                'notification_results' => $notificationResults,
            ],
            'created_at' => now(),
        ]);

        // 실패 확인
        $failures = [];
        foreach ($notificationResults as $nr) {
            foreach ($nr['results'] as $r) {
                if (! $r['success']) {
                    $failures[] = "{$r['channel']} 실패 ({$r['error']})";
                }
            }
        }

        $message = $nextSigner
            ? "{$nextSigner->name}에게 리마인더가 발송되었습니다."
            : '리마인더가 기록되었습니다.';
        if (! empty($failures)) {
            $message .= ' (일부 알림 실패: '.implode(', ', $failures).')';
        }

        return response()->json([
            'success' => true,
            'message' => $message,
            'notification_results' => $notificationResults,
        ]);
    }

    /**
     * 발송 방식에 따라 알림톡/이메일 분기 발송
     */
    private function dispatchNotification(
        EsignContract $contract,
        EsignSigner $signer,
        string $sendMethod,
        bool $smsFallback,
        bool $isReminder = false,
        ?string $templateName = null,
    ): array {
        $results = [];
        $alimtalkFailed = false;
        $isCounterpart = $signer->role === EsignSigner::ROLE_COUNTERPART;

        // 알림톡 발송: 상대방(counterpart)에게만 카카오톡 발송, 본사(creator)는 이메일
        if (in_array($sendMethod, ['alimtalk', 'both']) && $isCounterpart && $signer->phone) {
            $alimtalkResult = $this->sendAlimtalk($contract, $signer, $smsFallback, $isReminder, $templateName);
            $results[] = $alimtalkResult;
            $alimtalkFailed = ! ($alimtalkResult['success'] ?? false);
        }

        // 이메일 발송 조건:
        // 1) email/both 선택 시
        // 2) 본사(creator)는 항상 이메일
        // 3) 상대방이지만 전화번호 없으면 이메일 폴백
        // 4) 알림톡 발송 실패 시 이메일 자동 폴백
        $shouldSendEmail = in_array($sendMethod, ['email', 'both'])
            || ! $isCounterpart
            || ($sendMethod === 'alimtalk' && $isCounterpart && ! $signer->phone)
            || ($sendMethod === 'alimtalk' && $isCounterpart && $alimtalkFailed);

        if ($shouldSendEmail && $signer->email) {
            try {
                Mail::to($signer->email)->send(new EsignRequestMail($contract, $signer, $isReminder));
                $results[] = ['success' => true, 'channel' => 'email', 'error' => null];
            } catch (\Throwable $e) {
                \Log::warning('E-Sign 이메일 발송 실패', [
                    'contract_id' => $contract->id,
                    'signer_id' => $signer->id,
                    'error' => $e->getMessage(),
                ]);
                $results[] = ['success' => false, 'channel' => 'email', 'error' => $e->getMessage()];
            }
        }

        return $results;
    }

    /**
     * 알림톡 발송
     */
    private function sendAlimtalk(
        EsignContract $contract,
        EsignSigner $signer,
        bool $smsFallback = true,
        bool $isReminder = false,
        ?string $templateName = null,
    ): array {
        try {
            $member = BarobillMember::where('tenant_id', $contract->tenant_id)->first();
            if (! $member) {
                return ['success' => false, 'channel' => 'alimtalk', 'error' => '바로빌 회원 미등록'];
            }

            if (! $member->biz_no) {
                return ['success' => false, 'channel' => 'alimtalk', 'error' => '사업자번호 미설정 (알림톡 발송 불가)'];
            }

            $barobill = app(BarobillService::class);
            $barobill->setServerMode($member->server_mode ?? 'production');

            // 카카오톡 채널 ID 조회 (YellowId로 사용)
            $channelId = $this->getKakaotalkChannelId($barobill, $member->biz_no);
            if (! $channelId) {
                return ['success' => false, 'channel' => 'alimtalk', 'error' => '등록된 카카오톡 채널이 없습니다'];
            }

            $signUrl = config('app.url').'/esign/sign/'.$signer->access_token;
            $expires = $contract->expires_at?->format('Y-m-d H:i') ?? '없음';

            if (! $templateName) {
                $templateName = $this->resolveTemplateName($isReminder ? '전자계약_리마인드' : '전자계약_서명요청');
            }

            // 등록된 템플릿 본문 + 버튼 정보 조회 (정확한 포맷 유지)
            $tplData = $this->getTemplateData($barobill, $member->biz_no, $channelId, $templateName);
            $templateContent = $tplData['content'];
            $templateButtons = $tplData['buttons'];

            if ($templateContent) {
                $message = str_replace(
                    ['#{이름}', '#{계약명}', '#{기한}'],
                    [$signer->name, $contract->title, $expires],
                    $templateContent
                );
            } else {
                \Log::warning('E-Sign 알림톡: 템플릿 내용 조회 실패, 하드코딩 폴백 사용', [
                    'template_name' => $templateName,
                    'channel_id' => $channelId,
                ]);
                $message = $isReminder
                    ? "안녕하세요, {$signer->name}님.\n아직 서명이 완료되지 않은 전자계약이 있습니다.\n\n ■ 계약명: {$contract->title}\n ■ 서명 기한: {$expires}\n\n 기한 내에 서명을 완료해 주세요."
                    : " 안녕하세요, {$signer->name}님. \n 전자계약 서명 요청이 도착했습니다.\n\n ■ 계약명: {$contract->title}\n ■ 서명 기한: {$expires}\n\n 아래 버튼을 눌러 계약서를 확인하고 서명해 주세요.";
            }

            // 버튼: 템플릿에서 가져온 URL의 #{토큰}만 치환 (도메인은 템플릿 등록값 유지 — 카카오 검증)
            $buttons = ! empty($templateButtons) ? $templateButtons : [
                ['Name' => '계약서 확인하기', 'ButtonType' => 'WL',
                    'Url1' => $signUrl, 'Url2' => $signUrl],
            ];

            foreach ($buttons as &$btn) {
                foreach (['Url1', 'Url2'] as $urlKey) {
                    if (! empty($btn[$urlKey])) {
                        $btn[$urlKey] = str_replace(
                            ['#{토큰}', '#{%ED%86%A0%ED%81%B0}'],
                            [$signer->access_token, $signer->access_token],
                            urldecode($btn[$urlKey])
                        );
                    }
                }
            }
            unset($btn);

            $receiverNum = preg_replace('/[^0-9]/', '', $signer->phone);

            \Log::info('E-Sign 알림톡 발송 시도', [
                'contract_id' => $contract->id,
                'signer_id' => $signer->id,
                'template_name' => $templateName,
                'template_from_api' => (bool) $templateContent,
                'buttons_from_api' => ! empty($templateButtons),
                'receiver_num' => $receiverNum,
            ]);

            $result = $barobill->sendATKakaotalkEx(
                corpNum: $member->biz_no,
                senderId: $member->barobill_id,
                yellowId: $channelId,
                templateName: $templateName,
                receiverName: $signer->name,
                receiverNum: $receiverNum,
                title: '',
                message: $message,
                buttons: $buttons,
            );

            // 발송 접수 후 결과 확인 (SendKey 반환 시)
            if (($result['success'] ?? false) && ! empty($result['data']) && is_string($result['data'])) {
                $sendKey = $result['data'];
                \Log::info('E-Sign 알림톡 접수 성공', [
                    'contract_id' => $contract->id,
                    'send_key' => $sendKey,
                ]);

                // 3초 후 전달 결과 확인
                sleep(3);
                $sendResult = $barobill->getSendKakaotalk($member->biz_no, $sendKey);
                $resultData = $sendResult['data'] ?? null;
                $resultCode = is_object($resultData) ? ($resultData->ResultCode ?? null) : ($resultData['ResultCode'] ?? null);
                $resultMsg = is_object($resultData) ? ($resultData->ResultMessage ?? null) : ($resultData['ResultMessage'] ?? null);

                \Log::info('E-Sign 알림톡 전달 결과', [
                    'contract_id' => $contract->id,
                    'send_key' => $sendKey,
                    'result_code' => $resultCode,
                    'result_message' => $resultMsg,
                ]);

                // ResultCode 1 = 성공, 그 외 = 실패
                if ($resultCode !== null && $resultCode != 1) {
                    return [
                        'success' => false,
                        'channel' => 'alimtalk',
                        'error' => "카카오톡 전달 실패: {$resultMsg} (코드: {$resultCode})",
                    ];
                }
            }

            if (! ($result['success'] ?? false)) {
                \Log::warning('E-Sign 알림톡 발송 실패', [
                    'contract_id' => $contract->id,
                    'signer_id' => $signer->id,
                    'error' => $result['error'] ?? 'Unknown error',
                ]);

                return ['success' => false, 'channel' => 'alimtalk', 'error' => $result['error'] ?? 'API 호출 실패'];
            }

            return ['success' => true, 'channel' => 'alimtalk', 'error' => null];
        } catch (\Throwable $e) {
            \Log::warning('E-Sign 알림톡 발송 실패', [
                'contract_id' => $contract->id,
                'signer_id' => $signer->id,
                'error' => $e->getMessage(),
            ]);

            return ['success' => false, 'channel' => 'alimtalk', 'error' => $e->getMessage()];
        }
    }

    /**
     * 바로빌 카카오톡 채널 ID 조회 (YellowId로 사용)
     */
    private function getKakaotalkChannelId(BarobillService $barobill, string $bizNo): ?string
    {
        $result = $barobill->getKakaotalkChannels($bizNo);

        if (! ($result['success'] ?? false) || empty($result['data'])) {
            return null;
        }

        $data = $result['data'];

        if (is_object($data) && isset($data->KakaotalkChannel)) {
            $channels = is_array($data->KakaotalkChannel)
                ? $data->KakaotalkChannel
                : [$data->KakaotalkChannel];
        } elseif (is_array($data) && isset($data['KakaotalkChannel'])) {
            $channels = is_array($data['KakaotalkChannel'])
                ? $data['KakaotalkChannel']
                : [$data['KakaotalkChannel']];
        } else {
            $channels = is_array($data) ? $data : [$data];
        }

        $channel = $channels[0] ?? null;

        if (! $channel) {
            return null;
        }

        return is_array($channel)
            ? ($channel['ChannelId'] ?? null)
            : ($channel->ChannelId ?? null);
    }

    /**
     * 바로빌 등록 템플릿의 본문 내용 및 버튼 정보 조회
     *
     * @return array{content: string|null, buttons: array}
     */
    /**
     * 환경별 알림톡 템플릿명 반환 (운영: 원본, 개발: _DEV 접미사)
     */
    private function resolveTemplateName(string $baseName): string
    {
        return $baseName.(app()->environment('production') ? '' : '_DEV');
    }

    private function getTemplateData(BarobillService $barobill, string $bizNo, string $channelId, string $templateName): array
    {
        $empty = ['content' => null, 'buttons' => []];

        $result = $barobill->getKakaotalkTemplates($bizNo, $channelId);

        if (! ($result['success'] ?? false) || empty($result['data'])) {
            return $empty;
        }

        $data = $result['data'];
        $items = [];

        if (is_object($data) && isset($data->KakaotalkTemplate)) {
            $items = is_array($data->KakaotalkTemplate)
                ? $data->KakaotalkTemplate
                : [$data->KakaotalkTemplate];
        }

        foreach ($items as $tpl) {
            if (($tpl->TemplateName ?? '') === $templateName) {
                $buttons = [];
                $btnData = $tpl->Buttons ?? null;
                if ($btnData) {
                    $btnList = $btnData->KakaotalkButton ?? null;
                    if ($btnList) {
                        $btnList = is_array($btnList) ? $btnList : [$btnList];
                        foreach ($btnList as $btn) {
                            $buttons[] = [
                                'Name' => $btn->Name ?? '',
                                'ButtonType' => $btn->ButtonType ?? 'WL',
                                'Url1' => $btn->Url1 ?? '',
                                'Url2' => $btn->Url2 ?? '',
                            ];
                        }
                    }
                }

                return [
                    'content' => $tpl->TemplateContent ?? null,
                    'buttons' => $buttons,
                ];
            }
        }

        return $empty;
    }

    /**
     * 바로빌 등록 알림톡 템플릿 목록 조회 (승인 완료된 것만)
     */
    public function getAlimtalkTemplates(): JsonResponse
    {
        try {
            $tenantId = session('selected_tenant_id', 1);
            $member = BarobillMember::where('tenant_id', $tenantId)->first();

            if (! $member || ! $member->biz_no) {
                return response()->json([
                    'success' => false,
                    'message' => '바로빌 회원 정보 또는 사업자번호가 설정되지 않았습니다.',
                ]);
            }

            $barobill = app(BarobillService::class);
            $barobill->setServerMode($member->server_mode ?? 'production');

            $channelId = $this->getKakaotalkChannelId($barobill, $member->biz_no);
            if (! $channelId) {
                return response()->json([
                    'success' => false,
                    'message' => '등록된 카카오톡 채널이 없습니다.',
                ]);
            }

            $result = $barobill->getKakaotalkTemplates($member->biz_no, $channelId);
            if (! ($result['success'] ?? false) || empty($result['data'])) {
                return response()->json([
                    'success' => false,
                    'message' => '템플릿 목록을 조회할 수 없습니다.',
                ]);
            }

            $data = $result['data'];
            $items = [];
            if (is_object($data) && isset($data->KakaotalkTemplate)) {
                $items = is_array($data->KakaotalkTemplate)
                    ? $data->KakaotalkTemplate
                    : [$data->KakaotalkTemplate];
            }

            // 승인(Status=3)된 템플릿만 필터링
            $templates = [];
            foreach ($items as $tpl) {
                $status = $tpl->Status ?? null;
                if ($status != 3) {
                    continue;
                }

                $buttons = [];
                $btnData = $tpl->Buttons ?? null;
                if ($btnData) {
                    $btnList = $btnData->KakaotalkButton ?? null;
                    if ($btnList) {
                        $btnList = is_array($btnList) ? $btnList : [$btnList];
                        foreach ($btnList as $btn) {
                            $buttons[] = [
                                'Name' => $btn->Name ?? '',
                                'ButtonType' => $btn->ButtonType ?? 'WL',
                                'Url1' => $btn->Url1 ?? '',
                                'Url2' => $btn->Url2 ?? '',
                            ];
                        }
                    }
                }

                $templates[] = [
                    'name' => $tpl->TemplateName ?? '',
                    'content' => $tpl->TemplateContent ?? '',
                    'status' => $status,
                    'buttons' => $buttons,
                ];
            }

            return response()->json([
                'success' => true,
                'data' => [
                    'channel_id' => $channelId,
                    'templates' => $templates,
                ],
            ]);
        } catch (\Throwable $e) {
            \Log::error('알림톡 템플릿 목록 조회 실패', ['error' => $e->getMessage()]);

            return response()->json([
                'success' => false,
                'message' => '템플릿 목록 조회 중 오류: '.$e->getMessage(),
            ]);
        }
    }

    /**
     * PDF 다운로드
     */
    public function download(int $id)
    {
        $tenantId = session('selected_tenant_id', 1);
        $contract = EsignContract::forTenant($tenantId)->findOrFail($id);

        $filePath = $contract->signed_file_path ?: $contract->original_file_path;
        if (! $filePath || ! \Storage::disk('local')->exists($filePath)) {
            abort(404, 'PDF 파일을 찾을 수 없습니다.');
        }

        $fileName = $contract->original_file_name ?: 'contract.pdf';

        return \Storage::disk('local')->download($filePath, $fileName, [
            'Content-Type' => 'application/pdf',
        ]);
    }

    /**
     * PDF 업로드 (PDF 없이 생성된 계약에 나중에 업로드)
     */
    public function uploadPdf(Request $request, int $id): JsonResponse
    {
        $request->validate([
            'file' => 'required|file|mimes:pdf,doc,docx|max:20480',
        ]);

        $tenantId = session('selected_tenant_id', 1);
        $contract = EsignContract::forTenant($tenantId)->findOrFail($id);

        if ($contract->original_file_path) {
            return response()->json(['success' => false, 'message' => '이미 PDF 파일이 존재합니다.'], 422);
        }

        $file = $request->file('file');
        $converter = new DocxToPdfConverter;
        $result = $converter->convertAndStore($file, "esign/{$tenantId}/contracts");

        $contract->update([
            'original_file_path' => $result['path'],
            'original_file_name' => $result['name'],
            'original_file_hash' => $result['hash'],
            'original_file_size' => $result['size'],
            'updated_by' => auth()->id(),
        ]);

        return response()->json([
            'success' => true,
            'message' => 'PDF 파일이 업로드되었습니다.',
            'data' => ['path' => $result['path'], 'name' => $result['name']],
        ]);
    }

    /**
     * 템플릿 PDF 다운로드
     */
    public function downloadTemplatePdf(int $id)
    {
        $tenantId = session('selected_tenant_id', 1);
        $template = EsignFieldTemplate::forTenant($tenantId)->findOrFail($id);

        if (! $template->file_path || ! Storage::disk('local')->exists($template->file_path)) {
            abort(404, 'PDF 파일을 찾을 수 없습니다.');
        }

        $fileName = $template->file_name ?: 'template.pdf';

        return Storage::disk('local')->download($template->file_path, $fileName, [
            'Content-Type' => 'application/pdf',
        ]);
    }

    // ─── 필드 템플릿 관련 메서드 ───

    /**
     * 템플릿 목록 조회
     */
    public function indexTemplates(Request $request): JsonResponse
    {
        $tenantId = session('selected_tenant_id', 1);
        $query = EsignFieldTemplate::forTenant($tenantId)
            ->where('is_active', true);

        if ($category = $request->input('category')) {
            $query->where('category', $category);
        }
        if ($search = $request->input('search')) {
            $query->where('name', 'like', "%{$search}%");
        }
        if ($signerCount = $request->input('signer_count')) {
            $query->where('signer_count', $signerCount);
        }

        $templates = $query->withCount('items')->with('creator:id,name')->latest()->get();

        return response()->json(['success' => true, 'data' => $templates]);
    }

    /**
     * 템플릿 저장 (현재 필드를 템플릿으로)
     */
    public function storeTemplate(Request $request): JsonResponse
    {
        $request->validate([
            'name' => 'required|string|max:100',
            'description' => 'nullable|string',
            'category' => 'nullable|string|max:50',
            'include_pdf' => 'nullable|boolean',
            'contract_id' => 'nullable|integer',
            'signer_count' => 'nullable|integer|min:1|max:6',
            'variables' => 'nullable|array',
            'items' => 'nullable|array',
            'items.*.signer_order' => 'required|integer|min:1',
            'items.*.page_number' => 'required|integer|min:1',
            'items.*.position_x' => 'required|numeric',
            'items.*.position_y' => 'required|numeric',
            'items.*.width' => 'required|numeric',
            'items.*.height' => 'required|numeric',
            'items.*.field_type' => 'required|in:signature,stamp,text,date,checkbox',
            'items.*.field_label' => 'nullable|string|max:100',
            'items.*.field_variable' => 'nullable|string|max:50',
            'items.*.font_size' => 'nullable|integer|min:6|max:72',
            'items.*.text_align' => 'nullable|string|in:L,C,R',
            'items.*.is_required' => 'nullable|boolean',
        ]);

        $tenantId = session('selected_tenant_id', 1);

        // signer_count 결정: 직접 지정 > items에서 추출 > 기본값 2
        $items = $request->input('items', []);
        $signerCount = $request->input('signer_count')
            ?: (count($items) > 0 ? max(array_column($items, 'signer_order')) : 2);

        // PDF 포함 여부 확인
        $includePdf = $request->boolean('include_pdf');
        $contractId = $request->input('contract_id');
        $sourceContract = null;

        if ($includePdf && $contractId) {
            $sourceContract = EsignContract::forTenant($tenantId)->find($contractId);
            if (! $sourceContract || ! $sourceContract->original_file_path || ! Storage::disk('local')->exists($sourceContract->original_file_path)) {
                $sourceContract = null;
            }
        }

        $template = DB::transaction(function () use ($tenantId, $request, $items, $signerCount, $sourceContract) {
            $fileData = [];

            if ($sourceContract) {
                $timestamp = now()->format('YmdHis');
                $ext = pathinfo($sourceContract->original_file_path, PATHINFO_EXTENSION) ?: 'pdf';
                $newPath = "esign/{$tenantId}/templates/{$timestamp}.{$ext}";
                Storage::disk('local')->copy($sourceContract->original_file_path, $newPath);

                $fileData = [
                    'file_path' => $newPath,
                    'file_name' => $sourceContract->original_file_name,
                    'file_hash' => $sourceContract->original_file_hash,
                    'file_size' => $sourceContract->original_file_size,
                ];
            }

            $template = EsignFieldTemplate::create(array_merge([
                'tenant_id' => $tenantId,
                'name' => $request->input('name'),
                'description' => $request->input('description'),
                'category' => $request->input('category'),
                'signer_count' => $signerCount,
                'variables' => $request->input('variables', []),
                'is_active' => true,
                'created_by' => auth()->id(),
            ], $fileData));

            foreach ($items as $i => $item) {
                EsignFieldTemplateItem::create([
                    'template_id' => $template->id,
                    'signer_order' => $item['signer_order'],
                    'page_number' => $item['page_number'],
                    'position_x' => $item['position_x'],
                    'position_y' => $item['position_y'],
                    'width' => $item['width'],
                    'height' => $item['height'],
                    'field_type' => $item['field_type'],
                    'field_label' => $item['field_label'] ?? null,
                    'field_variable' => $item['field_variable'] ?? null,
                    'font_size' => $item['font_size'] ?? null,
                    'text_align' => $item['text_align'] ?? 'L',
                    'is_required' => $item['is_required'] ?? true,
                    'sort_order' => $i,
                ]);
            }

            return $template;
        });

        return response()->json([
            'success' => true,
            'message' => '필드 템플릿이 저장되었습니다.',
            'data' => $template->load('items'),
        ]);
    }

    /**
     * 템플릿 단건 조회
     */
    public function showTemplate(int $id): JsonResponse
    {
        $tenantId = session('selected_tenant_id', 1);
        $template = EsignFieldTemplate::forTenant($tenantId)
            ->where('is_active', true)
            ->with(['items', 'creator:id,name'])
            ->findOrFail($id);

        return response()->json(['success' => true, 'data' => $template]);
    }

    /**
     * 템플릿 메타데이터 수정
     */
    public function updateTemplate(Request $request, int $id): JsonResponse
    {
        $request->validate([
            'name' => 'required|string|max:100',
            'description' => 'nullable|string',
            'category' => 'nullable|string|max:50',
            'variables' => 'nullable|array',
            'variables.*.key' => 'required|string|max:50',
            'variables.*.label' => 'required|string|max:100',
            'variables.*.type' => 'nullable|in:text,number,date',
            'variables.*.default' => 'nullable|string|max:500',
        ]);

        $tenantId = session('selected_tenant_id', 1);
        $template = EsignFieldTemplate::forTenant($tenantId)->findOrFail($id);

        $updateData = [
            'name' => $request->input('name'),
            'description' => $request->input('description'),
            'category' => $request->input('category'),
        ];

        if ($request->has('variables')) {
            $updateData['variables'] = $request->input('variables');
        }

        $template->update($updateData);

        return response()->json([
            'success' => true,
            'message' => '템플릿이 수정되었습니다.',
            'data' => $template->fresh()->load(['creator:id,name', 'items'])->loadCount('items'),
        ]);
    }

    /**
     * 템플릿 PDF 교체
     */
    public function uploadTemplatePdf(Request $request, int $id): JsonResponse
    {
        $request->validate([
            'file' => 'required|file|mimes:pdf,doc,docx|max:20480',
        ]);

        $tenantId = session('selected_tenant_id', 1);
        $template = EsignFieldTemplate::forTenant($tenantId)->findOrFail($id);

        // 기존 파일 삭제
        if ($template->file_path && Storage::disk('local')->exists($template->file_path)) {
            Storage::disk('local')->delete($template->file_path);
        }

        $file = $request->file('file');
        $converter = new DocxToPdfConverter;
        $result = $converter->convertAndStore($file, "esign/{$tenantId}/templates");

        $template->update([
            'file_path' => $result['path'],
            'file_name' => $result['name'],
            'file_hash' => $result['hash'],
            'file_size' => $result['size'],
        ]);

        return response()->json([
            'success' => true,
            'message' => 'PDF가 교체되었습니다.',
            'data' => $template->fresh()->load(['creator:id,name', 'items'])->loadCount('items'),
        ]);
    }

    /**
     * 템플릿 PDF 제거
     */
    public function removeTemplatePdf(int $id): JsonResponse
    {
        $tenantId = session('selected_tenant_id', 1);
        $template = EsignFieldTemplate::forTenant($tenantId)->findOrFail($id);

        if ($template->file_path && Storage::disk('local')->exists($template->file_path)) {
            Storage::disk('local')->delete($template->file_path);
        }

        $template->update([
            'file_path' => null,
            'file_name' => null,
            'file_hash' => null,
            'file_size' => null,
        ]);

        return response()->json([
            'success' => true,
            'message' => 'PDF가 제거되었습니다.',
            'data' => $template->fresh()->load(['creator:id,name', 'items'])->loadCount('items'),
        ]);
    }

    /**
     * 템플릿 필드 아이템 삭제
     */
    public function destroyTemplateItem(int $templateId, int $itemId): JsonResponse
    {
        $tenantId = session('selected_tenant_id', 1);
        $template = EsignFieldTemplate::forTenant($tenantId)->findOrFail($templateId);
        $item = EsignFieldTemplateItem::where('template_id', $template->id)->findOrFail($itemId);

        $item->delete();

        // signer_count 재계산
        $maxOrder = EsignFieldTemplateItem::where('template_id', $template->id)->max('signer_order');
        $template->update(['signer_count' => $maxOrder ?: 0]);

        return response()->json([
            'success' => true,
            'message' => '필드가 삭제되었습니다.',
            'data' => $template->fresh()->load(['creator:id,name', 'items'])->loadCount('items'),
        ]);
    }

    /**
     * 템플릿 필드 아이템 일괄 저장 (에디터에서 사용)
     */
    public function updateTemplateItems(Request $request, int $templateId): JsonResponse
    {
        $request->validate([
            'items' => 'present|array',
            'items.*.signer_order' => 'required|integer|min:1',
            'items.*.page_number' => 'required|integer|min:1',
            'items.*.position_x' => 'required|numeric',
            'items.*.position_y' => 'required|numeric',
            'items.*.width' => 'required|numeric',
            'items.*.height' => 'required|numeric',
            'items.*.field_type' => 'required|in:signature,stamp,text,date,checkbox',
            'items.*.field_label' => 'nullable|string|max:100',
            'items.*.field_variable' => 'nullable|string|max:50',
            'items.*.font_size' => 'nullable|integer|min:6|max:72',
            'items.*.text_align' => 'nullable|string|in:L,C,R',
            'items.*.is_required' => 'nullable|boolean',
        ]);

        $tenantId = session('selected_tenant_id', 1);
        $template = EsignFieldTemplate::forTenant($tenantId)->findOrFail($templateId);

        $items = $request->input('items', []);

        DB::transaction(function () use ($template, $items) {
            // 기존 아이템 삭제
            EsignFieldTemplateItem::where('template_id', $template->id)->delete();

            // 새 아이템 생성
            foreach ($items as $i => $itemData) {
                EsignFieldTemplateItem::create([
                    'template_id' => $template->id,
                    'signer_order' => $itemData['signer_order'],
                    'page_number' => $itemData['page_number'],
                    'position_x' => round($itemData['position_x'], 2),
                    'position_y' => round($itemData['position_y'], 2),
                    'width' => round($itemData['width'], 2),
                    'height' => round($itemData['height'], 2),
                    'field_type' => $itemData['field_type'],
                    'field_label' => $itemData['field_label'] ?? '',
                    'field_variable' => $itemData['field_variable'] ?? null,
                    'font_size' => $itemData['font_size'] ?? null,
                    'text_align' => $itemData['text_align'] ?? 'L',
                    'is_required' => $itemData['is_required'] ?? true,
                    'sort_order' => $i,
                ]);
            }

            // signer_count 업데이트
            $maxOrder = collect($items)->max('signer_order') ?: 0;
            $template->update(['signer_count' => $maxOrder]);
        });

        return response()->json([
            'success' => true,
            'message' => '템플릿 필드가 저장되었습니다.',
            'data' => $template->fresh()->load('items'),
        ]);
    }

    /**
     * 템플릿 복제
     */
    public function duplicateTemplate(int $id): JsonResponse
    {
        $tenantId = session('selected_tenant_id', 1);
        $template = EsignFieldTemplate::forTenant($tenantId)
            ->where('is_active', true)
            ->with('items')
            ->findOrFail($id);

        $newTemplate = DB::transaction(function () use ($template, $tenantId) {
            $fileData = [];
            if ($template->file_path && Storage::disk('local')->exists($template->file_path)) {
                $timestamp = now()->format('YmdHis');
                $ext = pathinfo($template->file_path, PATHINFO_EXTENSION) ?: 'pdf';
                $newPath = "esign/{$tenantId}/templates/{$timestamp}_copy.{$ext}";
                Storage::disk('local')->copy($template->file_path, $newPath);

                $fileData = [
                    'file_path' => $newPath,
                    'file_name' => $template->file_name,
                    'file_hash' => $template->file_hash,
                    'file_size' => $template->file_size,
                ];
            }

            $newTemplate = EsignFieldTemplate::create(array_merge([
                'tenant_id' => $tenantId,
                'name' => $template->name.' (복사)',
                'description' => $template->description,
                'category' => $template->category,
                'signer_count' => $template->signer_count,
                'variables' => $template->variables,
                'is_active' => true,
                'created_by' => auth()->id(),
            ], $fileData));

            foreach ($template->items as $item) {
                EsignFieldTemplateItem::create([
                    'template_id' => $newTemplate->id,
                    'signer_order' => $item->signer_order,
                    'page_number' => $item->page_number,
                    'position_x' => $item->position_x,
                    'position_y' => $item->position_y,
                    'width' => $item->width,
                    'height' => $item->height,
                    'field_type' => $item->field_type,
                    'field_label' => $item->field_label,
                    'field_variable' => $item->field_variable,
                    'font_size' => $item->font_size,
                    'is_required' => $item->is_required,
                    'sort_order' => $item->sort_order,
                ]);
            }

            return $newTemplate;
        });

        return response()->json([
            'success' => true,
            'message' => '템플릿이 복제되었습니다.',
            'data' => $newTemplate->load(['items', 'creator:id,name'])->loadCount('items'),
        ]);
    }

    /**
     * 템플릿 삭제 (soft: is_active=false)
     */
    public function destroyTemplate(int $id): JsonResponse
    {
        $tenantId = session('selected_tenant_id', 1);
        $template = EsignFieldTemplate::forTenant($tenantId)->findOrFail($id);

        $template->update(['is_active' => false]);

        return response()->json(['success' => true, 'message' => '템플릿이 삭제되었습니다.']);
    }

    /**
     * 템플릿을 계약에 적용
     */
    public function applyTemplate(Request $request, int $id): JsonResponse
    {
        $request->validate([
            'template_id' => 'required|integer',
        ]);

        $tenantId = session('selected_tenant_id', 1);
        $contract = EsignContract::forTenant($tenantId)->with('signers')->findOrFail($id);
        $template = EsignFieldTemplate::forTenant($tenantId)
            ->where('is_active', true)
            ->with('items')
            ->findOrFail($request->input('template_id'));

        // 서명자 수 확인
        $contractSignerCount = $contract->signers->count();
        if ($template->signer_count > $contractSignerCount) {
            return response()->json([
                'success' => false,
                'message' => "템플릿에 필요한 서명자 수({$template->signer_count}명)가 계약의 서명자 수({$contractSignerCount}명)보다 많습니다.",
            ], 422);
        }

        // signer_order → signer_id 매핑
        $signerMap = [];
        foreach ($contract->signers as $signer) {
            $signerMap[$signer->sign_order] = $signer->id;
        }

        // 변수 해석용 맵 구성
        $variableValues = $this->buildVariableMap($contract, $template);

        DB::transaction(function () use ($contract, $template, $tenantId, $signerMap, $variableValues) {
            // 기존 필드 삭제
            EsignSignField::where('contract_id', $contract->id)->delete();

            // 템플릿 아이템 → 필드 생성
            foreach ($template->items as $item) {
                $signerId = $signerMap[$item->signer_order] ?? null;
                if (! $signerId) {
                    continue;
                }

                // 변수가 바인딩된 필드는 자동 채움
                $fieldValue = null;
                if ($item->field_variable && isset($variableValues[$item->field_variable])) {
                    $fieldValue = $variableValues[$item->field_variable];
                }

                EsignSignField::create([
                    'tenant_id' => $tenantId,
                    'contract_id' => $contract->id,
                    'signer_id' => $signerId,
                    'page_number' => $item->page_number,
                    'position_x' => $item->position_x,
                    'position_y' => $item->position_y,
                    'width' => $item->width,
                    'height' => $item->height,
                    'field_type' => $item->field_type,
                    'field_label' => $item->field_label,
                    'field_variable' => $item->field_variable,
                    'font_size' => $item->font_size,
                    'field_value' => $fieldValue,
                    'is_required' => $item->is_required,
                    'sort_order' => $item->sort_order,
                ]);
            }
        });

        $fields = EsignSignField::where('contract_id', $contract->id)->orderBy('sort_order')->get();

        return response()->json([
            'success' => true,
            'message' => '템플릿이 적용되었습니다.',
            'data' => $fields,
        ]);
    }

    /**
     * 다른 계약에서 필드 복사
     */
    public function copyFieldsFromContract(Request $request, int $id, int $sourceId): JsonResponse
    {
        $tenantId = session('selected_tenant_id', 1);
        $targetContract = EsignContract::forTenant($tenantId)->with('signers')->findOrFail($id);
        $sourceContract = EsignContract::forTenant($tenantId)->with(['signers', 'signFields'])->findOrFail($sourceId);

        if ($sourceContract->signFields->isEmpty()) {
            return response()->json([
                'success' => false,
                'message' => '소스 계약에 복사할 필드가 없습니다.',
            ], 422);
        }

        // 소스 계약 서명자의 sign_order → signer_id 매핑
        $sourceSignerOrderMap = [];
        foreach ($sourceContract->signers as $signer) {
            $sourceSignerOrderMap[$signer->id] = $signer->sign_order;
        }

        // 대상 계약 sign_order → signer_id 매핑
        $targetSignerMap = [];
        foreach ($targetContract->signers as $signer) {
            $targetSignerMap[$signer->sign_order] = $signer->id;
        }

        // 대상 계약 기준 변수 맵 구성
        $variableValues = $this->buildVariableMap($targetContract);

        DB::transaction(function () use ($targetContract, $sourceContract, $tenantId, $sourceSignerOrderMap, $targetSignerMap, $variableValues) {
            // 기존 필드 삭제
            EsignSignField::where('contract_id', $targetContract->id)->delete();

            foreach ($sourceContract->signFields as $field) {
                // 소스 signer_id → sign_order → 대상 signer_id
                $signOrder = $sourceSignerOrderMap[$field->signer_id] ?? null;
                $targetSignerId = $signOrder ? ($targetSignerMap[$signOrder] ?? null) : null;
                if (! $targetSignerId) {
                    continue;
                }

                // field_variable이 있으면 대상 계약의 변수 맵에서 값 조회
                $fieldValue = null;
                if ($field->field_variable && isset($variableValues[$field->field_variable])) {
                    $fieldValue = $variableValues[$field->field_variable];
                }

                EsignSignField::create([
                    'tenant_id' => $tenantId,
                    'contract_id' => $targetContract->id,
                    'signer_id' => $targetSignerId,
                    'page_number' => $field->page_number,
                    'position_x' => $field->position_x,
                    'position_y' => $field->position_y,
                    'width' => $field->width,
                    'height' => $field->height,
                    'field_type' => $field->field_type,
                    'field_label' => $field->field_label,
                    'field_variable' => $field->field_variable,
                    'font_size' => $field->font_size,
                    'field_value' => $fieldValue,
                    'is_required' => $field->is_required,
                    'sort_order' => $field->sort_order,
                ]);
            }
        });

        $fields = EsignSignField::where('contract_id', $targetContract->id)->orderBy('sort_order')->get();

        return response()->json([
            'success' => true,
            'message' => '필드가 복사되었습니다.',
            'data' => $fields,
        ]);
    }

    /**
     * 변수 해석 맵 구성 (시스템 변수 + 커스텀 변수)
     */
    private function buildVariableMap(EsignContract $contract, ?EsignFieldTemplate $template = null): array
    {
        $map = [];

        // 시스템 변수: 서명자 정보 (역할 기반: 1=creator/갑/회사, 2=counterpart/을/파트너)
        $signers = $contract->signers->sortBy(fn ($s) => $s->role === 'creator' ? 1 : 2);
        $idx = 1;
        foreach ($signers as $signer) {
            $map["signer{$idx}_name"] = $signer->name;
            $map["signer{$idx}_email"] = $signer->email;
            $map["signer{$idx}_phone"] = $signer->phone ?? '';
            $idx++;
        }

        // 시스템 변수: 계약 정보
        $map['contract_title'] = $contract->title ?? '';
        $map['current_date'] = now()->format('Y년 n월 j일');
        $map['expires_at'] = $contract->expires_at ? $contract->expires_at->format('Y년 n월 j일') : '';

        // 커스텀 변수: contract.metadata에서 조회
        $metadata = $contract->metadata ?? [];
        foreach ($metadata as $key => $value) {
            $map[$key] = $value ?? '';
        }

        return $map;
    }
}
