<?php

namespace App\Services;

use App\Models\User;
use App\Models\Usertype;
use App\Models\Park;
use App\Models\RegularContract;
use App\Models\OperatorQue;
use App\Models\Device;
use App\Services\GoogleVisionService;
use App\Services\GoogleMapsService;
use App\Services\ShjMailSendService;
use App\Services\ShjEightService;
use Exception;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;

/**
 * SHJ-1 本人確認自動処理 Service
 */
class ShjOneService
{
    protected $googleVisionService;
    protected $googleMapsService;
    protected $mailSendService;
    protected $shjEightService;

    public function __construct(
        GoogleVisionService $googleVisionService,
        GoogleMapsService $googleMapsService,
        ShjMailSendService $mailSendService,
        ShjEightService $shjEightService
    ) {
        $this->googleVisionService = $googleVisionService;
        $this->googleMapsService = $googleMapsService;
        $this->mailSendService = $mailSendService;
        $this->shjEightService = $shjEightService;
    }

    /**
     * SHJ-1メイン処理実行
     * 
     * @param int $userId 利用者連番
     * @param int $parkId 駐輪場ID
     * @return array
     */
    public function execute(int $userId, int $parkId): array
    {
        try {
            Log::info('SHJ-1 【処理１】開始: 本人確認自動処理レコード群取得', [
                'user_id' => $userId,
                'park_id' => $parkId
            ]);

            // 【処理１】本人確認自動処理レコード群取得 - 設計書通りの条件でフィルタリング
            $user = $this->getUserRecord($userId);
            if (!$user) {
                Log::error('SHJ-1 【処理１】失敗: 利用者が見つかりません', [
                    'user_id' => $userId,
                    'park_id' => $parkId
                ]);
                $result = [
                    'system_success' => false,
                    'message' => '利用者が見つかりません',
                    'stats' => ['error_count' => 1, 'processed_count' => 0]
                ];
                $this->createBatchLog($userId, null, null, $result);
                return $result;
            }

            Log::info('SHJ-1 【処理１】成功: 利用者データ取得', [
                'user_seq' => $user->user_seq,
                'user_name' => $user->user_name,
                'user_idcard' => $user->user_idcard,
                'photo_filename1' => $user->photo_filename1,
                'photo_filename2' => $user->photo_filename2
            ]);

            // 本人確認書類が免許証以外、または写真ファイルがない場合は対象外
            if (!$this->isTargetUser($user)) {
                Log::warning('SHJ-1 処理対象外判定', [
                    'user_seq' => $user->user_seq,
                    'user_idcard' => $user->user_idcard,
                    'photo_filename1' => $user->photo_filename1,
                    'user_idcard_chk_flag' => $user->user_idcard_chk_flag,
                    'reason' => '免許証以外または写真なしまたは既に処理済み'
                ]);
                $result = [
                    'system_success' => false,
                    'message' => '処理対象外の利用者です（免許証以外または写真なし）',
                    'stats' => ['error_count' => 1, 'processed_count' => 0]
                ];
                $this->createBatchLog($userId, $user->user_name, null, $result);
                return $result;
            }

            $park = $this->getParkRecord($parkId);
            if (!$park) {
                Log::error('SHJ-1 駐輪場データ取得失敗', [
                    'park_id' => $parkId
                ]);
                $result = [
                    'system_success' => false,
                    'message' => '駐輪場が見つかりません',
                    'stats' => ['error_count' => 1, 'processed_count' => 0]
                ];
                $this->createBatchLog($userId, $user->user_name, null, $result);
                return $result;
            }

            Log::info('SHJ-1 駐輪場データ取得成功', [
                'park_id' => $park->park_id,
                'park_name' => $park->park_name
            ]);

            // トランザクション開始
            DB::beginTransaction();
            Log::info('SHJ-1 データベーストランザクション開始');

            // 【判断１】対象外判定
            Log::info('SHJ-1 【判断１】開始: 利用者分類チェック');
            $categoryCheck = $this->checkUserCategory($user);
            
            if (!$categoryCheck['is_target']) {
                Log::warning('SHJ-1 【判断１】対象外: 手動処理対象', [
                    'user_seq' => $user->user_seq,
                    'category_name' => $categoryCheck['category_name'],
                    'reason' => '高齢者、障がい者、生活保護、中国、母子家庭等'
                ]);
                $result = $this->processNonTargetUser($user, $categoryCheck['category_name']);
                DB::commit(); // 対象外処理後はコミット
                $this->createBatchLog($userId, $user->user_name, null, $result);
                return $result;
            }

            Log::info('SHJ-1 【判断１】成功: 自動処理対象', [
                'user_seq' => $user->user_seq,
                'category_name' => $categoryCheck['category_name']
            ]);

            // 【処理２】本人確認自動処理＋800Mチェック処理
            Log::info('SHJ-1 【処理２】開始: 本人確認自動処理');
            $identityResult = $this->processIdentityVerification($user, $park);

            DB::commit();

            // バッチ処理ログ作成
            $this->createBatchLog($userId, $user->user_name, $identityResult['similarity_rate'] ?? null, $identityResult);

            return $identityResult;
            
        } catch (Exception $e) {
            DB::rollBack();
            Log::error('SHJ-1処理エラー', [
                'user_id' => $userId,
                'park_id' => $parkId,
                'error' => $e->getMessage()
            ]);

            $result = [
                'system_success' => false,
                'message' => 'システムエラーが発生しました: ' . $e->getMessage(),
                'stats' => ['error_count' => 1, 'processed_count' => 0]
            ];

            // エラー時もバッチログ作成
            $this->createBatchLog($userId, null, null, $result);

            return $result;
        }
    }

    /**
     * 利用者レコード取得（設計書の条件に従ってフィルタリング）
     */
    private function getUserRecord(int $userId): ?User
    {
        return User::where('user_seq', $userId)
            ->whereNotNull('photo_filename1') // 本人確認写真必須
            ->first();
    }

    /**
     * 処理対象ユーザーかチェック（設計書の条件）
     */
    private function isTargetUser(User $user): bool
    {
        Log::info('SHJ-1 isTargetUser チェック開始', [
            'user_seq' => $user->user_seq,
            'user_idcard' => $user->user_idcard,
            'photo_filename1' => $user->photo_filename1,
            'user_idcard_chk_flag' => $user->user_idcard_chk_flag,
            'user_idcard_chk_flag_type' => gettype($user->user_idcard_chk_flag),
            'auto_ok_config' => config('shj1.identity_check_status.auto_ok')
        ]);

        // 本人確認書類が免許証であること
        if ($user->user_idcard !== '免許証') {
            Log::info('SHJ-1 isTargetUser: 免許証以外', ['user_idcard' => $user->user_idcard]);
            return false;
        }

        // 写真ファイルが存在すること
        if (empty($user->photo_filename1)) {
            Log::info('SHJ-1 isTargetUser: 写真ファイルなし');
            return false;
        }

        // 既に本人確認済みの場合はスキップ（一度OKなら再チェック省略）
        // ※テスト用に一時的にコメントアウト - 重複処理を許可
        /*
        $autoOk = config('shj1.identity_check_status.auto_ok');
        if ($user->user_idcard_chk_flag === $autoOk) {  // 厳密比較に変更
            Log::info('SHJ-1 isTargetUser: 既に処理済み', [
                'current_flag' => $user->user_idcard_chk_flag,
                'current_flag_type' => gettype($user->user_idcard_chk_flag),
                'auto_ok' => $autoOk,
                'auto_ok_type' => gettype($autoOk),
                'loose_comparison' => $user->user_idcard_chk_flag == $autoOk ? 'equal' : 'not_equal',
                'strict_comparison' => $user->user_idcard_chk_flag === $autoOk ? 'equal' : 'not_equal'
            ]);
            return false;
        }
        */

        Log::info('SHJ-1 isTargetUser: 処理対象OK');
        return true;
    }

    /**
     * 駐輪場レコード取得
     */
    private function getParkRecord(int $parkId): ?Park
    {
        return Park::where('park_id', $parkId)->first();
    }

    /**
     * 利用者分類チェック
     */
    private function checkUserCategory(User $user): array
    {
        $usertype = Usertype::where('user_categoryid', $user->user_categoryid)->first();
        
        if (!$usertype) {
            return ['is_target' => false, 'category_name' => 'unknown'];
        }

        $categoryName = $usertype->usertype_subject3;
        $excludedCategories = config('shj1.user_categories.excluded_categories');
        
        // 対象外カテゴリーかチェック
        $isExcluded = in_array($categoryName, $excludedCategories);
        
        return [
            'is_target' => !$isExcluded,
            'category_name' => $categoryName
        ];
    }

    /**
     * 対象外ユーザーの処理
     */
    private function processNonTargetUser(User $user, string $categoryName): array
    {
        // 本人確認済ステータスをNGで更新（設計書通り）
        $autoNgValue = config('shj1.identity_check_status.auto_ng');
        
        Log::info('SHJ-1 対象外ユーザー: ユーザー情報更新開始', [
            'user_seq' => $user->user_seq,
            'category_name' => $categoryName,
            'before_user_idcard_chk_flag' => $user->user_idcard_chk_flag,
            'will_set_to' => $autoNgValue
        ]);
        
        $updateResult = $user->update([
            'user_idcard_chk_flag' => $autoNgValue,
            'user_chk_day' => now(),
            'user_chk_opeid' => 'SHJ-1'
        ]);
        
        Log::info('SHJ-1 対象外ユーザー: ユーザー情報更新完了', [
            'user_seq' => $user->user_seq,
            'update_result' => $updateResult,
            'after_user_idcard_chk_flag' => $user->fresh()->user_idcard_chk_flag
        ]);

        // オペレータキューも作成（設計書の要求）
        $this->createOperatorQueue($user, null, [], true);

        return [
            'system_success' => true,
            'identity_result' => 'NG',
            'message' => '対象外ユーザーのためNG処理完了',
            'details' => [
                'user_id' => $user->user_seq,
                'category' => $categoryName,
                'action' => 'set_ng_status_with_queue'
            ],
            'stats' => ['processed_count' => 1, 'ng_count' => 1, 'non_target_count' => 1]
        ];
    }

    /**
     * 本人確認自動処理＋800Mチェック処理（SHJ-1文書仕様）
     * 
     * 文書仕様：
     * 1. まずOCR照合処理を実行
     * 2. OCR照合成功の場合のみ距離チェック処理を実行
     * 3. 両方成功で最終成功
     */
    private function processIdentityVerification(User $user, Park $park): array
    {
        try {
            // 1. OCR処理による本人確認
            Log::info('SHJ-1 【処理２】開始: 本人確認自動処理');
            $ocrResult = $this->performComprehensiveOcrVerification($user);
            
            // OCR処理失敗の場合、距離チェックを実行せずに失敗処理
            if (!$ocrResult['success']) {
                Log::info('SHJ-1 OCR照合失敗、距離チェックスキップ', [
                    'user_seq' => $user->user_seq,
                    'reason' => $ocrResult['reason']
                ]);
                
                // 距離チェック結果をダミーで設定（実行しない）
                $distanceResult = [
                    'within_limit' => true, // OCR失敗なので距離は関係ない
                    'skipped' => true,
                    'reason' => 'ocr_failed'
                ];
                
                return $this->processFailureCase($user, $park, $ocrResult, $distanceResult);
            }
            
            // 2. OCR成功 → 距離チェック処理を実行
            Log::info('SHJ-1 OCR照合成功、距離チェック開始', [
                'user_seq' => $user->user_seq,
                'matched_address_type' => $ocrResult['matched_address_type']
            ]);
            
            $distanceResult = $this->performDistanceCheck($user, $park);
            
            // 3. 最終判定：OCR成功 AND 距離内
            if ($distanceResult['within_limit']) {
                return $this->processSuccessCase($user, $park, $ocrResult, $distanceResult);
            } else {
                return $this->processFailureCase($user, $park, $ocrResult, $distanceResult);
            }
            
        } catch (Exception $e) {
            Log::error('SHJ-1 【処理２】エラー', [
                'user_seq' => $user->user_seq,
                'park_id' => $park->park_id,
                'error' => $e->getMessage()
            ]);
            
            // システムエラーとして失敗処理
            return $this->processFailureCase($user, $park, 
                ['success' => false, 'reason' => 'system_error'], 
                ['within_limit' => true, 'skipped' => true, 'reason' => 'system_error']
            );
        }
    }

    /**
     * 包括的OCR処理（SHJ-1文書仕様に従った実装）
     * 
     * 文書仕様：
     * - 表面画像で一度OKが出た場合、裏面のチェックはスキップする
     * - [OCR值]と[利用者住所]を照合する
     */
    private function performComprehensiveOcrVerification(User $user): array
    {
        try {
            Log::info('SHJ-1 OCR処理開始', [
                'user_seq' => $user->user_seq,
                'photo_filename1' => $user->photo_filename1,
                'photo_filename2' => $user->photo_filename2
            ]);

            // 表面画像の処理
            if ($user->photo_filename1) {
                $photoPath = $this->buildPhotoPath($user->photo_filename1);
                Log::info('SHJ-1 表面画像処理開始', [
                    'user_seq' => $user->user_seq,
                    'photo_filename1' => $user->photo_filename1,
                    'photo_path' => $photoPath
                ]);
                
                $frontResult = $this->processImageForOcr($user, $photoPath, 'front');
                
                Log::info('SHJ-1 表面画像処理完了', [
                    'user_seq' => $user->user_seq,
                    'front_result' => $frontResult
                ]);
                
                // SHJ-1文書通り：一度OKが出た場合、裏面チェックはスキップ
                if ($frontResult['success']) {
                    Log::info('SHJ-1 表面画像でOK、裏面チェックスキップ', [
                        'user_seq' => $user->user_seq
                    ]);
                    
                    return [
                        'success' => true,
                        'processed_side' => 'front',
                        'extracted_name' => $frontResult['extracted_name'],
                        'extracted_address' => $frontResult['extracted_address'],
                        'ocr_value' => $frontResult['ocr_value'],
                        'matched_address_type' => $frontResult['matched_address_type'],
                        'matched_address' => $frontResult['matched_address'],
                        'similarity' => $frontResult['similarity'],
                        'reason' => $frontResult['reason']
                    ];
                }
            }
            
            // 表面画像がNGまたは存在しない場合、裏面画像を処理
            if ($user->photo_filename2) {
                $photoPath = $this->buildPhotoPath($user->photo_filename2);
                Log::info('SHJ-1 裏面画像処理開始', [
                    'user_seq' => $user->user_seq,
                    'photo_filename2' => $user->photo_filename2,
                    'photo_path' => $photoPath
                ]);
                
                $backResult = $this->processImageForOcr($user, $photoPath, 'back');
                
                Log::info('SHJ-1 裏面画像処理完了', [
                    'user_seq' => $user->user_seq,
                    'back_result' => $backResult
                ]);
                
                if ($backResult['success']) {
                    return [
                        'success' => true,
                        'processed_side' => 'back',
                        'extracted_name' => $backResult['extracted_name'],
                        'extracted_address' => $backResult['extracted_address'],
                        'ocr_value' => $backResult['ocr_value'],
                        'matched_address_type' => $backResult['matched_address_type'],
                        'matched_address' => $backResult['matched_address'],
                        'similarity' => $backResult['similarity'],
                        'reason' => $backResult['reason']
                    ];
                }
                
                // 裏面もNG
                return [
                    'success' => false,
                    'processed_side' => 'both',
                    'reason' => 'both_sides_failed',
                    'front_result' => $frontResult ?? null,
                    'back_result' => $backResult
                ];
            }
            
            // 表面のみでNG
            return [
                'success' => false,
                'processed_side' => 'front_only',
                'reason' => 'front_failed_no_back',
                'front_result' => $frontResult ?? null
            ];
            
        } catch (Exception $e) {
            Log::error('SHJ-1 OCR処理エラー', [
                'user_seq' => $user->user_seq,
                'error' => $e->getMessage()
            ]);
            
            return [
                'success' => false,
                'reason' => 'system_error',
                'error' => $e->getMessage()
            ];
        }
    }

    /**
     * 照片文件路径构建（Laravel Storage方式）
     */
    private function buildPhotoPath(string $filename): string
    {
        // Storage::disk('public')用，需要 "photo/filename" 格式
        return 'photo/' . $filename;
    }

    /**
     * 画像OCR処理（SHJ-1文書仕様に従った実装）
     */
    private function processImageForOcr(User $user, string $filename, string $side): array
    {
        // Google Vision APIでOCR実行
        $ocrText = $this->googleVisionService->extractTextFromImage($filename);
        
        // 完全なOCR認識結果をログ出力
        Log::info('SHJ-1 完全OCR認識結果', [
            'user_seq' => $user->user_seq,
            'filename' => $filename,
            'side' => $side,
            'ocr_text_length' => strlen($ocrText),
            'ocr_full_text' => base64_encode($ocrText)
        ]);
        
        if (empty($ocrText)) {
            Log::error('SHJ-1 OCR結果が空', ['user_seq' => $user->user_seq, 'filename' => $filename]);
            return [
                'success' => false,
                'reason' => 'ocr_empty',
                'extracted_name' => '',
                'extracted_address' => '',
                'ocr_value' => ''
            ];
        }
        
        // SHJ-1文書通り：OCR文本からの氏名・住所抽出
        $extractResult = $this->extractNameAndAddressFromOcr($ocrText);
        
        if (!$extractResult['success']) {
            Log::error('SHJ-1 OCR抽出失敗', [
                'user_seq' => $user->user_seq,
                'filename' => $filename,
                'reason' => $extractResult['reason']
            ]);
            return $extractResult;
        }
        
        Log::info('SHJ-1 OCR抽出成功', [
            'user_seq' => $user->user_seq,
            'filename' => $filename,
            'side' => $side,
            'extracted_name' => $extractResult['extracted_name'],
            'extracted_address' => $extractResult['extracted_address'],
            'ocr_value' => $extractResult['ocr_value']
        ]);
        
        // SHJ-1文書通り：[OCR値]と[利用者住所]の照合
        $matchResult = $this->performOcrMatching($user, $extractResult['ocr_value']);
        
        return array_merge($extractResult, $matchResult);
    }
    
    /**
     * SHJ-1文書通り：OCRテキストから氏名と住所を抽出
     * 
     * 文書仕様：
     * 1. "氏名"があるかチェック
     * 2. "氏名"の次から年号和暦（昭和|平成）まで → [氏名]
     * 3. "住所"があるかチェック  
     * 4. "住所"の次から和暦（昭和|平成|令和）または"交付"まで → [住所]
     * 5. [OCR値] = [氏名] + [住所]
     */
    private function extractNameAndAddressFromOcr(string $ocrText): array
    {
        // ノイズ除去：改行とスペースを統一
        $cleanedOcr = preg_replace('/\s+/', '', $ocrText);
        
        // 1. "氏名"キーワードの存在チェック
        if (strpos($cleanedOcr, '氏名') === false) {
            return [
                'success' => false,
                'reason' => 'name_keyword_not_found',
                'extracted_name' => '',
                'extracted_address' => '',
                'ocr_value' => ''
            ];
        }
        
        // 2. 氏名抽出：「氏名」の次から「住所」まで
        $namePattern = '/氏名([^住]*?)住所/u';
        if (!preg_match($namePattern, $cleanedOcr, $nameMatches)) {
            return [
                'success' => false,
                'reason' => 'name_extraction_failed',
                'extracted_name' => '',
                'extracted_address' => '',
                'ocr_value' => ''
            ];
        }
        
        $extractedName = $this->removeSymbols($nameMatches[1]);
        
        // 3. "住所"キーワードの存在チェック
        if (strpos($cleanedOcr, '住所') === false) {
            return [
                'success' => false,
                'reason' => 'address_keyword_not_found',
                'extracted_name' => $extractedName,
                'extracted_address' => '',
                'ocr_value' => ''
            ];
        }
        
        // 4. 住所抽出：「住所」の次から改行文字または「交付」まで
        $addressPattern = '/住所([^\r\n|]*?)(?:\r|\n|\|交付|交付)/u';
        if (!preg_match($addressPattern, $cleanedOcr, $addressMatches)) {
            return [
                'success' => false,
                'reason' => 'address_extraction_failed',
                'extracted_name' => $extractedName,
                'extracted_address' => '',
                'ocr_value' => ''
            ];
        }
        
        $extractedAddress = $this->removeSymbols($addressMatches[1]);
        
        // 5. SHJ-1文書通り：[OCR値] = [氏名] + [住所]
        $ocrValue = $extractedName . $extractedAddress;
        
        return [
            'success' => true,
            'extracted_name' => $extractedName,
            'extracted_address' => $extractedAddress,
            'ocr_value' => $ocrValue
        ];
    }
    
    /**
     * 記号を除去（SHJ-1文書仕様）
     */
    private function removeSymbols(string $text): string
    {
        // 基本的な記号・区切り文字を除去
        $cleaned = preg_replace('/[|\-\|\/\\\\()（）\[\]【】「」『』<>《》≪≫]/u', '', $text);
        $cleaned = preg_replace('/\s+/u', '', $cleaned); // スペース除去
        return trim($cleaned);
    }
    
    /**
     * SHJ-1文書通り：[OCR值]と[利用者住所]の照合処理
     * 
     * 文書仕様：
     * 1. [利用者居住所]と[OCR値]を照合 → 閾値以下なら次へ
     * 2. [利用者関連住所]と[OCR値]を照合 → 閾値以下ならOCR照合失敗
     */
    private function performOcrMatching(User $user, string $ocrValue): array
    {
        $threshold = config('shj1.ocr.similarity_threshold');
        
        // [利用者居住所]を構築
        $residentAddress = $user->user_regident_pre . $user->user_regident_city . $user->user_regident_add;
        
        // 1. [利用者居住所]と[OCR値]を照合
        similar_text($residentAddress, $ocrValue, $residentSimilarity);
        
        Log::info('SHJ-1 居住住所照合', [
            'user_seq' => $user->user_seq,
            'resident_address' => $residentAddress,
            'ocr_value' => $ocrValue,
            'similarity' => $residentSimilarity,
            'threshold' => $threshold
        ]);
        
        if ($residentSimilarity >= $threshold) {
            // 居住住所で照合成功
            Log::info('SHJ-1 OCR照合成功（居住住所）', [
                'user_seq' => $user->user_seq,
                'matched_address_type' => '居住住所',
                'similarity' => $residentSimilarity
            ]);
            
            return [
                'success' => true,
                'matched_address_type' => '居住住所',
                'matched_address' => $residentAddress,
                'similarity' => $residentSimilarity,
                'reason' => 'resident_address_matched'
            ];
        }
        
        // 2. [利用者関連住所]と[OCR値]を照合
        $relatedAddress = $user->user_relate_pre . $user->user_relate_city . $user->user_relate_add;
        
        if (!empty(trim($relatedAddress))) {
            similar_text($relatedAddress, $ocrValue, $relatedSimilarity);
            
            Log::info('SHJ-1 関連住所照合', [
                'user_seq' => $user->user_seq,
                'related_address' => $relatedAddress,
                'ocr_value' => $ocrValue,
                'similarity' => $relatedSimilarity,
                'threshold' => $threshold
            ]);
            
            if ($relatedSimilarity >= $threshold) {
                // 関連住所で照合成功
                Log::info('SHJ-1 OCR照合成功（関連住所）', [
                    'user_seq' => $user->user_seq,
                    'matched_address_type' => '関連住所',
                    'similarity' => $relatedSimilarity
                ]);
                
                return [
                    'success' => true,
                    'matched_address_type' => '関連住所',
                    'matched_address' => $relatedAddress,
                    'similarity' => $relatedSimilarity,
                    'reason' => 'related_address_matched'
                ];
            }
        }
        
        // 両方とも閾值以下：OCR照合失敗
        Log::info('SHJ-1 OCR照合失敗', [
            'user_seq' => $user->user_seq,
            'resident_similarity' => $residentSimilarity,
            'related_similarity' => $relatedAddress ? ($relatedSimilarity ?? 0) : 'N/A',
            'threshold' => $threshold
        ]);
        
        return [
            'success' => false,
            'matched_address_type' => null,
            'matched_address' => null,
            'similarity' => max($residentSimilarity, $relatedSimilarity ?? 0),
            'reason' => 'threshold_not_met'
        ];
    }

    /**
     * 距離チェック処理（設計書通りpark.distance_between_two_pointsを使用）
     */
    private function performDistanceCheck(User $user, Park $park): array
    {
        try {
            // ユーザー住所を構築
            $userAddress = $user->user_regident_pre . $user->user_regident_city . $user->user_regident_add;
            
            // 駐輪場住所
            $parkAddress = $park->park_adrs;
            
            Log::info('SHJ-1 距離計算開始', [
                'user_seq' => $user->user_seq,
                'park_id' => $park->park_id,
                'user_address' => $userAddress,
                'park_address' => $parkAddress
            ]);
            
            // Google Maps APIで距離計算
            $distanceResult = $this->googleMapsService->calculateDistance($userAddress, $parkAddress);
            $distanceMeters = $distanceResult['distance_meters'];
            
            // 駐輪場の二点間距離制限を取得（設計書の要求）
            $limitMeters = $park->distance_between_two_points ?? config('shj1.distance.default_limit_meters');
            
            $withinLimit = $distanceMeters <= $limitMeters;
            $distanceDetail = $this->googleMapsService->generateDistanceDetailString(
                $park->park_id, 
                $distanceMeters, 
                'google_maps'
            );
            
            Log::info('SHJ-1 距離計算完了', [
                'user_seq' => $user->user_seq,
                'park_id' => $park->park_id,
                'calculated_distance_meters' => $distanceMeters,
                'distance_text' => $distanceResult['distance_text'] ?? null,
                'limit_meters' => $limitMeters,
                'within_limit' => $withinLimit,
                'distance_detail' => $distanceDetail
            ]);
            
            return [
                'within_limit' => $withinLimit,
                'distance_meters' => $distanceMeters,
                'limit_meters' => $limitMeters,
                'distance_detail' => $distanceDetail,
                'user_address' => $userAddress,
                'park_address' => $parkAddress
            ];
            
        } catch (Exception $e) {
            Log::error('Distance check error', [
                'user_id' => $user->user_seq,
                'park_id' => $park->park_id,
                'error' => $e->getMessage()
            ]);
            
            // API失敗時は距離NGとして処理（設計書の要求）
            return [
                'within_limit' => false,
                'distance_meters' => 999999,
                'limit_meters' => $park->distance_between_two_points ?? config('shj1.distance.default_limit_meters'),
                'error' => $e->getMessage(),
                'distance_detail' => $park->park_id . "/" . $park->park_id . "/API Error: " . $e->getMessage()
            ];
        }
    }

    /**
     * 成功ケースの処理
     */
    private function processSuccessCase(User $user, Park $park, array $ocrResult, array $distanceResult): array
    {
        // ユーザー情報更新
        $autoOkValue = config('shj1.identity_check_status.auto_ok');
        
        Log::info('SHJ-1 成功ケース: ユーザー情報更新開始', [
            'user_seq' => $user->user_seq,
            'before_user_idcard_chk_flag' => $user->user_idcard_chk_flag,
            'will_set_to' => $autoOkValue
        ]);
        
        $updateResult = $user->update([
            'user_idcard_chk_flag' => $autoOkValue,
            'user_chk_day' => now(),
            'user_chk_opeid' => 'SHJ-1'
        ]);
        
        Log::info('SHJ-1 成功ケース: ユーザー情報更新完了', [
            'user_seq' => $user->user_seq,
            'update_result' => $updateResult,
            'after_user_idcard_chk_flag' => $user->fresh()->user_idcard_chk_flag
        ]);

        // 成功メール送信
        $this->sendSuccessEmail($user, $park);

        return [
            'system_success' => true,
            'identity_result' => 'OK',
            'message' => '本人確認自動処理が成功しました',
            'similarity_rate' => $ocrResult['similarity'] ?? 0,
            'details' => [
                'user_id' => $user->user_seq,
                'park_id' => $park->park_id,
                'ocr_result' => $ocrResult['reason'],
                'distance_check' => 'within_limit'
            ],
            'stats' => ['processed_count' => 1, 'success_count' => 1]
        ];
    }

    /**
     * 失敗ケースの処理
     */
    private function processFailureCase(User $user, Park $park, array $ocrResult, array $distanceResult): array
    {
        // ユーザー情報更新（設計書通り、NG時もuser_chk_dayを更新）
        $autoNgValue = config('shj1.identity_check_status.auto_ng');
        
        Log::info('SHJ-1 失敗ケース: ユーザー情報更新開始', [
            'user_seq' => $user->user_seq,
            'before_user_idcard_chk_flag' => $user->user_idcard_chk_flag,
            'will_set_to' => $autoNgValue,
            'ocr_result' => $ocrResult['reason'] ?? 'unknown',
            'distance_within_limit' => $distanceResult['within_limit'] ?? 'unknown'
        ]);
        
        $updateResult = $user->update([
            'user_idcard_chk_flag' => $autoNgValue,
            'user_chk_day' => now(),
            'user_chk_opeid' => 'SHJ-1'
        ]);
        
        Log::info('SHJ-1 失敗ケース: ユーザー情報更新完了', [
            'user_seq' => $user->user_seq,
            'update_result' => $updateResult,
            'after_user_idcard_chk_flag' => $user->fresh()->user_idcard_chk_flag
        ]);

        // オペレータキュー作成
        $this->createOperatorQueue($user, $park, $distanceResult);

        // 800M違反フラグ更新（距離NGの場合）
        if (!$distanceResult['within_limit']) {
            $this->update800mFlag($user, $park);
        }

        // 失敗メール送信
        $this->sendFailureEmail($user, $park);

        return [
            'system_success' => true,
            'identity_result' => 'NG',
            'message' => '本人確認自動処理NGのため手動処理キューを作成しました',
            'similarity_rate' => $ocrResult['similarity'] ?? 0,
            'details' => [
                'user_id' => $user->user_seq,
                'park_id' => $park->park_id,
                'ocr_result' => $ocrResult['reason'],
                'distance_check' => $distanceResult['within_limit'] ? 'within_limit' : 'over_limit'
            ],
            'stats' => ['processed_count' => 1, 'ng_count' => 1]
        ];
    }

    /**
     * オペレータキュー作成
     */
    private function createOperatorQueue(User $user, ?Park $park, array $distanceResult, bool $isNonTarget = false): void
    {
        $queueType = $user->user_school ? 
            config('shj1.operator_queue.queue_types.student') : 
            config('shj1.operator_queue.queue_types.general');
            
        $comment = config('shj1.operator_queue.default_comment');
        if (isset($distanceResult['distance_detail'])) {
            $comment .= " / " . $distanceResult['distance_detail'];
        }
        if ($isNonTarget) {
            $comment .= " / 対象外ユーザー";
        }

        OperatorQue::create([
            'que_class' => $queueType,
            'user_id' => $user->user_seq,
            'park_id' => $park ? $park->park_id : null,
            'que_status' => config('shj1.operator_queue.queue_status.created'),
            'que_status_comment' => $comment,
            'operator_id' => config('shj1.operator_queue.batch_operator_id'),
            'created_at' => now(),
            'updated_at' => now()
        ]);
    }

    /**
     * 800M違反フラグ更新（設計書通り本人確認オペレータIDも更新）
     */
    private function update800mFlag(User $user, Park $park): void
    {
        RegularContract::where('user_id', $user->user_seq)
            ->where('park_id', $park->park_id)
            ->update([
                '800m_flag' => config('shj1.contract_800m.violation_flag'),
                'updated_at' => now()
            ]);
    }

    /**
     * 成功メール送信
     */
    private function sendSuccessEmail(User $user, Park $park): void
    {
        try {
            // SHJ-7 メール送信処理
            $mailResult = $this->mailSendService->executeMailSend(
                $user->user_primemail,
                $user->user_submail,
                config('shj1.mail.program_id_success')
            );

            // SHJ-7仕様準拠: result === 0 が正常、それ以外は異常
            if (($mailResult['result'] ?? 1) === 0) {
                Log::info('SHJ-1 成功メール送信成功', [
                    'user_id' => $user->user_seq,
                    'email' => $user->user_primemail
                ]);
            } else {
                // 仕様準拠: error_info をログに記録
                Log::error('SHJ-1 成功メール送信失敗', [
                    'user_id' => $user->user_seq,
                    'email' => $user->user_primemail,
                    'error_info' => $mailResult['error_info'] ?? 'メール送信失敗'
                ]);
            }
        } catch (Exception $e) {
            Log::error('SHJ-1 成功メール送信例外エラー', [
                'user_id' => $user->user_seq,
                'error' => $e->getMessage()
            ]);
        }
    }

    /**
     * 失敗メール送信
     */
    private function sendFailureEmail(User $user, Park $park): void
    {
        try {
            // SHJ-7 メール送信処理
            $mailResult = $this->mailSendService->executeMailSend(
                $user->user_primemail,
                $user->user_submail,
                config('shj1.mail.program_id_failure')
            );

            // SHJ-7仕様準拠: result === 0 が正常、それ以外は異常
            if (($mailResult['result'] ?? 1) === 0) {
                Log::info('SHJ-1 失敗メール送信成功', [
                    'user_id' => $user->user_seq,
                    'email' => $user->user_primemail
                ]);
            } else {
                // 仕様準拠: error_info をログに記録
                Log::error('SHJ-1 失敗メール送信失敗', [
                    'user_id' => $user->user_seq,
                    'email' => $user->user_primemail,
                    'error_info' => $mailResult['error_info'] ?? 'メール送信失敗'
                ]);
            }
        } catch (Exception $e) {
            Log::error('SHJ-1 失敗メール送信例外エラー', [
                'user_id' => $user->user_seq,
                'error' => $e->getMessage()
            ]);
        }
    }
    
    /**
     * 分析用テキスト正規化
     */
    private function normalizeForAnalysis(string $text): string
    {
        // 空白・改行除去
        $text = preg_replace('/\s+/', '', $text);
        
        // 全角→半角統一
        $text = mb_convert_kana($text, 'rnask', 'UTF-8');
        
        // 住所同義語統一
        $text = str_replace(['東京市', '東京府'], '東京都', $text);
        $text = str_replace(['の'], '', $text);
        
        return $text;
    }
    
    /**
     * 文字レベル一致分析
     */
    private function analyzeCharacterMatches(string $expected, string $ocrText): array
    {
        $expectedChars = mb_str_split($expected, 1, 'UTF-8');
        $normalizedOcr = $this->normalizeForAnalysis($ocrText);

        $analysis = [
            'total_chars' => count($expectedChars),
            'matched_chars' => 0,
            'missing_chars' => [],
            'match_rate' => 0
        ];

        foreach ($expectedChars as $char) {
            if (mb_strpos($normalizedOcr, $char, 0, 'UTF-8') !== false) {
                $analysis['matched_chars']++;
            } else {
                $analysis['missing_chars'][] = $char;
            }
        }

        $analysis['match_rate'] = round(($analysis['matched_chars'] / $analysis['total_chars']) * 100, 2);

        return $analysis;
    }

    /**
     * バッチ処理ログ作成
     *
     * SHJ-8サービスを呼び出してbat_job_logテーブルに登録
     *
     * @param int $userId 利用者連番
     * @param string|null $userName 利用者名
     * @param float|null $similarityRate 類似度
     * @param array $result 処理結果
     * @return void
     */
    private function createBatchLog(int $userId, ?string $userName, ?float $similarityRate, array $result): void
    {
        try {
            $device = Device::orderBy('device_id')->first();
            $deviceId = $device ? $device->device_id : 1;
            $today = now()->format('Y/m/d');

            // ステータス判定
            $status = $result['system_success'] ? 'success' : 'error';

            // ステータスコメント生成: {user_id}/{user_name}/{similarity_rate}%
            $displayUserId = $userId;
            $displayUserName = $userName ?? '不明';
            $displaySimilarity = $similarityRate !== null ? number_format($similarityRate, 1) : '0.0';

            $statusComment = "{$displayUserId}/{$displayUserName}/{$displaySimilarity}%";

            // SHJ-8サービス呼び出し
            $this->shjEightService->execute(
                $deviceId,
                'SHJ-1',
                'SHJ-1本人確認自動処理',
                $status,
                $statusComment,
                $today,
                $today
            );

            Log::info('SHJ-1 バッチ処理ログ作成完了', [
                'user_id' => $userId,
                'status' => $status,
                'status_comment' => $statusComment
            ]);

        } catch (Exception $e) {
            Log::error('SHJ-1 バッチ処理ログ作成エラー', [
                'error' => $e->getMessage(),
                'user_id' => $userId
            ]);
        }
    }

}