<?php

namespace App\Services;

use App\Helpers\AiTokenHelper;
use App\Models\System\AiConfig;
use App\Models\System\AiPricingConfig;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;

class RagSearchService
{
    /**
     * 문서 검색 기본 경로
     */
    private string $docsBasePath;

    /**
     * 검색 대상 확장자
     */
    private array $extensions = ['md', 'php', 'js', 'json'];

    public function __construct()
    {
        $this->docsBasePath = '/var/www/docs';
    }

    /**
     * RAG 검색 수행
     *
     * 1. docs 폴더에서 키워드 기반 문서 검색
     * 2. 관련 문서 내용을 Gemini API에 컨텍스트로 전달
     * 3. 답변 생성 및 반환
     */
    public function search(string $query): array
    {
        $config = AiConfig::getActiveGemini();

        if (! $config) {
            return [
                'ok' => false,
                'error' => 'Gemini API 설정이 없습니다. 시스템설정 > AI 설정에서 Gemini를 추가해주세요.',
            ];
        }

        // 1. 관련 문서 검색
        $documents = $this->findRelevantDocuments($query);

        if (empty($documents)) {
            return [
                'ok' => false,
                'error' => '관련 문서를 찾지 못했습니다. 다른 키워드로 검색해보세요.',
                'searched_count' => 0,
            ];
        }

        // 2. 컨텍스트 구성
        $context = $this->buildContext($documents);

        // 3. Gemini API 호출
        return $this->callGemini($config, $query, $context, $documents);
    }

    /**
     * docs 폴더에서 키워드 기반으로 관련 문서 검색
     */
    private function findRelevantDocuments(string $query): array
    {
        $keywords = $this->extractKeywords($query);
        $files = $this->getAllDocFiles();
        $scoredFiles = [];

        foreach ($files as $filePath) {
            $content = @file_get_contents($filePath);
            if ($content === false || empty(trim($content))) {
                continue;
            }

            $score = $this->calculateRelevance($content, $filePath, $keywords);

            if ($score > 0) {
                $relativePath = str_replace($this->docsBasePath.'/', '', $filePath);
                $scoredFiles[] = [
                    'path' => $relativePath,
                    'full_path' => $filePath,
                    'score' => $score,
                    'content' => $content,
                    'size' => strlen($content),
                ];
            }
        }

        // 점수 내림차순 정렬
        usort($scoredFiles, fn ($a, $b) => $b['score'] <=> $a['score']);

        // 상위 문서 선택 (토큰 제한 고려: ~100KB 이내)
        $selected = [];
        $totalSize = 0;
        $maxSize = 100 * 1024; // 100KB

        foreach ($scoredFiles as $file) {
            if ($totalSize + $file['size'] > $maxSize) {
                continue;
            }
            $selected[] = $file;
            $totalSize += $file['size'];

            if (count($selected) >= 10) {
                break;
            }
        }

        return $selected;
    }

    /**
     * 쿼리에서 키워드 추출
     */
    private function extractKeywords(string $query): array
    {
        // 한글/영문/숫자 토큰 분리
        preg_match_all('/[\p{L}\p{N}]+/u', mb_strtolower($query), $matches);
        $tokens = $matches[0] ?? [];

        // 불용어 제거
        $stopWords = ['은', '는', '이', '가', '을', '를', '에', '의', '에서', '으로', '로', '와', '과', '도', '하다', '있다', '없다', '되다', '것', '수', '등', '및', '때', '중', '더', '안', '해', '이런', '저런', '그런', '어떤', '무엇', '어떻게', '왜', '뭐', '좀', 'the', 'a', 'an', 'is', 'are', 'was', 'were', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'how', 'what', 'why', 'when', 'where'];

        return array_values(array_filter($tokens, function ($token) use ($stopWords) {
            return mb_strlen($token) >= 2 && ! in_array($token, $stopWords);
        }));
    }

    /**
     * 문서 관련도 점수 계산
     */
    private function calculateRelevance(string $content, string $filePath, array $keywords): float
    {
        $score = 0.0;
        $lowerContent = mb_strtolower($content);
        $lowerPath = mb_strtolower($filePath);

        foreach ($keywords as $keyword) {
            // 본문 매칭
            $count = mb_substr_count($lowerContent, $keyword);
            $score += min($count, 10) * 1.0;

            // 파일 경로 매칭 (가중치 높음)
            if (mb_strpos($lowerPath, $keyword) !== false) {
                $score += 5.0;
            }
        }

        // 파일 유형 가중치
        if (str_ends_with($filePath, '.md')) {
            $score *= 1.2; // Markdown 문서 우선
        }

        // INDEX.md, README.md 가중치
        $basename = basename($filePath);
        if (in_array($basename, ['INDEX.md', 'README.md'])) {
            $score *= 1.1;
        }

        return $score;
    }

    /**
     * docs 폴더의 모든 대상 파일 목록
     */
    private function getAllDocFiles(): array
    {
        $files = [];
        $basePath = realpath($this->docsBasePath);

        if (! $basePath || ! is_dir($basePath)) {
            return [];
        }

        $iterator = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($basePath, \RecursiveDirectoryIterator::SKIP_DOTS),
            \RecursiveIteratorIterator::LEAVES_ONLY
        );

        foreach ($iterator as $file) {
            if (! $file->isFile()) {
                continue;
            }

            $ext = strtolower($file->getExtension());
            if (! in_array($ext, $this->extensions)) {
                continue;
            }

            // node_modules, vendor, .git 등 제외
            $path = $file->getPathname();
            if (preg_match('#/(node_modules|vendor|\.git|pptx-output|assets)/#', $path)) {
                continue;
            }

            // 파일 크기 제한 (50KB 이하만)
            if ($file->getSize() > 50 * 1024) {
                continue;
            }

            $files[] = $path;
        }

        return $files;
    }

    /**
     * 검색된 문서들로 Gemini 컨텍스트 구성
     */
    private function buildContext(array $documents): string
    {
        $context = '';

        foreach ($documents as $doc) {
            $context .= "=== 문서: {$doc['path']} ===\n";
            $context .= $doc['content'];
            $context .= "\n\n";
        }

        return $context;
    }

    /**
     * Gemini API 호출
     */
    private function callGemini(AiConfig $config, string $query, string $context, array $documents): array
    {
        $systemPrompt = <<<'PROMPT'
당신은 SAM(Smart Automation Management) 프로젝트의 기술 문서 어시스턴트입니다.

역할:
- 제공된 문서 컨텍스트를 기반으로 사용자의 질문에 정확히 답변합니다.
- 문서에 없는 내용은 "해당 정보는 현재 문서에서 찾을 수 없습니다"라고 답변합니다.
- 답변은 한글로 작성하며, 코드/경로/기술 용어는 원문 그대로 사용합니다.
- 관련 파일 경로가 있으면 함께 안내합니다.

답변 형식:
- Markdown 형식으로 작성합니다.
- 코드 블록에는 언어를 지정합니다.
- 핵심을 먼저 말하고, 상세 설명은 뒤에 배치합니다.
PROMPT;

        $userMessage = "## 질문\n{$query}\n\n## 참조 문서\n{$context}";

        $parts = [
            ['text' => $systemPrompt],
            ['text' => $userMessage],
        ];

        $body = [
            'contents' => [
                ['parts' => $parts],
            ],
            'generationConfig' => [
                'temperature' => 0.3,
                'maxOutputTokens' => 4096,
            ],
        ];

        try {
            if ($config->isVertexAi()) {
                $response = $this->callVertexAi($config, $body);
            } else {
                $response = $this->callGoogleAiStudio($config, $body);
            }

            if (! $response->successful()) {
                Log::error('RagSearchService: Gemini API 호출 실패', [
                    'status' => $response->status(),
                    'body' => $response->body(),
                ]);

                return [
                    'ok' => false,
                    'error' => 'AI 응답 생성에 실패했습니다. (HTTP '.$response->status().')',
                ];
            }

            $result = $response->json();
            $answer = $result['candidates'][0]['content']['parts'][0]['text'] ?? '';

            // 토큰 사용량 계산
            $usage = $result['usageMetadata'] ?? [];
            $promptTokens = $usage['promptTokenCount'] ?? 0;
            $completionTokens = $usage['candidatesTokenCount'] ?? 0;
            $totalTokens = $usage['totalTokenCount'] ?? 0;

            // 비용 계산
            $pricing = AiPricingConfig::getActivePricing('gemini');
            $inputPrice = $pricing ? (float) $pricing->input_price_per_million / 1_000_000 : 0.10 / 1_000_000;
            $outputPrice = $pricing ? (float) $pricing->output_price_per_million / 1_000_000 : 0.40 / 1_000_000;
            $costUsd = ($promptTokens * $inputPrice) + ($completionTokens * $outputPrice);
            $exchangeRate = AiPricingConfig::getExchangeRate();
            $costKrw = $costUsd * $exchangeRate;

            // 토큰 사용량 DB 저장
            AiTokenHelper::saveGeminiUsage($result, $result['modelVersion'] ?? $config->model, 'RAG검색');

            // 참조 문서 목록 추출
            $references = array_map(fn ($doc) => [
                'path' => $doc['path'],
                'score' => round($doc['score'], 1),
            ], $documents);

            return [
                'ok' => true,
                'answer' => $answer,
                'references' => $references,
                'searched_count' => count($documents),
                'model' => $config->model,
                'token_usage' => [
                    'prompt_tokens' => $promptTokens,
                    'completion_tokens' => $completionTokens,
                    'total_tokens' => $totalTokens,
                    'cost_usd' => round($costUsd, 6),
                    'cost_krw' => round($costKrw, 2),
                ],
            ];

        } catch (\Exception $e) {
            Log::error('RagSearchService: 예외 발생', ['error' => $e->getMessage()]);

            return [
                'ok' => false,
                'error' => 'AI 호출 중 오류가 발생했습니다: '.$e->getMessage(),
            ];
        }
    }

    /**
     * Google AI Studio API 호출 (API 키 인증)
     */
    private function callGoogleAiStudio(AiConfig $config, array $body): \Illuminate\Http\Client\Response
    {
        $model = $config->model;
        $apiKey = $config->api_key;
        $baseUrl = $config->base_url ?? 'https://generativelanguage.googleapis.com/v1beta';

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

        return Http::timeout(120)->post($url, $body);
    }

    /**
     * Vertex AI API 호출 (서비스 계정 인증)
     */
    private function callVertexAi(AiConfig $config, array $body): \Illuminate\Http\Client\Response
    {
        $model = $config->model;
        $projectId = $config->getProjectId();
        $region = $config->getRegion();

        $accessToken = $this->getAccessToken($config);
        if (! $accessToken) {
            throw new \RuntimeException('Google Cloud 인증 실패');
        }

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

        // Vertex AI는 role 필수
        $body['contents'][0]['role'] = 'user';

        return Http::withToken($accessToken)->timeout(120)->post($url, $body);
    }

    /**
     * Vertex AI 용 OAuth 액세스 토큰 발급
     */
    private function getAccessToken(AiConfig $config): ?string
    {
        $serviceAccountPath = $config->getServiceAccountPath();
        if (! $serviceAccountPath || ! file_exists($serviceAccountPath)) {
            return null;
        }

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

        try {
            $now = time();
            $header = base64_encode(json_encode(['alg' => 'RS256', 'typ' => 'JWT']));
            $claim = base64_encode(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']);
            openssl_sign("{$header}.{$claim}", $signature, $privateKey, OPENSSL_ALGO_SHA256);

            $jwt = "{$header}.{$claim}.".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()) {
                return $response->json('access_token');
            }

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

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

            return null;
        }
    }

    /**
     * 검색 가능한 문서 통계 조회
     */
    public function getDocStats(): array
    {
        $files = $this->getAllDocFiles();
        $totalSize = 0;
        $byExtension = [];

        foreach ($files as $file) {
            $size = filesize($file);
            $totalSize += $size;
            $ext = pathinfo($file, PATHINFO_EXTENSION);
            $byExtension[$ext] = ($byExtension[$ext] ?? 0) + 1;
        }

        return [
            'total_files' => count($files),
            'total_size_kb' => round($totalSize / 1024),
            'by_extension' => $byExtension,
        ];
    }
}
