<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\CreditTransaction;
use App\Models\Setting;
use App\Models\Voice;
use App\Services\CreditService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;

class TtsController extends Controller
{
    /**
     * Get VieNeu-TTS server base URL from Setting DB or config.
     */
    protected function getTtsBaseUrl(): string
    {
        return Setting::get(
            'service_url_vieneu_tts',
            config('services.vieneu_tts.url', env('VIENEU_TTS_URL', 'http://127.0.0.1:8001'))
        );
    }

    /**
     * GET /api/tts/voices
     * List all system voices + custom voices of the authenticated user.
     */
    public function index(Request $request)
    {
        $userId = $request->user()?->id;

        // 1. Query existing voices for user
        $voices = Voice::forUser($userId)
            ->orderBy('type', 'desc')
            ->orderBy('id', 'desc')
            ->get();

        // 2. Auto-sync if system voices are missing in DB
        $systemCount = Voice::whereNull('user_id')->where('type', 'system')->count();
        if ($systemCount === 0) {
            try {
                $ttsUrl = rtrim($this->getTtsBaseUrl(), '/') . '/voices';
                $response = Http::timeout(5)->get($ttsUrl);
                if ($response->successful() && is_array($response->json())) {
                    $remoteVoices = $response->json();
                    foreach ($remoteVoices as $v) {
                        $vid = $v['id'] ?? null;
                        $label = $v['name'] ?? $vid;
                        if ($vid && $vid !== '(no preset voices)') {
                            $gender = str_contains($label, 'Nữ') ? 'Nữ' : (str_contains($label, 'Nam') ? 'Nam' : '');
                            $style = str_contains($label, 'tin tức') ? 'tin_tuc' : (str_contains($label, 'kể chuyện') ? 'ke_chuyen' : 'tu_nhien');
                            $name = trim(explode('—', $label)[0]);

                            Voice::updateOrCreate(
                                ['voice_key' => $vid],
                                [
                                    'name' => $name ?: $vid,
                                    'voice_key' => $vid,
                                    'type' => 'system',
                                    'description' => $label,
                                    'gender' => $gender,
                                    'style' => $style,
                                    'is_active' => true,
                                ]
                            );
                        }
                    }
                }
            } catch (\Exception $e) {
                Log::warning('Auto-sync system voices from VieNeu-TTS failed: ' . $e->getMessage());
            }

            // Fallback: If still 0 system voices in DB, auto-seed default system voices
            if (Voice::whereNull('user_id')->where('type', 'system')->count() === 0) {
                (new \Database\Seeders\VoiceSeeder())->run();
            }

            // Re-query voices after auto-sync or seeding
            $voices = Voice::forUser($userId)
                ->orderBy('type', 'desc')
                ->orderBy('id', 'desc')
                ->get();
        }

        // 3. Ensure static sample audio files exist for all system voices
        foreach ($voices as $voice) {
            if ($voice->type === 'system' && !$voice->sample_url) {
                $this->ensureVoiceSampleExists($voice);
            }
        }

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

    /**
     * Ensure a static sample MP3 file exists for a voice in storage/app/public/samples/{slug}.mp3
     */
    protected function ensureVoiceSampleExists(Voice $voice): void
    {
        if ($voice->sample_path) return;

        $slug = Str::slug($voice->voice_key);
        $sampleFile = 'samples/' . $slug . '.mp3';

        if (!\Illuminate\Support\Facades\Storage::disk('public')->exists($sampleFile)) {
            try {
                $genUrl = $this->getTtsBaseUrl() . '/tts';
                $sampleRes = Http::timeout(30)->post($genUrl, [
                    'text'            => "Xin chào! Đây là mẫu giọng đọc thử nghiệm của " . $voice->name,
                    'voice_id'        => $voice->voice_key,
                    'response_format' => 'mp3',
                ]);

                if ($sampleRes->successful()) {
                    \Illuminate\Support\Facades\Storage::disk('public')->put($sampleFile, $sampleRes->body());
                }
            } catch (\Exception $ex) {
                Log::warning("Failed pre-generating sample for {$voice->voice_key}: " . $ex->getMessage());
            }
        }
    }

    /**
     * POST /api/tts/voices
     * Upload sample audio to clone a new custom voice for the user.
     */
    public function storeVoice(Request $request)
    {
        $request->validate([
            'name' => 'required|string|max:50',
            'audio' => 'required|file|mimes:mp3,wav|max:10240', // Max 10MB
            'description' => 'nullable|string|max:255',
            'gender' => 'nullable|string|max:20',
            'style' => 'nullable|string|max:50',
            'type' => 'nullable|string|in:system,custom',
        ]);

        $user = $request->user();
        $slug = Str::slug($request->name);
        $voiceKey = "user_{$user->id}_{$slug}_" . Str::random(4);

        $isAdmin = $user && method_exists($user, 'roles') && $user->roles()->whereIn('slug', ['admin', 'super-admin'])->exists();
        $voiceType = $request->type ?? ($isAdmin ? 'system' : 'custom');

        // 1. Store sample file locally on Laravel storage
        $samplePath = $request->file('audio')->store("voice_samples/{$user->id}", 'public');

        // 2. Attempt remote sync to VieNeu-TTS microservice if available (/voices/add)
        $uploadedFile = $request->file('audio');
        $fileContents = file_get_contents($uploadedFile->getRealPath());
        $filename = $uploadedFile->getClientOriginalName();

        $idsToSync = array_unique(array_filter([$request->name, $voiceKey]));
        $syncedRemote = false;

        foreach ($idsToSync as $idToSync) {
            try {
                $ttsUrl = rtrim($this->getTtsBaseUrl(), '/') . '/voices/add';
                $res = Http::timeout(10)
                    ->attach('file', $fileContents, $filename)
                    ->post($ttsUrl, [
                        'voice_id' => $idToSync,
                        'description' => $request->description ?? ("Voice của " . $user->name),
                        'gender' => $request->gender ?? '',
                        'style' => $request->style ?? 'tu_nhien',
                        'save' => 'true',
                    ]);

                if ($res->successful()) {
                    Log::info("Successfully synced voice '{$idToSync}' to VieNeu-TTS at /voices/add");
                    $syncedRemote = true;
                }
            } catch (\Exception $e) {
                Log::warning("VieNeu-TTS sync notice for '{$idToSync}': " . $e->getMessage());
            }
        }

        if (!$syncedRemote) {
            Log::info("Remote VieNeu-TTS voice preset sync skipped or endpoint unavailable; saved locally in database.");
        }

        // 3. Save voice record into Laravel database
        $voice = Voice::create([
            'user_id' => $voiceType === 'system' ? null : $user->id,
            'name' => $request->name,
            'voice_key' => $voiceKey,
            'type' => $voiceType,
            'description' => $request->description,
            'gender' => $request->gender,
            'style' => $request->style ?? 'tu_nhien',
            'sample_path' => $samplePath,
            'is_active' => true,
        ]);

        return response()->json([
            'status' => 'success',
            'message' => 'Đã tạo giọng đọc mới thành công!',
            'data' => $voice,
        ], 201);
    }

    /**
     * DELETE /api/tts/voices/{id}
     * Delete a custom voice owned by the authenticated user.
     */
    public function deleteVoice(Request $request, $id)
    {
        $user = $request->user();
        $isAdmin = $user && method_exists($user, 'roles') && $user->roles()->whereIn('slug', ['admin', 'super-admin'])->exists();

        $voice = Voice::where('id', $id)
            ->when(!$isAdmin, function ($q) use ($user) {
                $q->where('user_id', $user?->id);
            })
            ->first();

        if (!$voice) {
            return response()->json([
                'status' => 'error',
                'message' => 'Giọng đọc không tồn tại hoặc bạn không có quyền xóa.',
            ], 404);
        }

        // Call VieNeu-TTS to delete voice preset
        try {
            $ttsBase = rtrim($this->getTtsBaseUrl(), '/');
            Http::timeout(5)->delete("{$ttsBase}/voices/" . urlencode($voice->voice_key));
            if ($voice->name !== $voice->voice_key) {
                Http::timeout(5)->delete("{$ttsBase}/voices/" . urlencode($voice->name));
            }
        } catch (\Exception $e) {
            Log::warning('Failed to delete voice preset on VieNeu-TTS server', ['error' => $e->getMessage()]);
        }

        // Delete local sample file if exists
        if ($voice->sample_path && Storage::disk('public')->exists($voice->sample_path)) {
            Storage::disk('public')->delete($voice->sample_path);
        }

        $voice->delete();

        return response()->json([
            'status' => 'success',
            'message' => 'Đã xóa giọng đọc thành công.',
        ]);
    }

    /**
     * POST /api/tts/generate-chunked
     * Generate TTS audio for long texts (> CHUNK_THRESHOLD chars) by splitting into chunks,
     * generating each chunk in parallel (via concurrent HTTP calls), then merging with FFmpeg.
     * For short texts, delegates directly to generate().
     */
    public function generateChunked(Request $request, CreditService $creditService)
    {
        $request->validate([
            'text'     => 'required|string|max:15000',
            'voice_id' => 'required',
            'format'   => 'nullable|string|in:mp3,wav',
        ]);

        $text   = trim($request->text);
        $format = $request->format ?? 'mp3';

        // ── Threshold: dưới 800 ký tự → xử lý thẳng, không cần chunk ────────
        if (mb_strlen($text) <= 800) {
            return $this->generate($request, $creditService);
        }

        $user = $request->user();
        if (!$user) {
            return response()->json(['status' => 'error', 'message' => 'Bạn cần đăng nhập.'], 401);
        }

        // ── Credit check ──────────────────────────────────────────────────────
        $cost = Setting::getCreditCost('tts');
        if ($cost > 0) {
            $wallet = $creditService->ensureWallet($user);
            if ($wallet->availableBalance() < $cost) {
                return response()->json([
                    'status'            => 'error',
                    'message'           => "Không đủ credits. Cần: {$cost}, hiện có: {$wallet->availableBalance()}.",
                    'required_credits'  => $cost,
                    'available_credits' => $wallet->availableBalance(),
                ], 402);
            }
        }

        // ── Resolve voice key ─────────────────────────────────────────────────
        $voiceInput = $request->voice_id;
        $voice = Voice::where(function ($q) use ($voiceInput) {
            $q->where('id', $voiceInput)->orWhere('voice_key', $voiceInput)->orWhere('name', $voiceInput);
        })->first();

        if ($voice && $voice->type === 'custom' && $voice->user_id !== $user->id) {
            return response()->json(['status' => 'error', 'message' => 'Bạn không có quyền dùng giọng này.'], 403);
        }

        $voiceName = $voice ? $voice->name       : $voiceInput;
        $voiceKey  = $voice ? $voice->voice_key  : $voiceInput;

        // ── Split text by sentence boundaries (mỗi chunk ~600 ký tự) ──────────
        $chunks = $this->splitTextIntoChunks($text, 600);
        Log::info("TTS Chunked: " . count($chunks) . " chunks for " . mb_strlen($text) . " chars");

        $ttsBase   = rtrim($this->getTtsBaseUrl(), '/');
        $tmpDir    = sys_get_temp_dir() . '/tts_' . uniqid();
        mkdir($tmpDir, 0755, true);

        $chunkFiles = [];
        $errors     = [];

        // ── Generate each chunk (sequential to protect CPU) ──────────────────
        foreach ($chunks as $i => $chunk) {
            if (!trim($chunk)) continue;

            $candidates = array_unique(array_filter([$voiceName, $voiceKey, $voiceInput]));
            $success    = false;

            foreach ($candidates as $targetVoice) {
                $tmpFile = "{$tmpDir}/chunk_{$i}.{$format}";
                
                // 1. Thử /tts trước (nhanh và trả binary mp3 hoàn chỉnh)
                try {
                    $res = Http::timeout(60)->post("{$ttsBase}/tts", [
                        'text'            => $chunk,
                        'voice_id'        => $targetVoice,
                        'response_format' => $format,
                    ]);

                    if ($res->successful() && strlen($res->body()) > 0) {
                        file_put_contents($tmpFile, $res->body());
                        $chunkFiles[] = $tmpFile;
                        $success = true;
                        break;
                    }
                } catch (\Exception $e) {
                    $errors[] = "Chunk {$i} /tts ({$targetVoice}): " . $e->getMessage();
                }

                // 2. Fallback sang /stream với sink nếu /tts không thành công
                try {
                    $res = Http::withOptions(['sink' => $tmpFile])
                        ->timeout(120)
                        ->post("{$ttsBase}/stream", [
                            'text'            => $chunk,
                            'voice_id'        => $targetVoice,
                            'response_format' => $format,
                        ]);

                    if ($res->successful() && file_exists($tmpFile) && filesize($tmpFile) > 0) {
                        $chunkFiles[] = $tmpFile;
                        $success = true;
                        break;
                    } else {
                        @unlink($tmpFile);
                    }
                } catch (\Exception $e) {
                    @unlink($tmpFile);
                    $errors[] = "Chunk {$i} /stream ({$targetVoice}): " . $e->getMessage();
                }
            }

            if (!$success) {
                Log::warning("TTS chunk {$i} failed. Errors: " . implode('; ', $errors));
            }
        }

        if (empty($chunkFiles)) {
            $this->cleanTmpDir($tmpDir);
            return response()->json([
                'status'  => 'error',
                'message' => 'Không thể tạo âm thanh từ văn bản này. ' . implode('; ', $errors),
            ], 502);
        }

        // ── Merge chunk files with FFmpeg ─────────────────────────────────────
        $outputFile = "{$tmpDir}/merged.{$format}";
        $mergeError = '';

        if (count($chunkFiles) === 1) {
            copy($chunkFiles[0], $outputFile);
        } else {
            $listFile = "{$tmpDir}/filelist.txt";
            $listContent = '';
            foreach ($chunkFiles as $cf) {
                $listContent .= "file '" . addslashes($cf) . "'\n";
            }
            file_put_contents($listFile, $listContent);

            // Silence padding 150ms between chunks for natural breathing
            $silenceFile = "{$tmpDir}/silence.{$format}";
            $silenceDuration = 0.15;
            $ffmpegSilence = "ffmpeg -f lavfi -i anullsrc=r=48000:cl=mono -t {$silenceDuration} -ar 48000 -ac 1 " .
                             ($format === 'mp3' ? '-codec:a libmp3lame -qscale:a 2 ' : '') .
                             "-y {$silenceFile} 2>&1";
            exec($ffmpegSilence, $silenceOut, $silenceCode);

            // Interleave silence between chunks
            $listWithSilence = "{$tmpDir}/filelist_silence.txt";
            $silenceContent  = '';
            foreach ($chunkFiles as $idx => $cf) {
                $silenceContent .= "file '" . addslashes($cf) . "'\n";
                if ($idx < count($chunkFiles) - 1 && file_exists($silenceFile)) {
                    $silenceContent .= "file '" . addslashes($silenceFile) . "'\n";
                }
            }
            file_put_contents($listWithSilence, $silenceContent);

            $ffmpegCmd = "ffmpeg -f concat -safe 0 -i " . escapeshellarg($listWithSilence) . " -c copy -y " . escapeshellarg($outputFile) . " 2>&1";
            exec($ffmpegCmd, $ffmpegOut, $ffmpegCode);

            if ($ffmpegCode !== 0 || !file_exists($outputFile)) {
                // Fallback: concat without silence if FFmpeg fails
                $ffmpegFallback = "ffmpeg -f concat -safe 0 -i " . escapeshellarg($listFile) . " -c copy -y " . escapeshellarg($outputFile) . " 2>&1";
                exec($ffmpegFallback, $fbOut, $fbCode);
                $mergeError = implode("\n", $fbOut);
            }
        }

        if (!file_exists($outputFile)) {
            $this->cleanTmpDir($tmpDir);
            Log::error("TTS Chunked merge failed. FFmpeg output: {$mergeError}");
            return response()->json([
                'status'  => 'error',
                'message' => 'Lỗi khi ghép file âm thanh. Vui lòng thử lại.',
            ], 500);
        }

        $audioBytes = file_get_contents($outputFile);
        $this->cleanTmpDir($tmpDir);

        // ── Deduct credits ───────────────────────────────────────────────────
        if ($cost > 0) {
            try {
                $creditService->deduct(
                    $user, $cost,
                    CreditTransaction::TYPE_GENERATE_TTS, 'tts', null,
                    "Tạo giọng đọc TTS dài ({$cost} credits)",
                    ['text_length' => mb_strlen($text), 'voice_id' => $voiceKey, 'chunks' => count($chunkFiles)]
                );
            } catch (\Exception $e) {
                Log::error("Failed to deduct TTS credits: " . $e->getMessage());
            }
        }

        $mediaType = $format === 'mp3' ? 'audio/mpeg' : 'audio/wav';
        $filename  = 'speech_' . time() . '.' . $format;

        return response($audioBytes, 200, [
            'Content-Type'        => $mediaType,
            'Content-Disposition' => "attachment; filename=\"{$filename}\"",
            'X-Chunks-Count'      => count($chunkFiles),
        ]);
    }

    /**
     * Split text into chunks at sentence boundaries (., !, ?) respecting max_chars limit.
     */
    protected function splitTextIntoChunks(string $text, int $maxChars = 1500): array
    {
        // Split on sentence-ending punctuation followed by space or newline
        $sentences = preg_split('/(?<=[.!?。！？])\s+/u', $text, -1, PREG_SPLIT_NO_EMPTY);
        $chunks    = [];
        $current   = '';

        foreach ($sentences as $sentence) {
            if (mb_strlen($current) + mb_strlen($sentence) + 1 <= $maxChars) {
                $current .= ($current ? ' ' : '') . $sentence;
            } else {
                if ($current !== '') $chunks[] = $current;
                // If a single sentence exceeds maxChars, split it hard
                if (mb_strlen($sentence) > $maxChars) {
                    $parts = str_split($sentence, $maxChars);
                    $lastPart = array_pop($parts);
                    foreach ($parts as $part) $chunks[] = $part;
                    $current = $lastPart;
                } else {
                    $current = $sentence;
                }
            }
        }

        if ($current !== '') $chunks[] = $current;

        return array_filter($chunks, fn($c) => trim($c) !== '');
    }

    /**
     * Recursively remove a temporary directory.
     */
    protected function cleanTmpDir(string $dir): void
    {
        if (!is_dir($dir)) return;
        array_map(fn($f) => is_file($f) ? unlink($f) : null, glob("{$dir}/*") ?: []);
        @rmdir($dir);
    }

    /**
     * POST /api/tts/generate
     * Generate TTS speech MP3 audio for a given text and voice_id.
     */
    public function generate(Request $request, CreditService $creditService)
    {
        $request->validate([
            'text' => 'required|string|max:5000',
            'voice_id' => 'required', // Can be Voice database ID or system voice key
            'format' => 'nullable|string|in:mp3,wav',
        ]);

        $user = $request->user();
        if (!$user) {
            return response()->json([
                'status' => 'error',
                'message' => 'Bạn cần đăng nhập để sử dụng dịch vụ tạo giọng đọc TTS.',
            ], 401);
        }

        $userId = $user->id;
        $voiceInput = $request->voice_id;

        // 1. Calculate credit cost for TTS
        $cost = Setting::getCreditCost('tts');

        // 2. Pre-check user credit balance
        if ($cost > 0) {
            $wallet = $creditService->ensureWallet($user);
            if ($wallet->availableBalance() < $cost) {
                return response()->json([
                    'status' => 'error',
                    'message' => "Bạn không đủ credits để tạo giọng đọc. Cần: {$cost} credits, hiện có: {$wallet->availableBalance()} credits.",
                    'required_credits' => $cost,
                    'available_credits' => $wallet->availableBalance(),
                ], 402);
            }
        }

        // Find voice by database ID or by voice_key / name
        $voice = Voice::where(function ($q) use ($voiceInput) {
            $q->where('id', $voiceInput)
              ->orWhere('voice_key', $voiceInput)
              ->orWhere('name', $voiceInput);
        })->first();

        $voiceKey = $voice ? $voice->voice_key : $voiceInput;

        // Check ownership if it's a custom voice
        if ($voice && $voice->type === 'custom' && $voice->user_id !== $userId) {
            return response()->json([
                'status' => 'error',
                'message' => 'Bạn không có quyền sử dụng giọng đọc riêng này.',
            ], 403);
        }

        $voiceName = $voice ? $voice->name : $voiceInput;
        $voiceKey = $voice ? $voice->voice_key : $voiceInput;

        $format = $request->format ?? 'mp3';

        // Prepare voice candidates: try voice name (e.g. "Kenh", "Ngọc Trân") first, then voice_key
        $voiceCandidates = array_unique(array_filter([$voiceName, $voiceKey, $voiceInput]));
        // Chỉ dùng 2 endpoint thực sự tồn tại của VieNeu-TTS
        // /tts  → trả file hoàn chỉnh (ưu tiên)
        // /stream → streaming response, cần dùng sink
        $ttsBase = rtrim($this->getTtsBaseUrl(), '/');

        $response    = null;
        $lastError   = '';

        foreach ($voiceCandidates as $targetVoice) {
            // ── Thử /tts trước (response đầy đủ, nhanh) ──────────────────────
            try {
                $res = Http::timeout(60)->post("{$ttsBase}/tts", [
                    'text'            => $request->text,
                    'voice_id'        => $targetVoice,
                    'response_format' => $format,
                ]);

                if ($res->successful()) {
                    $response = $res;
                    break;
                }

                $lastError = "/tts ('{$targetVoice}'): HTTP {$res->status()} " . $res->body();
            } catch (\Exception $e) {
                $lastError = "/tts ('{$targetVoice}'): " . $e->getMessage();
            }

            // ── Fallback /stream (dùng sink để đọc đúng streaming) ───────────
            try {
                $tmpStream = sys_get_temp_dir() . '/tts_stream_' . uniqid() . '.' . $format;
                $res = Http::withOptions(['sink' => $tmpStream])
                    ->timeout(120)
                    ->post("{$ttsBase}/stream", [
                        'text'            => $request->text,
                        'voice_id'        => $targetVoice,
                        'response_format' => $format,
                    ]);

                if ($res->successful() && file_exists($tmpStream) && filesize($tmpStream) > 0) {
                    $response = $res;
                    // Đính kèm path để đọc lại phía dưới
                    $response->streamFilePath = $tmpStream;
                    break;
                }

                @unlink($tmpStream);
                $lastError = "/stream ('{$targetVoice}'): HTTP {$res->status()} " . $res->body();
            } catch (\Exception $e) {
                @unlink($tmpStream ?? '');
                $lastError = "/stream ('{$targetVoice}'): " . $e->getMessage();
            }
        }

        if ($response && $response->successful()) {
            // 3. Deduct credit AFTER successful TTS audio generation
            if ($cost > 0) {
                try {
                    $creditService->deduct(
                        $user,
                        $cost,
                        CreditTransaction::TYPE_GENERATE_TTS,
                        'tts',
                        null,
                        "Tạo giọng đọc TTS ({$cost} credits)",
                        ['text_length' => mb_strlen($request->text), 'voice_id' => $voiceKey]
                    );
                } catch (\Exception $e) {
                    Log::error("Failed to deduct TTS credits for user {$user->id}: " . $e->getMessage());
                }
            }

            $mediaType = $format === 'mp3' ? 'audio/mpeg' : 'audio/wav';
            $filename  = 'speech_' . time() . '.' . $format;

            // Nếu dùng /stream với sink → đọc từ file, rồi xóa
            if (!empty($response->streamFilePath) && file_exists($response->streamFilePath)) {
                $audioBytes = file_get_contents($response->streamFilePath);
                @unlink($response->streamFilePath);
            } else {
                $audioBytes = $response->body();
            }

            return response($audioBytes, 200, [
                'Content-Type'        => $mediaType,
                'Content-Disposition' => 'attachment; filename="' . $filename . '"',
            ]);
        }

        Log::error('VieNeu-TTS Generate Error', ['url' => $this->getTtsBaseUrl(), 'error' => $lastError]);

        return response()->json([
            'status' => 'error',
            'message' => 'Không thể tạo giọng đọc từ VieNeu-TTS server (' . $this->getTtsBaseUrl() . '). Vui lòng kiểm tra địa chỉ API VieNeu-TTS trong Cấu Hình Hệ Thống (Admin CMS > Settings). Chi tiết: ' . ($lastError ?: 'Server phản hồi 404 Not Found.'),
        ], 502);
    }

    /**
     * PUT/PATCH /api/tts/voices/{id}
     * Update voice information (name, description, gender, style, is_active).
     */
    public function updateVoice(Request $request, $id)
    {
        $voice = Voice::find($id);
        if (!$voice) {
            return response()->json([
                'status' => 'error',
                'message' => 'Giọng đọc không tồn tại.',
            ], 404);
        }

        $user = $request->user();
        $isAdmin = $user && method_exists($user, 'roles') && $user->roles()->whereIn('slug', ['admin', 'super-admin'])->exists();
        if (!$isAdmin && $voice->type === 'custom' && $voice->user_id !== $user->id) {
            return response()->json([
                'status' => 'error',
                'message' => 'Bạn không có quyền chỉnh sửa giọng đọc này.',
            ], 403);
        }

        $request->validate([
            'name' => 'sometimes|required|string|max:50',
            'description' => 'nullable|string|max:255',
            'gender' => 'nullable|string|max:20',
            'style' => 'nullable|string|max:50',
            'type' => 'nullable|string|in:system,custom',
            'is_active' => 'sometimes|boolean',
        ]);

        $updateData = $request->only([
            'name',
            'description',
            'gender',
            'style',
            'type',
            'is_active',
        ]);

        if (isset($updateData['type']) && $updateData['type'] === 'system') {
            $updateData['user_id'] = null;
        }

        $voice->update($updateData);

        return response()->json([
            'status' => 'success',
            'message' => 'Đã cập nhật thông tin giọng đọc thành công!',
            'data' => $voice,
        ]);
    }
}
