<?php

namespace App\Services;

use App\Helpers\AiTokenHelper;
use App\Models\Juil\MeetingMinute;
use App\Models\Juil\MeetingMinuteSegment;
use App\Models\System\AiConfig;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

class MeetingMinuteService
{
    public function __construct(
        private readonly GoogleCloudService $googleCloudService
    ) {}

    public function getList(array $params): LengthAwarePaginator
    {
        $query = MeetingMinute::with('user:id,name')
            ->orderBy('meeting_date', 'desc')
            ->orderBy('id', 'desc');

        if (! empty($params['search'])) {
            $search = $params['search'];
            $query->where(function ($q) use ($search) {
                $q->where('title', 'like', "%{$search}%")
                    ->orWhere('full_transcript', 'like', "%{$search}%");
            });
        }

        if (! empty($params['date_from'])) {
            $query->where('meeting_date', '>=', $params['date_from']);
        }

        if (! empty($params['date_to'])) {
            $query->where('meeting_date', '<=', $params['date_to']);
        }

        if (! empty($params['status'])) {
            $query->where('status', $params['status']);
        }

        $perPage = (int) ($params['per_page'] ?? 12);

        return $query->paginate($perPage);
    }

    public function create(array $data): MeetingMinute
    {
        return MeetingMinute::create([
            'tenant_id' => session('selected_tenant_id'),
            'user_id' => Auth::id(),
            'title' => $data['title'] ?? '무제 회의록',
            'folder' => $data['folder'] ?? null,
            'participants' => $data['participants'] ?? null,
            'meeting_date' => $data['meeting_date'] ?? now()->toDateString(),
            'meeting_time' => $data['meeting_time'] ?? now()->format('H:i'),
            'status' => MeetingMinute::STATUS_DRAFT,
            'stt_language' => $data['stt_language'] ?? 'ko-KR',
        ]);
    }

    public function update(MeetingMinute $meeting, array $data): MeetingMinute
    {
        $meeting->update(array_filter([
            'title' => $data['title'] ?? null,
            'folder' => array_key_exists('folder', $data) ? $data['folder'] : null,
            'participants' => array_key_exists('participants', $data) ? $data['participants'] : null,
            'meeting_date' => $data['meeting_date'] ?? null,
            'meeting_time' => $data['meeting_time'] ?? null,
        ], fn ($v) => $v !== null));

        return $meeting->fresh();
    }

    public function delete(MeetingMinute $meeting): bool
    {
        if ($meeting->audio_file_path) {
            $this->googleCloudService->deleteFromStorage($meeting->audio_file_path);
        }

        return $meeting->delete();
    }

    public function saveSegments(MeetingMinute $meeting, array $segments): MeetingMinute
    {
        // 기존 세그먼트 삭제 후 새로 생성
        $meeting->segments()->delete();

        $fullTranscript = '';

        foreach ($segments as $index => $segment) {
            $text = $segment['text'] ?? '';
            // 언더스코어 노이즈 제거
            $text = trim(preg_replace('/\s{2,}/', ' ', str_replace('_', '', $text)));

            MeetingMinuteSegment::create([
                'meeting_minute_id' => $meeting->id,
                'segment_order' => $index,
                'speaker_name' => $segment['speaker_name'] ?? '화자 1',
                'speaker_label' => $segment['speaker_label'] ?? null,
                'text' => $text,
                'start_time_ms' => $segment['start_time_ms'] ?? 0,
                'end_time_ms' => $segment['end_time_ms'] ?? null,
                'is_manual_speaker' => $segment['is_manual_speaker'] ?? true,
            ]);

            $speakerName = $segment['speaker_name'] ?? '화자 1';
            $fullTranscript .= "[{$speakerName}] {$text}\n";
        }

        $meeting->update([
            'full_transcript' => trim($fullTranscript),
            'status' => MeetingMinute::STATUS_DRAFT,
        ]);

        return $meeting->fresh()->load('segments');
    }

    public function uploadAudio(MeetingMinute $meeting, $file, int $durationSeconds): bool
    {
        $extension = $file->getClientOriginalExtension() ?: 'webm';
        $objectName = sprintf(
            'meeting-minutes/%d/%d/%s.%s',
            $meeting->tenant_id,
            $meeting->id,
            now()->format('YmdHis'),
            $extension
        );

        $tempPath = $file->getRealPath();
        $result = $this->googleCloudService->uploadToStorage($tempPath, $objectName);

        if (! $result) {
            Log::error('MeetingMinute: GCS 오디오 업로드 실패', [
                'meeting_id' => $meeting->id,
            ]);

            return false;
        }

        $meeting->update([
            'audio_file_path' => $objectName,
            'audio_gcs_uri' => $result['uri'],
            'audio_file_size' => $result['size'] ?? $file->getSize(),
            'duration_seconds' => $durationSeconds,
        ]);

        AiTokenHelper::saveGcsStorageUsage('회의록-GCS저장', $result['size'] ?? $file->getSize());

        return true;
    }

    public function generateSummary(MeetingMinute $meeting): ?array
    {
        if (empty($meeting->full_transcript)) {
            return null;
        }

        $meeting->update(['status' => MeetingMinute::STATUS_PROCESSING]);

        $config = AiConfig::getActiveGemini();

        if (! $config) {
            Log::warning('Gemini API 설정이 없습니다.');
            $meeting->update(['status' => MeetingMinute::STATUS_FAILED]);

            return null;
        }

        $prompt = $this->buildSummaryPrompt($meeting->full_transcript);

        try {
            $result = $this->callGeminiForSummary($config, $prompt);

            if (! $result) {
                $meeting->update(['status' => MeetingMinute::STATUS_FAILED]);

                return null;
            }

            $meeting->update([
                'summary' => $result['summary'] ?? null,
                'decisions' => $result['decisions'] ?? [],
                'action_items' => $result['action_items'] ?? [],
                'status' => MeetingMinute::STATUS_COMPLETED,
            ]);

            return $result;
        } catch (\Exception $e) {
            Log::error('MeetingMinute: Gemini 요약 실패', [
                'meeting_id' => $meeting->id,
                'error' => $e->getMessage(),
            ]);
            $meeting->update(['status' => MeetingMinute::STATUS_FAILED]);

            return null;
        }
    }

    public function logSttUsage(int $durationSeconds): void
    {
        AiTokenHelper::saveSttUsage('회의록-음성인식', $durationSeconds);
    }

    /**
     * 업로드된 오디오에 대해 자동 화자 분리(Speaker Diarization) 실행
     * V2 + Chirp 2 우선 시도, 실패 시 V1 + latest_long 자동 폴백
     */
    public function processDiarization(MeetingMinute $meeting, int $minSpeakers = 2, int $maxSpeakers = 6): ?array
    {
        if (empty($meeting->audio_gcs_uri)) {
            return null;
        }

        $meeting->update(['status' => MeetingMinute::STATUS_PROCESSING]);

        try {
            $result = $this->googleCloudService->speechToTextWithDiarizationAuto(
                $meeting->audio_gcs_uri,
                $meeting->stt_language ?? 'ko-KR',
                $minSpeakers,
                $maxSpeakers,
                $this->getDefaultPhraseHints()
            );

            if (! $result || empty($result['segments'])) {
                Log::warning('MeetingMinute: 화자 분리 결과 없음', ['meeting_id' => $meeting->id]);
                $meeting->update(['status' => MeetingMinute::STATUS_FAILED]);

                return null;
            }

            $engine = $result['engine'] ?? 'v1';
            $speakerCount = $result['speaker_count'] ?? 1;
            $segments = $result['segments'];

            Log::info('MeetingMinute: STT 화자 분리 완료', [
                'meeting_id' => $meeting->id,
                'engine' => $engine,
                'segments' => count($segments),
                'speaker_count' => $speakerCount,
            ]);

            // Google 화자분리가 1명만 인식한 경우 → Gemini AI로 화자 재분배
            if ($speakerCount <= 1 && count($segments) > 0 && $minSpeakers >= 2) {
                $fullText = implode(' ', array_map(fn ($s) => $s['text'] ?? '', $segments));
                $geminiSegments = $this->splitSpeakersWithGemini($fullText, $minSpeakers);

                if ($geminiSegments && count($geminiSegments) > 1) {
                    $segments = $geminiSegments;
                    $speakerCount = count(array_unique(array_column($segments, 'speaker_label')));
                    $engine .= '+gemini';
                    Log::info('MeetingMinute: Gemini 화자 재분배 완료', [
                        'meeting_id' => $meeting->id,
                        'segments' => count($segments),
                        'speaker_count' => $speakerCount,
                    ]);
                }
            }

            // 기존 세그먼트 교체
            $meeting->segments()->delete();
            $fullTranscript = '';

            foreach ($segments as $index => $segment) {
                MeetingMinuteSegment::create([
                    'meeting_minute_id' => $meeting->id,
                    'segment_order' => $index,
                    'speaker_name' => $segment['speaker_name'] ?? '화자 1',
                    'speaker_label' => $segment['speaker_label'] ?? null,
                    'text' => $segment['text'] ?? '',
                    'start_time_ms' => $segment['start_time_ms'] ?? 0,
                    'end_time_ms' => $segment['end_time_ms'] ?? null,
                    'is_manual_speaker' => false,
                ]);

                $speakerName = $segment['speaker_name'] ?? '화자 1';
                $text = $segment['text'] ?? '';
                $fullTranscript .= "[{$speakerName}] {$text}\n";
            }

            $meeting->update([
                'full_transcript' => trim($fullTranscript),
                'status' => MeetingMinute::STATUS_DRAFT,
            ]);

            // STT 사용량 기록 (엔진 구분)
            if ($meeting->duration_seconds > 0) {
                $usageLabel = $engine === 'v2' ? '회의록-화자분리(Chirp2)' : '회의록-화자분리';
                AiTokenHelper::saveSttUsage($usageLabel, $meeting->duration_seconds);
            }

            return [
                'segments' => $segments,
                'speaker_count' => $speakerCount,
                'full_transcript' => trim($fullTranscript),
            ];
        } catch (\Exception $e) {
            Log::error('MeetingMinute: 화자 분리 실패', [
                'meeting_id' => $meeting->id,
                'error' => $e->getMessage(),
            ]);
            $meeting->update(['status' => MeetingMinute::STATUS_FAILED]);

            return null;
        }
    }

    /**
     * Speech Adaptation 도메인 용어 힌트
     */
    private function getDefaultPhraseHints(): array
    {
        return [
            '블라인드', '스크린', '롤스크린', '허니콤', '버티컬',
            '원단', '바텀레일', '헤드레일', '브라켓',
            '주일', '경동', '주일블라인드', '경동블라인드',
            '수주', '발주', '납기', '출하', '재고', '원가', '단가',
            'SAM', 'ERP', 'MES',
        ];
    }

    /**
     * Gemini AI를 사용하여 단일 화자 텍스트를 대화 패턴으로 화자 분리
     */
    private function splitSpeakersWithGemini(string $fullText, int $expectedSpeakers = 2): ?array
    {
        $config = AiConfig::getActiveGemini();
        if (! $config) {
            return null;
        }

        $prompt = <<<PROMPT
다음은 {$expectedSpeakers}명이 대화한 회의 녹취록인데, 음성 인식이 화자를 구분하지 못해서 한 덩어리로 들어왔습니다.
대화의 맥락, 호칭, 질문-답변 패턴, 화제 전환, 어투 변화를 분석하여 화자를 분리해주세요.

## 규칙:
- 반드시 JSON 배열만 응답 (다른 텍스트 없이)
- 각 항목은 {"speaker": 1 또는 2, "text": "해당 화자의 발화"}
- 원본 텍스트를 빠짐없이 분배 (추가/삭제 금지)
- 화자 전환이 불확실하면, 문맥상 다른 사람이 말하는 것 같은 지점에서 분리
- 최소 2명 이상의 화자로 분리

## 응답 형식:
[
  {"speaker": 1, "text": "화자1의 발화 내용"},
  {"speaker": 2, "text": "화자2의 발화 내용"},
  {"speaker": 1, "text": "화자1의 다음 발화"}
]

## 녹취록:
{$fullText}
PROMPT;

        try {
            if ($config->isVertexAi()) {
                $responseText = $this->callVertexAiApi($config, $prompt);
            } else {
                $responseText = $this->callGoogleAiStudioApi($config, $prompt);
            }

            if (! $responseText) {
                return null;
            }

            // JSON 파싱
            $cleaned = preg_replace('/```json\s*/', '', $responseText);
            $cleaned = preg_replace('/```\s*/', '', $cleaned);
            $cleaned = trim($cleaned);

            $parsed = json_decode($cleaned, true);

            if (json_last_error() !== JSON_ERROR_NONE || ! is_array($parsed) || count($parsed) < 2) {
                Log::warning('MeetingMinute: Gemini 화자 분리 파싱 실패', [
                    'response' => mb_substr($responseText, 0, 500),
                ]);

                return null;
            }

            // 세그먼트 형식으로 변환
            $segments = [];
            foreach ($parsed as $item) {
                $speakerNum = $item['speaker'] ?? 1;
                $text = trim($item['text'] ?? '');
                if ($text === '') {
                    continue;
                }
                $segments[] = [
                    'speaker_name' => '화자 '.$speakerNum,
                    'speaker_label' => (string) $speakerNum,
                    'text' => $text,
                    'start_time_ms' => 0,
                    'end_time_ms' => null,
                    'is_manual_speaker' => false,
                ];
            }

            return count($segments) >= 2 ? $segments : null;
        } catch (\Exception $e) {
            Log::error('MeetingMinute: Gemini 화자 분리 예외', ['error' => $e->getMessage()]);

            return null;
        }
    }

    private function buildSummaryPrompt(string $transcript): string
    {
        return <<<PROMPT
다음은 회의 녹취록입니다. 이 내용을 분석하여 아래 JSON 형식으로 정확히 응답해주세요.
다른 텍스트 없이 JSON만 응답해주세요.

## 응답 형식 (JSON):
{
  "summary": "회의 전체 요약 (3-5문장)",
  "decisions": ["결정사항 1", "결정사항 2"],
  "action_items": [
    {"assignee": "담당자명", "task": "할일 내용", "deadline": "기한 또는 null"}
  ],
  "keywords": ["핵심 키워드1", "핵심 키워드2"]
}

## 주의사항:
- summary는 회의의 핵심 내용을 간결하게 요약
- decisions는 회의에서 확정된 결정사항만 포함
- action_items의 assignee는 녹취록에서 파악 가능한 경우만 기입, 불명확하면 "미정"
- keywords는 3-5개의 핵심 키워드

## 녹취록:
{$transcript}
PROMPT;
    }

    private function callGeminiForSummary(AiConfig $config, string $prompt): ?array
    {
        if ($config->isVertexAi()) {
            $responseText = $this->callVertexAiApi($config, $prompt);
        } else {
            $responseText = $this->callGoogleAiStudioApi($config, $prompt);
        }

        if (! $responseText) {
            return null;
        }

        // JSON 파싱 (코드블록 제거)
        $cleaned = preg_replace('/```json\s*/', '', $responseText);
        $cleaned = preg_replace('/```\s*/', '', $cleaned);
        $cleaned = trim($cleaned);

        $parsed = json_decode($cleaned, true);

        if (json_last_error() !== JSON_ERROR_NONE) {
            Log::warning('MeetingMinute: Gemini JSON 파싱 실패', [
                'response' => mb_substr($responseText, 0, 500),
            ]);

            return [
                'summary' => $responseText,
                'decisions' => [],
                'action_items' => [],
                'keywords' => [],
            ];
        }

        return $parsed;
    }

    private function callGoogleAiStudioApi(AiConfig $config, string $prompt): ?string
    {
        $model = $config->model;
        $apiKey = $config->api_key;
        $baseUrl = $config->base_url ?? 'https://generativelanguage.googleapis.com/v1beta';

        $url = "{$baseUrl}/models/{$model}:generateContent?key={$apiKey}";

        return $this->callGeminiApi($url, $prompt, [
            'Content-Type' => 'application/json',
        ], false);
    }

    private function callVertexAiApi(AiConfig $config, string $prompt): ?string
    {
        $model = $config->model;
        $projectId = $config->getProjectId();
        $region = $config->getRegion();

        if (! $projectId) {
            Log::error('Vertex AI 프로젝트 ID가 설정되지 않았습니다.');

            return null;
        }

        $accessToken = $this->getAccessToken($config);
        if (! $accessToken) {
            Log::error('Google Cloud 인증 실패');

            return null;
        }

        $url = "https://{$region}-aiplatform.googleapis.com/v1/projects/{$projectId}/locations/{$region}/publishers/google/models/{$model}:generateContent";

        return $this->callGeminiApi($url, $prompt, [
            'Authorization' => 'Bearer '.$accessToken,
            'Content-Type' => 'application/json',
        ], true);
    }

    private function getAccessToken(AiConfig $config): ?string
    {
        $configuredPath = $config->getServiceAccountPath();

        $possiblePaths = array_filter([
            $configuredPath,
            '/var/www/sales/apikey/google_service_account.json',
            storage_path('app/google_service_account.json'),
        ]);

        $serviceAccountPath = null;
        foreach ($possiblePaths as $path) {
            if ($path && file_exists($path)) {
                $serviceAccountPath = $path;
                break;
            }
        }

        if (! $serviceAccountPath) {
            Log::error('Service account file not found', ['tried_paths' => $possiblePaths]);

            return null;
        }

        $serviceAccount = json_decode(file_get_contents($serviceAccountPath), true);
        if (! $serviceAccount) {
            return null;
        }

        $now = time();
        $jwtHeader = $this->base64UrlEncode(json_encode(['alg' => 'RS256', 'typ' => 'JWT']));
        $jwtClaim = $this->base64UrlEncode(json_encode([
            'iss' => $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($serviceAccount['private_key']);
        if (! $privateKey) {
            return null;
        }

        openssl_sign($jwtHeader.'.'.$jwtClaim, $signature, $privateKey, OPENSSL_ALGO_SHA256);
        $jwt = $jwtHeader.'.'.$jwtClaim.'.'.$this->base64UrlEncode($signature);

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

            if ($response->successful()) {
                return $response->json()['access_token'] ?? null;
            }

            return null;
        } catch (\Exception $e) {
            Log::error('OAuth token request exception', ['error' => $e->getMessage()]);

            return null;
        }
    }

    private function base64UrlEncode(string $data): string
    {
        return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
    }

    private function callGeminiApi(string $url, string $prompt, array $headers, bool $isVertexAi = false): ?string
    {
        $content = [
            'parts' => [
                ['text' => $prompt],
            ],
        ];

        if ($isVertexAi) {
            $content['role'] = 'user';
        }

        try {
            $response = Http::timeout(120)
                ->withHeaders($headers)
                ->post($url, [
                    'contents' => [$content],
                    'generationConfig' => [
                        'temperature' => 0.3,
                        'topK' => 40,
                        'topP' => 0.95,
                        'maxOutputTokens' => 4096,
                    ],
                ]);

            if (! $response->successful()) {
                Log::error('MeetingMinute Gemini API error', [
                    'status' => $response->status(),
                    'body' => $response->body(),
                ]);

                return null;
            }

            $result = $response->json();

            AiTokenHelper::saveGeminiUsage($result, $result['modelVersion'] ?? 'gemini', '회의록-AI요약');

            return $result['candidates'][0]['content']['parts'][0]['text'] ?? null;
        } catch (\Exception $e) {
            Log::error('MeetingMinute Gemini API 예외', ['error' => $e->getMessage()]);

            return null;
        }
    }
}
