<?php

namespace App\Services;

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

/**
 * Google Cloud Vision API サービス
 * SHJ-1本人確認自動処理でOCR処理に使用
 */
class GoogleVisionService
{
    protected $apiKey;
    protected $projectId;
    protected $baseUrl = 'https://vision.googleapis.com/v1';

    public function __construct()
    {
        $this->apiKey = config('shj1.apis.google_vision.api_key');
        $this->projectId = config('shj1.apis.google_vision.project_id');
    }

    /**
     * 画像からテキストを抽出（OCR処理）
     * 
     * @param string $imageFilename 画像ファイル名
     * @return string 抽出されたテキスト
     * @throws Exception
     */
    public function extractTextFromImage(string $imageFilename): string
    {
        try {
            Log::info('GoogleVision OCR処理開始', [
                'filename' => $imageFilename
            ]);

            // 画像ファイルを取得
            $imageData = $this->getImageData($imageFilename);
            
            // Vision API リクエスト実行
            $detectedText = $this->callVisionApi($imageData);
            
            Log::info('GoogleVision OCR処理完了', [
                'filename' => $imageFilename,
                'text_length' => strlen($detectedText),
                'text_preview' => substr($detectedText, 0, 100) . '...',
                'text_full' => $detectedText,  // ← 完整のOCR結果を追加
                'debug_marker' => 'NEW_CODE_EXECUTED_' . time()  // ← デバッグマーカー追加
            ]);
            
            return $detectedText;
            
        } catch (Exception $e) {
            Log::error('GoogleVision OCR処理エラー', [
                'filename' => $imageFilename,
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);
            
            throw $e;
        }
    }

    /**
     * 画像データを取得（Laravel Storage使用）
     */
    private function getImageData(string $filename): string
    {
        $disk = config('shj1.storage.photo_disk');
        
        Log::info('GoogleVision 画像ファイル読取開始', [
            'filename' => $filename,
            'disk' => $disk
        ]);
        
        if (!Storage::disk($disk)->exists($filename)) {
            Log::error('GoogleVision 画像ファイルが見つかりません', [
                'filename' => $filename,
                'disk' => $disk
            ]);
            throw new Exception("画像ファイルが見つかりません: {$filename}");
        }
        
        $imageContent = Storage::disk($disk)->get($filename);
        
        if (empty($imageContent)) {
            Log::error('GoogleVision 画像ファイルが空です', [
                'filename' => $filename
            ]);
            throw new Exception("画像ファイルが空です: {$filename}");
        }

        Log::info('GoogleVision 画像ファイル読取成功', [
            'filename' => $filename,
            'size' => strlen($imageContent)
        ]);
        
        return base64_encode($imageContent);
    }

    /**
     * Google Cloud Vision API呼び出し
     */
    private function callVisionApi(string $base64Image): string
    {
        if (!$this->isApiKeyConfigured()) {
            throw new Exception("Google Vision API キーが設定されていません");
        }

        $url = "{$this->baseUrl}/images:annotate";
        
        $requestBody = [
            'requests' => [
                [
                    'image' => [
                        'content' => $base64Image
                    ],
                    'features' => [
                        [
                            'type' => 'TEXT_DETECTION',
                            'maxResults' => 1
                        ]
                    ],
                    'imageContext' => [
                        'languageHints' => ['ja'] // 日本語優先
                    ]
                ]
            ]
        ];

        $response = Http::timeout(30)
            ->withHeaders([
                'Content-Type' => 'application/json',
            ])
            ->post($url . '?key=' . $this->apiKey, $requestBody);

        if (!$response->successful()) {
            throw new Exception("Google Vision API request failed: " . $response->status());
        }

        $data = $response->json();

        // APIエラーチェック
        if (isset($data['responses'][0]['error'])) {
            $error = $data['responses'][0]['error'];
            throw new Exception("Google Vision API error: " . $error['message']);
        }

        // テキスト抽出結果を取得
        $textAnnotations = $data['responses'][0]['textAnnotations'] ?? [];
        
        if (empty($textAnnotations)) {
            return ''; // テキストが検出されなかった場合
        }

        // 最初のアノテーションが全体のテキスト
        return $textAnnotations[0]['description'] ?? '';
    }

    /**
     * 文字列の類似度を計算
     * 
     * @param string $expected 期待する文字列
     * @param string $detected 検出された文字列
     * @return float 類似度（0-100）
     */
    public function calculateSimilarity(string $expected, string $detected): float
    {
        // 前処理：空白文字削除、大文字小文字統一
        $expected = $this->normalizeText($expected);
        $detected = $this->normalizeText($detected);
        
        if (empty($expected) || empty($detected)) {
            return 0.0;
        }
        
        // 複数の計算方法を試し、最高値を返す
        $similarities = [];
        
        // 1. 標準的な類似度計算
        similar_text($expected, $detected, $percent);
        $similarities[] = $percent;
        
        // 2. 文字含有率計算（OCRの分離文字対応）
        $containsScore = $this->calculateContainsScore($expected, $detected);
        $similarities[] = $containsScore;
        
        // 3. 部分文字列マッチング
        $substringScore = $this->calculateSubstringScore($expected, $detected);
        $similarities[] = $substringScore;
        
        // 最高スコアを返す
        return max($similarities);
    }
    
    /**
     * 文字含有率スコア計算（姓名の分離文字対応）
     */
    private function calculateContainsScore(string $expected, string $detected): float
    {
        if (empty($expected)) return 0.0;
        
        // 期待文字列の各文字が検出文字列に含まれるかチェック
        $expectedChars = mb_str_split($expected, 1, 'UTF-8');
        $containedCount = 0;
        
        foreach ($expectedChars as $char) {
            if (mb_strpos($detected, $char, 0, 'UTF-8') !== false) {
                $containedCount++;
            }
        }
        
        return ($containedCount / count($expectedChars)) * 100;
    }
    
    /**
     * 部分文字列マッチングスコア計算
     */
    private function calculateSubstringScore(string $expected, string $detected): float
    {
        if (empty($expected)) return 0.0;
        
        // 期待文字列を2文字以上のチャンクに分割してマッチング
        $chunks = [];
        $expectedLength = mb_strlen($expected, 'UTF-8');
        
        // 2文字チャンク
        for ($i = 0; $i <= $expectedLength - 2; $i++) {
            $chunks[] = mb_substr($expected, $i, 2, 'UTF-8');
        }
        
        // 3文字チャンク
        for ($i = 0; $i <= $expectedLength - 3; $i++) {
            $chunks[] = mb_substr($expected, $i, 3, 'UTF-8');
        }
        
        if (empty($chunks)) return 0.0;
        
        $matchedChunks = 0;
        foreach ($chunks as $chunk) {
            if (mb_strpos($detected, $chunk, 0, 'UTF-8') !== false) {
                $matchedChunks++;
            }
        }
        
        return ($matchedChunks / count($chunks)) * 100;
    }

    /**
     * テキスト正規化（OCR認識結果の形式差異に対応）
     */
    private function normalizeText(string $text): string
    {
        // 空白文字除去、改行除去
        $text = preg_replace('/\s+/', '', $text);
        
        // 全角英数字・記号を半角に変換
        $text = mb_convert_kana($text, 'rnask', 'UTF-8');
        
        // 住所の同義語統一
        $text = str_replace(['東京市', '東京府'], '東京都', $text);
        $text = str_replace(['大阪市', '大阪府'], '大阪', $text);
        
        // 数字の統一（全角→半角は上でやっているが、明示的に処理）
        $text = str_replace(['１', '２', '３', '４', '５', '６', '７', '８', '９', '０'], 
                          ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'], $text);
        
        // 記号の統一
        $text = str_replace(['ー', '−', '―'], '-', $text);
        $text = str_replace(['の'], '', $text);  // 「1番地の1」→「1番地1」
        
        return $text;
    }

    /**
     * 部分マッチング検索
     * 
     * @param string $needle 検索する文字列
     * @param string $haystack 検索対象の文字列
     * @return bool
     */
    public function containsText(string $needle, string $haystack): bool
    {
        $needle = $this->normalizeText($needle);
        $haystack = $this->normalizeText($haystack);
        
        return mb_strpos($haystack, $needle) !== false;
    }

    /**
     * 画像形式の検証
     */
    public function isValidImageFormat(string $filename): bool
    {
        $allowedExtensions = config('shj1.storage.allowed_extensions');
        $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
        
        return in_array($extension, $allowedExtensions);
    }

    /**
     * APIキーが設定されているかチェック
     */
    public function isApiKeyConfigured(): bool
    {
        $dummyKey = env('GOOGLE_VISION_API_KEY') === 'dummy_google_vision_api_key_replace_with_real_one';
        return !empty($this->apiKey) && !$dummyKey;
    }

    /**
     * Vision API レスポンスから信頼度を取得
     */
    public function getConfidenceScore(array $textAnnotation): float
    {
        if (!isset($textAnnotation['boundingPoly']['vertices'])) {
            return 0.0;
        }
        
        // 検出領域のサイズから信頼度を推定
        $vertices = $textAnnotation['boundingPoly']['vertices'];
        if (count($vertices) < 4) {
            return 0.0;
        }
        
        // 単純な信頼度計算（実際の実装では、より詳細な分析が必要）
        return 0.95; // デフォルト高信頼度
    }
}
