<?php

namespace App\Services;

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;

/**
 * Google Cloud 서비스 (Storage, Speech-to-Text)
 */
class GoogleCloudService
{
    private ?array $serviceAccount = null;

    private ?string $accessToken = null;

    private ?int $tokenExpiry = null;

    public function __construct()
    {
        $this->loadServiceAccount();
    }

    /**
     * 서비스 계정 로드
     */
    private function loadServiceAccount(): void
    {
        $path = config('services.google.credentials_path');

        if ($path && file_exists($path)) {
            $this->serviceAccount = json_decode(file_get_contents($path), true);
        }
    }

    /**
     * OAuth 토큰 발급
     */
    public function getAccessToken(): ?string
    {
        // 캐시된 토큰이 유효하면 재사용
        if ($this->accessToken && $this->tokenExpiry && time() < $this->tokenExpiry - 60) {
            return $this->accessToken;
        }

        if (! $this->serviceAccount) {
            Log::error('Google Cloud: 서비스 계정 파일이 없습니다.');

            return null;
        }

        try {
            $now = time();
            $jwtHeader = base64_encode(json_encode(['alg' => 'RS256', 'typ' => 'JWT']));
            $jwtClaim = base64_encode(json_encode([
                'iss' => $this->serviceAccount['client_email'],
                'scope' => 'https://www.googleapis.com/auth/cloud-platform',
                'aud' => 'https://oauth2.googleapis.com/token',
                'exp' => $now + 3600,
                'iat' => $now,
            ]));

            $privateKey = openssl_pkey_get_private($this->serviceAccount['private_key']);
            if (! $privateKey) {
                Log::error('Google Cloud: 개인 키 읽기 실패');

                return null;
            }

            openssl_sign($jwtHeader.'.'.$jwtClaim, $signature, $privateKey, OPENSSL_ALGO_SHA256);

            $jwt = $jwtHeader.'.'.$jwtClaim.'.'.base64_encode($signature);

            $response = Http::asForm()->post('https://oauth2.googleapis.com/token', [
                'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
                'assertion' => $jwt,
            ]);

            if ($response->successful()) {
                $data = $response->json();
                $this->accessToken = $data['access_token'];
                $this->tokenExpiry = $now + ($data['expires_in'] ?? 3600);

                return $this->accessToken;
            }

            Log::error('Google Cloud: OAuth 토큰 발급 실패', ['response' => $response->body()]);

            return null;
        } catch (\Exception $e) {
            Log::error('Google Cloud: OAuth 토큰 발급 예외', ['error' => $e->getMessage()]);

            return null;
        }
    }

    /**
     * GCS에 파일 업로드
     *
     * @return array|null ['uri' => 'gs://...', 'size' => bytes] or null
     */
    public function uploadToStorage(string $localPath, string $objectName): ?array
    {
        $token = $this->getAccessToken();
        if (! $token) {
            return null;
        }

        $bucket = config('services.google.storage_bucket');
        if (! $bucket) {
            Log::error('Google Cloud: Storage 버킷 설정 없음');

            return null;
        }

        try {
            $fileContent = file_get_contents($localPath);
            $fileSize = strlen($fileContent);
            $mimeType = mime_content_type($localPath) ?: 'audio/webm';

            $uploadUrl = 'https://storage.googleapis.com/upload/storage/v1/b/'.
                urlencode($bucket).'/o?uploadType=media&name='.
                urlencode($objectName);

            $response = Http::withToken($token)
                ->withHeaders(['Content-Type' => $mimeType])
                ->withBody($fileContent, $mimeType)
                ->post($uploadUrl);

            if ($response->successful()) {
                $result = $response->json();
                Log::info('Google Cloud: Storage 업로드 성공', [
                    'object' => $objectName,
                    'size' => $result['size'] ?? $fileSize,
                    'bucket' => $bucket,
                ]);

                return [
                    'uri' => 'gs://'.$bucket.'/'.$objectName,
                    'size' => (int) ($result['size'] ?? $fileSize),
                ];
            }

            Log::error('Google Cloud: Storage 업로드 실패', ['response' => $response->body()]);

            return null;
        } catch (\Exception $e) {
            Log::error('Google Cloud: Storage 업로드 예외', ['error' => $e->getMessage()]);

            return null;
        }
    }

    /**
     * Base64 오디오를 GCS에 업로드
     *
     * @return array|null ['uri' => 'gs://...', 'size' => bytes] or null
     */
    public function uploadBase64Audio(string $base64Audio, string $objectName): ?array
    {
        // Base64 데이터 파싱
        $audioData = $base64Audio;
        if (preg_match('/^data:audio\/\w+;base64,(.+)$/', $base64Audio, $matches)) {
            $audioData = $matches[1];
        }

        // 임시 파일 생성
        $tempPath = storage_path('app/temp/'.uniqid('audio_').'.webm');
        $tempDir = dirname($tempPath);

        if (! is_dir($tempDir)) {
            mkdir($tempDir, 0755, true);
        }

        file_put_contents($tempPath, base64_decode($audioData));

        // GCS 업로드
        $result = $this->uploadToStorage($tempPath, $objectName);

        // 임시 파일 삭제
        @unlink($tempPath);

        return $result;
    }

    /**
     * Speech-to-Text API 호출
     */
    public function speechToText(string $gcsUri, string $languageCode = 'ko-KR'): ?string
    {
        $token = $this->getAccessToken();
        if (! $token) {
            return null;
        }

        try {
            // 긴 오디오는 비동기 처리 (LongRunningRecognize)
            $response = Http::withToken($token)
                ->post('https://speech.googleapis.com/v1/speech:longrunningrecognize', [
                    'config' => [
                        'encoding' => 'WEBM_OPUS',
                        'sampleRateHertz' => 48000,
                        'audioChannelCount' => 2,
                        'languageCode' => $languageCode,
                        'enableAutomaticPunctuation' => true,
                        'model' => 'latest_long',
                    ],
                    'audio' => [
                        'uri' => $gcsUri,
                    ],
                ]);

            if (! $response->successful()) {
                Log::error('Google Cloud: STT 요청 실패', ['response' => $response->body()]);

                return null;
            }

            $operation = $response->json();
            $operationName = $operation['name'] ?? null;

            Log::info('Google Cloud: STT 요청 응답', ['operation' => $operation]);

            if (! $operationName) {
                Log::error('Google Cloud: STT 작업 이름 없음', ['response_body' => $response->body()]);

                return null;
            }

            // 작업 완료 대기 (폴링)
            $result = $this->waitForSttOperation($operationName);
            Log::info('Google Cloud: STT 완료', ['operationName' => $operationName, 'result_length' => strlen($result ?? '')]);

            return $result;
        } catch (\Exception $e) {
            Log::error('Google Cloud: STT 예외', ['error' => $e->getMessage()]);

            return null;
        }
    }

    /**
     * STT 작업 완료 대기
     */
    private function waitForSttOperation(string $operationName, int $maxAttempts = 60): ?string
    {
        $token = $this->getAccessToken();
        if (! $token) {
            Log::error('Google Cloud: STT 폴링 토큰 획득 실패');

            return null;
        }

        for ($i = 0; $i < $maxAttempts; $i++) {
            sleep(5); // 5초 대기

            $response = Http::withToken($token)
                ->get("https://speech.googleapis.com/v1/operations/{$operationName}");

            if (! $response->successful()) {
                continue;
            }

            $result = $response->json();

            if (isset($result['done']) && $result['done']) {
                if (isset($result['error'])) {
                    Log::error('Google Cloud: STT 작업 실패', ['error' => $result['error']]);

                    return null;
                }

                // 결과 텍스트 추출
                $transcript = '';
                $results = $result['response']['results'] ?? [];

                foreach ($results as $res) {
                    $alternatives = $res['alternatives'] ?? [];
                    if (! empty($alternatives)) {
                        $transcript .= $alternatives[0]['transcript'] ?? '';
                    }
                }

                return $transcript;
            }
        }

        Log::error('Google Cloud: STT 작업 타임아웃');

        return null;
    }

    /**
     * Speaker Diarization을 포함한 Speech-to-Text API 호출
     *
     * @return array|null ['segments' => [...], 'full_transcript' => '...']
     */
    public function speechToTextWithDiarization(
        string $gcsUri,
        string $languageCode = 'ko-KR',
        int $minSpeakers = 2,
        int $maxSpeakers = 6
    ): ?array {
        $token = $this->getAccessToken();
        if (! $token) {
            return null;
        }

        try {
            $response = Http::withToken($token)
                ->post('https://speech.googleapis.com/v1/speech:longrunningrecognize', [
                    'config' => [
                        'encoding' => 'WEBM_OPUS',
                        'sampleRateHertz' => 48000,
                        'audioChannelCount' => 2,
                        'languageCode' => $languageCode,
                        'enableAutomaticPunctuation' => true,
                        'model' => 'latest_long',
                        'enableWordTimeOffsets' => true,
                        'diarizationConfig' => [
                            'enableSpeakerDiarization' => true,
                            'minSpeakerCount' => $minSpeakers,
                            'maxSpeakerCount' => $maxSpeakers,
                        ],
                    ],
                    'audio' => [
                        'uri' => $gcsUri,
                    ],
                ]);

            if (! $response->successful()) {
                Log::error('Google Cloud: STT Diarization 요청 실패', ['response' => $response->body()]);

                return null;
            }

            $operation = $response->json();
            $operationName = $operation['name'] ?? null;

            if (! $operationName) {
                Log::error('Google Cloud: STT Diarization 작업 이름 없음');

                return null;
            }

            Log::info('Google Cloud: STT Diarization 요청 시작', ['operationName' => $operationName]);

            $rawResult = $this->waitForSttDiarizationOperation($operationName);

            if (! $rawResult) {
                return null;
            }

            return $this->parseDiarizationResult($rawResult);
        } catch (\Exception $e) {
            Log::error('Google Cloud: STT Diarization 예외', ['error' => $e->getMessage()]);

            return null;
        }
    }

    /**
     * STT Diarization 작업 완료 대기 (raw 결과 반환)
     */
    private function waitForSttDiarizationOperation(string $operationName, int $maxAttempts = 60): ?array
    {
        $token = $this->getAccessToken();
        if (! $token) {
            return null;
        }

        for ($i = 0; $i < $maxAttempts; $i++) {
            sleep(5);

            $response = Http::withToken($token)
                ->get("https://speech.googleapis.com/v1/operations/{$operationName}");

            if (! $response->successful()) {
                continue;
            }

            $result = $response->json();

            if (isset($result['done']) && $result['done']) {
                if (isset($result['error'])) {
                    Log::error('Google Cloud: STT Diarization 작업 실패', ['error' => $result['error']]);

                    return null;
                }

                return $result;
            }
        }

        Log::error('Google Cloud: STT Diarization 작업 타임아웃');

        return null;
    }

    /**
     * Diarization 결과를 화자별 세그먼트로 파싱
     */
    private function parseDiarizationResult(array $operationResult): ?array
    {
        $results = $operationResult['response']['results'] ?? [];

        if (empty($results)) {
            return null;
        }

        // Diarization 결과는 마지막 result의 alternatives[0].words에 전체 word-level 정보가 있음
        $lastResult = end($results);
        $words = $lastResult['alternatives'][0]['words'] ?? [];

        if (empty($words)) {
            // word-level 결과 없으면 일반 transcript로 폴백
            $transcript = '';
            foreach ($results as $res) {
                $transcript .= ($res['alternatives'][0]['transcript'] ?? '').' ';
            }
            $transcript = $this->cleanSttText(trim($transcript));

            return [
                'segments' => [[
                    'speaker_name' => '화자 1',
                    'speaker_label' => '1',
                    'text' => $transcript,
                    'start_time_ms' => 0,
                    'end_time_ms' => null,
                    'is_manual_speaker' => false,
                ]],
                'full_transcript' => '[화자 1] '.$transcript,
                'speaker_count' => 1,
            ];
        }

        // word-level 화자 정보를 세그먼트로 그룹핑
        // Google STT의 SentencePiece 토크나이저: ▁(U+2581)는 새 단어 시작 표시
        $segments = [];
        $currentSpeaker = null;
        $currentTokens = [];
        $segmentStartMs = 0;

        foreach ($words as $word) {
            $speakerTag = $word['speakerTag'] ?? 0;
            $wordText = $word['word'] ?? '';
            $startMs = $this->parseGoogleTimeToMs($word['startTime'] ?? '0s');
            $endMs = $this->parseGoogleTimeToMs($word['endTime'] ?? '0s');

            // SentencePiece: ▁(U+2581) 또는 _로 시작하면 새 단어
            $isNewWord = preg_match('/^[\x{2581}_]/u', $wordText);

            // 모든 구분자 문자 제거: _(U+005F), ▁(U+2581)
            $cleanToken = preg_replace('/[\x{2581}_]/u', '', $wordText);
            if (trim($cleanToken) === '') {
                continue;
            }

            if ($speakerTag !== $currentSpeaker && $currentSpeaker !== null && ! empty($currentTokens)) {
                $segments[] = [
                    'speaker_name' => '화자 '.$currentSpeaker,
                    'speaker_label' => (string) $currentSpeaker,
                    'text' => $this->joinSentencePieceTokens($currentTokens),
                    'start_time_ms' => $segmentStartMs,
                    'end_time_ms' => $startMs,
                    'is_manual_speaker' => false,
                ];
                $currentTokens = [];
                $segmentStartMs = $startMs;
            }

            $currentSpeaker = $speakerTag;
            $currentTokens[] = ['text' => $cleanToken, 'new_word' => (bool) $isNewWord];
        }

        // 마지막 세그먼트
        if (! empty($currentTokens)) {
            $lastWord = end($words);
            $segments[] = [
                'speaker_name' => '화자 '.$currentSpeaker,
                'speaker_label' => (string) $currentSpeaker,
                'text' => $this->joinSentencePieceTokens($currentTokens),
                'start_time_ms' => $segmentStartMs,
                'end_time_ms' => $this->parseGoogleTimeToMs($lastWord['endTime'] ?? '0s'),
                'is_manual_speaker' => false,
            ];
        }

        // full_transcript 생성
        $fullTranscript = '';
        foreach ($segments as $seg) {
            $fullTranscript .= "[{$seg['speaker_name']}] {$seg['text']}\n";
        }

        // 고유 화자 수
        $speakerCount = count(array_unique(array_column($segments, 'speaker_label')));

        return [
            'segments' => $segments,
            'full_transcript' => trim($fullTranscript),
            'speaker_count' => $speakerCount,
        ];
    }

    /**
     * Google STT 시간 형식("1.500s")을 밀리초로 변환
     */
    private function parseGoogleTimeToMs(string $timeStr): int
    {
        if (preg_match('/^([\d.]+)s$/', $timeStr, $matches)) {
            return (int) round((float) $matches[1] * 1000);
        }

        return 0;
    }

    /**
     * SentencePiece 토큰 배열을 자연스러운 텍스트로 결합
     *
     * ▁(U+2581)가 있던 토큰은 새 단어 시작 → 앞에 공백 추가
     * ▁가 없던 토큰은 이전 단어에 바로 붙임
     */
    private function joinSentencePieceTokens(array $tokens): string
    {
        $result = '';
        foreach ($tokens as $i => $token) {
            if ($i === 0) {
                $result = $token['text'];
            } elseif ($token['new_word']) {
                $result .= ' '.$token['text'];
            } else {
                $result .= $token['text'];
            }
        }

        return trim(preg_replace('/\s{2,}/', ' ', $result));
    }

    /**
     * STT 텍스트에서 SentencePiece/언더스코어 노이즈 제거
     */
    private function cleanSttText(string $text): string
    {
        // ▁(U+2581)를 공백으로, _(U+005F)는 제거, 연속 공백 정리
        $cleaned = preg_replace('/\x{2581}/u', ' ', $text);
        $cleaned = str_replace('_', '', $cleaned);

        return trim(preg_replace('/\s{2,}/', ' ', $cleaned));
    }

    /**
     * Speech-to-Text V2 API + Chirp 2 모델
     *
     * @return array|null ['segments' => [...], 'full_transcript' => '...', 'speaker_count' => int]
     */
    public function speechToTextV2(
        string $gcsUri,
        string $languageCode = 'ko-KR',
        int $minSpeakers = 2,
        int $maxSpeakers = 6,
        array $phraseHints = []
    ): ?array {
        $token = $this->getAccessToken();
        if (! $token) {
            return null;
        }

        $projectId = $this->serviceAccount['project_id'] ?? null;
        if (! $projectId) {
            Log::warning('Google Cloud: STT V2 - project_id 없음, V1 폴백 필요');

            return null;
        }

        $location = config('services.google.location', 'us-central1');

        try {
            $recognitionConfig = [
                'auto_decoding_config' => (object) [],
                'model' => 'chirp_2',
                'language_codes' => [$languageCode],
                'features' => [
                    'enable_automatic_punctuation' => true,
                    'enable_word_time_offsets' => true,
                    'diarization_config' => [
                        'min_speaker_count' => $minSpeakers,
                        'max_speaker_count' => $maxSpeakers,
                    ],
                ],
            ];

            // Speech Adaptation: phrase hints
            if (! empty($phraseHints)) {
                $phrases = array_map(fn ($p) => ['value' => $p, 'boost' => 10.0], $phraseHints);
                $recognitionConfig['adaptation'] = [
                    'phrase_sets' => [
                        [
                            'inline_phrase_set' => [
                                'phrases' => $phrases,
                            ],
                        ],
                    ],
                ];
            }

            $requestBody = [
                'config' => $recognitionConfig,
                'files' => [
                    ['uri' => $gcsUri],
                ],
                'recognition_output_config' => [
                    'inline_response_config' => (object) [],
                ],
            ];

            $url = "https://{$location}-speech.googleapis.com/v2/projects/{$projectId}/locations/{$location}/recognizers/_:batchRecognize";

            Log::info('Google Cloud: STT V2 (Chirp 2) 요청 시작', [
                'gcsUri' => $gcsUri,
                'model' => 'chirp_2',
                'language' => $languageCode,
                'phraseHints' => count($phraseHints),
            ]);

            $response = Http::withToken($token)
                ->timeout(30)
                ->post($url, $requestBody);

            if (! $response->successful()) {
                Log::error('Google Cloud: STT V2 요청 실패', [
                    'status' => $response->status(),
                    'response' => $response->body(),
                ]);

                return null;
            }

            $operation = $response->json();
            $operationName = $operation['name'] ?? null;

            if (! $operationName) {
                Log::error('Google Cloud: STT V2 작업 이름 없음');

                return null;
            }

            Log::info('Google Cloud: STT V2 작업 시작됨', ['operationName' => $operationName]);

            $operationResult = $this->waitForV2Operation($operationName);

            if (! $operationResult) {
                return null;
            }

            return $this->parseV2Result($operationResult, $gcsUri);
        } catch (\Exception $e) {
            Log::error('Google Cloud: STT V2 예외', ['error' => $e->getMessage()]);

            return null;
        }
    }

    /**
     * V2 Operation 폴링
     */
    private function waitForV2Operation(string $operationName, int $maxAttempts = 60): ?array
    {
        $token = $this->getAccessToken();
        if (! $token) {
            return null;
        }

        $location = config('services.google.location', 'asia-southeast1');

        for ($i = 0; $i < $maxAttempts; $i++) {
            sleep(5);

            $response = Http::withToken($token)
                ->get("https://{$location}-speech.googleapis.com/v2/{$operationName}");

            if (! $response->successful()) {
                continue;
            }

            $result = $response->json();

            if (isset($result['done']) && $result['done']) {
                if (isset($result['error'])) {
                    Log::error('Google Cloud: STT V2 작업 실패', ['error' => $result['error']]);

                    return null;
                }

                Log::info('Google Cloud: STT V2 작업 완료');

                return $result;
            }
        }

        Log::error('Google Cloud: STT V2 작업 타임아웃');

        return null;
    }

    /**
     * V2 batchRecognize 결과 파싱
     */
    private function parseV2Result(array $operationResult, string $gcsUri): ?array
    {
        // V2 응답 구조: response.results[uri].transcript.results[].alternatives[].words[]
        $batchResults = $operationResult['response']['results'] ?? [];

        // URI 키로 결과 찾기
        $transcriptData = $batchResults[$gcsUri] ?? null;

        // URI가 정확히 매치하지 않으면 첫 번째 결과 사용
        if (! $transcriptData && ! empty($batchResults)) {
            $transcriptData = reset($batchResults);
        }

        if (! $transcriptData) {
            Log::warning('Google Cloud: STT V2 결과 없음');

            return null;
        }

        $results = $transcriptData['transcript']['results'] ?? [];

        if (empty($results)) {
            Log::warning('Google Cloud: STT V2 transcript 결과 없음');

            return null;
        }

        // 마지막 result에 전체 word-level diarization 정보가 있음
        $lastResult = end($results);
        $words = $lastResult['alternatives'][0]['words'] ?? [];

        if (empty($words)) {
            // word-level 결과 없으면 일반 transcript 사용
            $transcript = '';
            foreach ($results as $res) {
                $transcript .= ($res['alternatives'][0]['transcript'] ?? '').' ';
            }
            $transcript = trim($transcript);

            return [
                'segments' => [[
                    'speaker_name' => '화자 1',
                    'speaker_label' => '1',
                    'text' => $transcript,
                    'start_time_ms' => 0,
                    'end_time_ms' => null,
                    'is_manual_speaker' => false,
                ]],
                'full_transcript' => '[화자 1] '.$transcript,
                'speaker_count' => 1,
            ];
        }

        // SentencePiece 토큰 여부 감지 (▁ 문자 포함 시)
        $hasSentencePiece = false;
        foreach ($words as $w) {
            if (preg_match('/[\x{2581}_]/u', $w['word'] ?? '')) {
                $hasSentencePiece = true;
                break;
            }
        }

        // word-level 화자 정보를 세그먼트로 그룹핑
        $segments = [];
        $currentSpeaker = null;
        $currentTokens = [];
        $currentWords = [];
        $segmentStartMs = 0;

        foreach ($words as $word) {
            $speakerTag = $word['speakerTag'] ?? $word['speaker_tag'] ?? 0;
            $wordText = $word['word'] ?? '';
            $startMs = $this->parseGoogleTimeToMs($word['startOffset'] ?? $word['startTime'] ?? $word['start_offset'] ?? $word['start_time'] ?? '0s');
            $endMs = $this->parseGoogleTimeToMs($word['endOffset'] ?? $word['endTime'] ?? $word['end_offset'] ?? $word['end_time'] ?? '0s');

            if ($hasSentencePiece) {
                // SentencePiece 방식 처리
                $isNewWord = preg_match('/^[\x{2581}_]/u', $wordText);
                $cleanToken = preg_replace('/[\x{2581}_]/u', '', $wordText);
                if (trim($cleanToken) === '') {
                    continue;
                }

                if ($speakerTag !== $currentSpeaker && $currentSpeaker !== null && ! empty($currentTokens)) {
                    $segments[] = [
                        'speaker_name' => '화자 '.$currentSpeaker,
                        'speaker_label' => (string) $currentSpeaker,
                        'text' => $this->joinSentencePieceTokens($currentTokens),
                        'start_time_ms' => $segmentStartMs,
                        'end_time_ms' => $startMs,
                        'is_manual_speaker' => false,
                    ];
                    $currentTokens = [];
                    $segmentStartMs = $startMs;
                }

                $currentSpeaker = $speakerTag;
                $currentTokens[] = ['text' => $cleanToken, 'new_word' => (bool) $isNewWord];
            } else {
                // 일반 단어 방식 처리 (Chirp 2)
                if ($speakerTag !== $currentSpeaker && $currentSpeaker !== null && ! empty($currentWords)) {
                    $segments[] = [
                        'speaker_name' => '화자 '.$currentSpeaker,
                        'speaker_label' => (string) $currentSpeaker,
                        'text' => implode(' ', $currentWords),
                        'start_time_ms' => $segmentStartMs,
                        'end_time_ms' => $startMs,
                        'is_manual_speaker' => false,
                    ];
                    $currentWords = [];
                    $segmentStartMs = $startMs;
                }

                $currentSpeaker = $speakerTag;
                $currentWords[] = trim($wordText);
            }
        }

        // 마지막 세그먼트
        if ($hasSentencePiece && ! empty($currentTokens)) {
            $lastWord = end($words);
            $segments[] = [
                'speaker_name' => '화자 '.$currentSpeaker,
                'speaker_label' => (string) $currentSpeaker,
                'text' => $this->joinSentencePieceTokens($currentTokens),
                'start_time_ms' => $segmentStartMs,
                'end_time_ms' => $this->parseGoogleTimeToMs($lastWord['endOffset'] ?? $lastWord['endTime'] ?? $lastWord['end_offset'] ?? $lastWord['end_time'] ?? '0s'),
                'is_manual_speaker' => false,
            ];
        } elseif (! $hasSentencePiece && ! empty($currentWords)) {
            $lastWord = end($words);
            $segments[] = [
                'speaker_name' => '화자 '.$currentSpeaker,
                'speaker_label' => (string) $currentSpeaker,
                'text' => implode(' ', $currentWords),
                'start_time_ms' => $segmentStartMs,
                'end_time_ms' => $this->parseGoogleTimeToMs($lastWord['endOffset'] ?? $lastWord['endTime'] ?? $lastWord['end_offset'] ?? $lastWord['end_time'] ?? '0s'),
                'is_manual_speaker' => false,
            ];
        }

        // full_transcript 생성
        $fullTranscript = '';
        foreach ($segments as $seg) {
            $fullTranscript .= "[{$seg['speaker_name']}] {$seg['text']}\n";
        }

        $speakerCount = count(array_unique(array_column($segments, 'speaker_label')));

        Log::info('Google Cloud: STT V2 파싱 완료', [
            'segments' => count($segments),
            'speakers' => $speakerCount,
            'sentencePiece' => $hasSentencePiece,
        ]);

        return [
            'segments' => $segments,
            'full_transcript' => trim($fullTranscript),
            'speaker_count' => $speakerCount,
        ];
    }

    /**
     * V2 + Chirp 2 시도 → 실패 시 V1 + latest_long 자동 폴백
     *
     * @return array|null ['segments' => [...], 'full_transcript' => '...', 'speaker_count' => int, 'engine' => 'v2'|'v1']
     */
    public function speechToTextWithDiarizationAuto(
        string $gcsUri,
        string $languageCode = 'ko-KR',
        int $minSpeakers = 2,
        int $maxSpeakers = 6,
        array $phraseHints = []
    ): ?array {
        $projectId = $this->serviceAccount['project_id'] ?? null;

        // V2 + Chirp 2 시도
        if ($projectId) {
            Log::info('Google Cloud: STT V2 (Chirp 2) 시도');

            $v2Result = $this->speechToTextV2($gcsUri, $languageCode, $minSpeakers, $maxSpeakers, $phraseHints);

            if ($v2Result && ! empty($v2Result['segments'])) {
                $v2Result['engine'] = 'v2';

                return $v2Result;
            }

            Log::warning('Google Cloud: STT V2 실패, V1 폴백');
        }

        // V1 + latest_long 폴백
        Log::info('Google Cloud: STT V1 (latest_long) 폴백 실행');

        $v1Result = $this->speechToTextWithDiarization($gcsUri, $languageCode, $minSpeakers, $maxSpeakers);

        if ($v1Result) {
            $v1Result['engine'] = 'v1';
        }

        return $v1Result;
    }

    /**
     * GCS 파일 삭제
     */
    public function deleteFromStorage(string $objectName): bool
    {
        $token = $this->getAccessToken();
        if (! $token) {
            return false;
        }

        $bucket = config('services.google.storage_bucket');
        if (! $bucket) {
            return false;
        }

        try {
            $deleteUrl = 'https://storage.googleapis.com/storage/v1/b/'.
                urlencode($bucket).'/o/'.urlencode($objectName);

            $response = Http::withToken($token)->delete($deleteUrl);

            return $response->successful();
        } catch (\Exception $e) {
            Log::error('Google Cloud: Storage 삭제 예외', ['error' => $e->getMessage()]);

            return false;
        }
    }

    /**
     * GCS에서 파일 다운로드 (스트림)
     */
    public function downloadFromStorage(string $objectName): ?string
    {
        $token = $this->getAccessToken();
        if (! $token) {
            Log::error('Google Cloud: 다운로드 토큰 획득 실패');

            return null;
        }

        $bucket = config('services.google.storage_bucket');
        if (! $bucket) {
            Log::error('Google Cloud: Storage 버킷 설정 없음');

            return null;
        }

        try {
            $url = 'https://storage.googleapis.com/storage/v1/b/'.
                urlencode($bucket).'/o/'.urlencode($objectName).'?alt=media';

            $response = Http::withToken($token)->get($url);

            if ($response->successful()) {
                Log::info('Google Cloud: Storage 다운로드 성공', [
                    'object' => $objectName,
                    'size' => strlen($response->body()),
                ]);

                return $response->body();
            }

            Log::error('Google Cloud: Storage 다운로드 실패', [
                'status' => $response->status(),
                'response' => $response->body(),
            ]);

            return null;
        } catch (\Exception $e) {
            Log::error('Google Cloud: Storage 다운로드 예외', ['error' => $e->getMessage()]);

            return null;
        }
    }

    /**
     * 서비스 사용 가능 여부
     */
    public function isAvailable(): bool
    {
        return $this->serviceAccount !== null && $this->getAccessToken() !== null;
    }
}
