<?php

namespace App\Services;

use App\Models\Park;
use App\Models\User;
use App\Models\RegularContract;
use App\Models\OperatorQue;
use App\Models\Device;
use App\Services\ShjMailSendService;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;

/**
 * SHJ-3 定期更新リマインダー処理サービス
 * 
 * 駐輪場の定期契約更新対象者に対するリマインダー処理を実行するビジネスロジック
 * バッチ処理「SHJ-3定期更新リマインダー」の核となる処理を担当
 */
class ShjThreeService
{
    /**
     * Park モデル
     *
     * @var Park
     */
    protected $parkModel;

    /**
     * User モデル
     *
     * @var User
     */
    protected $userModel;

    /**
     * RegularContract モデル
     *
     * @var RegularContract
     */
    protected $contractModel;

    /**
     * OperatorQue モデル
     *
     * @var OperatorQue
     */
    protected $operatorQueModel;

    /**
     * ShjMailSendService
     *
     * @var ShjMailSendService
     */
    protected $mailSendService;

    /**
     * ShjEightService
     *
     * @var ShjEightService
     */
    protected $shjEightService;

    /**
     * コンストラクタ
     *
     * @param Park $parkModel
     * @param User $userModel
     * @param RegularContract $contractModel
     * @param OperatorQue $operatorQueModel
     * @param ShjMailSendService $mailSendService
     * @param ShjEightService $shjEightService
     */
    public function __construct(
        Park $parkModel,
        User $userModel,
        RegularContract $contractModel,
        OperatorQue $operatorQueModel,
        ShjMailSendService $mailSendService,
        ShjEightService $shjEightService
    ) {
        $this->parkModel = $parkModel;
        $this->userModel = $userModel;
        $this->contractModel = $contractModel;
        $this->operatorQueModel = $operatorQueModel;
        $this->mailSendService = $mailSendService;
        $this->shjEightService = $shjEightService;
    }

    /**
     * SHJ-3 定期更新リマインダー処理メイン実行
     * 
     * 処理フロー（仕様書準拠）:
     * 【処理0】駐輪場マスタの情報を取得する
     * 【判断0】当該駐輪場実行タイミングチェック
     * 【処理2】定期更新対象者を取得する
     * 【判断2】利用者有無をチェック
     * 【処理3】対象者向けにメール送信、またはオペレーターキュー追加処理
     * 【処理4】バッチ処理ログを作成する（各駐輪場ごとに実行）
     *
     * @return array 処理結果
     */
    public function executeReminderProcess(): array
    {
        $overallProcessedParksCount = 0;
        $overallTotalTargetUsers = 0;
        $overallMailSuccessCount = 0;
        $overallMailErrorCount = 0;
        $overallQueueSuccessCount = 0;
        $overallQueueErrorCount = 0;
        
        try {
            Log::info('SHJ-3 定期更新リマインダー処理開始');

            // 【処理0】駐輪場マスタの情報を取得する
            $parkList = $this->getParkMasterInfo();

            if (empty($parkList)) {
                $message = '対象の駐輪場マスタが見つかりません';
                Log::warning($message);

                // 駐輪場が見つからない場合でも実行ログを記録
                $this->createOverallBatchLog(0, 0, 0, 0, 0, 0);

                return [
                    'success' => false,
                    'message' => $message,
                    'processed_parks_count' => 0,
                    'total_target_users' => 0,
                    'mail_success_count' => 0,
                    'mail_error_count' => 0,
                    'operator_queue_count' => 0
                ];
            }

            // 取得レコード数分【判断0】を繰り返す
            foreach ($parkList as $park) {
                // 各駐輪場ごとの内部変数（仕様書：場景A）
                $mailSuccessCount = 0;
                $mailErrorCount = 0;
                $queueSuccessCount = 0;
                $queueErrorCount = 0;
                $batchComment = '';
                
                Log::info('駐輪場処理開始', [
                    'park_id' => $park->park_id,
                    'park_name' => $park->park_name
                ]);

                // 【判断0】当該駐輪場実行タイミングチェック
                $timingCheckResult = $this->checkExecutionTiming($park);
                
                if (!$timingCheckResult['should_execute']) {
                    Log::info('実行タイミング対象外', [
                        'park_id' => $park->park_id,
                        'reason' => $timingCheckResult['reason']
                    ]);
                    // 次の駐輪場マスタへ
                    continue;
                }

                $overallProcessedParksCount++;

                // 【処理2】定期更新対象者を取得する
                $targetUsers = $this->getRegularUpdateTargetUsers(
                    $park,
                    $timingCheckResult['update_pattern']
                );

                // 【判断2】利用者有無をチェック
                if (empty($targetUsers)) {
                    // 仕様書：利用者なしの結果を設定する
                    $batchComment = "定期更新リマインダー：今月の定期更新対象者は無しです / {$park->park_name}";
                    
                    Log::info('利用者なし', [
                        'park_id' => $park->park_id,
                        'batch_comment' => $batchComment
                    ]);
                    
                    // 【処理4】バッチ処理ログを作成する
                    $this->createShjBatchLog(
                        $park,
                        $batchComment,
                        $mailSuccessCount,
                        $mailErrorCount,
                        $queueSuccessCount,
                        $queueErrorCount
                    );
                    
                    // 次の駐輪場マスタへ
                    continue;
                }

                $overallTotalTargetUsers += count($targetUsers);

                // 【処理3】処理2の対象レコード数分繰り返す
                foreach ($targetUsers as $targetUser) {
                    $processResult = $this->processTargetUser($targetUser, $park);
                    
                    if ($processResult['type'] === 'mail_success') {
                        $mailSuccessCount++;
                    } elseif ($processResult['type'] === 'mail_error') {
                        $mailErrorCount++;
                        // バッチコメントに異常情報を追加
                        if (!empty($processResult['error_info'])) {
                            $batchComment .= ($batchComment ? ' / ' : '') . $processResult['error_info'];
                        }
                    } elseif ($processResult['type'] === 'queue_success') {
                        $queueSuccessCount++;
                    } elseif ($processResult['type'] === 'queue_error') {
                        $queueErrorCount++;
                    }
                }

                // 全体集計用に加算
                $overallMailSuccessCount += $mailSuccessCount;
                $overallMailErrorCount += $mailErrorCount;
                $overallQueueSuccessCount += $queueSuccessCount;
                $overallQueueErrorCount += $queueErrorCount;

                Log::info('駐輪場処理完了', [
                    'park_id' => $park->park_id,
                    'target_users_count' => count($targetUsers),
                    'mail_success' => $mailSuccessCount,
                    'mail_error' => $mailErrorCount,
                    'queue_success' => $queueSuccessCount,
                    'queue_error' => $queueErrorCount
                ]);

                // 【処理4】バッチ処理ログを作成する（各駐輪場ごと）
                $this->createShjBatchLog(
                    $park,
                    $batchComment,
                    $mailSuccessCount,
                    $mailErrorCount,
                    $queueSuccessCount,
                    $queueErrorCount
                );
            }

            Log::info('SHJ-3 定期更新リマインダー処理完了', [
                'processed_parks_count' => $overallProcessedParksCount,
                'total_target_users' => $overallTotalTargetUsers,
                'mail_success_count' => $overallMailSuccessCount,
                'mail_error_count' => $overallMailErrorCount,
                'queue_success_count' => $overallQueueSuccessCount,
                'queue_error_count' => $overallQueueErrorCount
            ]);

            // 駐輪場が0件でも全体の実行ログを記録する
            $this->createOverallBatchLog(
                $overallProcessedParksCount,
                $overallTotalTargetUsers,
                $overallMailSuccessCount,
                $overallMailErrorCount,
                $overallQueueSuccessCount,
                $overallQueueErrorCount
            );

            return [
                'success' => true,
                'message' => 'SHJ-3 定期更新リマインダー処理が正常に完了しました',
                'processed_parks_count' => $overallProcessedParksCount,
                'total_target_users' => $overallTotalTargetUsers,
                'mail_success_count' => $overallMailSuccessCount,
                'mail_error_count' => $overallMailErrorCount,
                'operator_queue_count' => $overallQueueSuccessCount + $overallQueueErrorCount
            ];

        } catch (\Exception $e) {
            $errorMessage = 'SHJ-3 定期更新リマインダー処理でエラーが発生: ' . $e->getMessage();

            Log::error('SHJ-3 定期更新リマインダー処理エラー', [
                'exception' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);

            // エラー時も実行ログを記録
            $this->createOverallBatchLog(
                $overallProcessedParksCount,
                $overallTotalTargetUsers,
                $overallMailSuccessCount,
                $overallMailErrorCount,
                $overallQueueSuccessCount,
                $overallQueueErrorCount
            );

            return [
                'success' => false,
                'message' => $errorMessage,
                'details' => $e->getMessage(),
                'processed_parks_count' => $overallProcessedParksCount,
                'total_target_users' => $overallTotalTargetUsers,
                'mail_success_count' => $overallMailSuccessCount,
                'mail_error_count' => $overallMailErrorCount,
                'operator_queue_count' => $overallQueueSuccessCount + $overallQueueErrorCount
            ];
        }
    }

    /**
     * 【処理0】駐輪場マスタの情報を取得する
     * 
     * 仕様書に基づくSQLクエリ:
     * SELECT 駐輪場ID, 駐輪場名, 更新期間開始日, 更新期間開始時, 
     *        更新期間終了日, 更新期間終了時, リマインダー種別, リマインダー時間
     * FROM 駐輪場マスタ
     * WHERE 閉設フラグ = 0
     * ORDER BY 駐輪場ふりがな asc
     *
     * @return array 駐輪場マスタ情報
     */
    private function getParkMasterInfo(): array
    {
        try {
            $parkInfo = DB::table('park')
                ->select([
                    'park_id',                              // 駐輪場ID
                    'park_name',                            // 駐輪場名
                    'update_grace_period_start_date',       // 更新期間開始日（例："20"）
                    'update_grace_period_start_time',       // 更新期間開始時（例："09:00"）
                    'update_grace_period_end_date',         // 更新期間終了日（例："6"）
                    'update_grace_period_end_time',         // 更新期間終了時（例："23:59"）
                    // 注意：reminder_type と reminder_time フィールドはまだ存在しない
                    // 暫定的に0（毎日）と開始時刻をデフォルト値として使用
                ])
                ->where('park_close_flag', 0)               // 閉設フラグ = 0
                ->orderBy('park_ruby', 'asc')               // 駐輪場ふりがな 昇順
                ->get()
                ->toArray();

            // reminder_type と reminder_time をデフォルト値で補完
            foreach ($parkInfo as $park) {
                $park->reminder_type = 0;  // 0=毎日
                $park->reminder_time = $park->update_grace_period_start_time;  // 開始時刻を使用
            }

            Log::info('駐輪場マスタ情報取得完了', [
                'park_count' => count($parkInfo)
            ]);

            return $parkInfo;

        } catch (\Exception $e) {
            Log::error('駐輪場マスタ情報取得エラー', [
                'error' => $e->getMessage()
            ]);

            throw $e;
        }
    }

    /**
     * 【判断0】当該駐輪場実行タイミングチェック
     * 
     * 仕様書に基づく実行タイミング判定:
     * 1. リマインダー時間 = 現在の時間 のチェック
     * 2. 内部変数.更新パターン の設定（A or B）
     * 3. 内部変数.更新期間開始日からの経過日数 の算出
     * 4. 内部変数.実行フラグ の判定
     *
     * @param object $park 駐輪場情報
     * @return array 実行タイミング判定結果
     */
    private function checkExecutionTiming($park): array
    {
        try {
            $now = Carbon::now();
            $currentTime = $now->format('H:i');
            $todayDay = (int)$now->format('d');  // 本日の日（1-31）
            
            Log::info('実行タイミングチェック開始', [
                'park_id' => $park->park_id,
                'current_time' => $currentTime,
                'today_day' => $todayDay,
                'reminder_time' => $park->reminder_time,
                'reminder_type' => $park->reminder_type
            ]);

            // 仕様書：駐輪場マスタ.リマインダー時間 = [現在の時間] の場合
            if ($park->reminder_time !== $currentTime) {
                return [
                    'should_execute' => false,
                    'reason' => "リマインダー時間不一致（設定:{$park->reminder_time} vs 現在:{$currentTime}）"
                ];
            }

            // 内部変数.更新パターン を設定
            // DBから返る値は文字列なので、型変換して使用
            $startDay = (int)$park->update_grace_period_start_date;  // 例："20" → 20
            $endDay = (int)$park->update_grace_period_end_date;      // 例："6" → 6
            
            $updatePattern = '';
            if ($startDay <= $endDay) {
                // パターンA: 月を跨らない場合
                $updatePattern = 'A';
            } else {
                // パターンB: 月を跨る場合
                $updatePattern = 'B';
            }

            // 内部変数.更新期間開始日からの経過日数 を設定
            $elapsedDays = 99;  // デフォルト: 対象外
            
            if ($updatePattern === 'A') {
                // パターンA の場合
                if ($endDay < $todayDay) {
                    // 駐輪場マスタ.更新期間終了日 > [本日の日付]の日 の場合
                    $elapsedDays = 99;  // 対象外
                } elseif ($startDay <= $todayDay) {
                    // 駐輪場マスタ.更新期間開始日 <= [本日の日付]の日 の場合
                    $elapsedDays = $todayDay - $startDay;
                } else {
                    // その他の場合
                    $elapsedDays = 99;  // 対象外
                }
            } else {
                // パターンB の場合
                if ($startDay <= $todayDay) {
                    // 駐輪場マスタ.更新期間開始日 <= [本日の日付]の日 の場合
                    $elapsedDays = $todayDay - $startDay;
                } elseif ($endDay >= $todayDay) {
                    // 駐輪場マスタ.更新期間終了日 >= [本日の日付]の日 の場合
                    // 仕様書の計算式: ([先月の月末日]の日 − 駐輪場マスタ.更新期間開始日) ＋ [本日の日付]の日
                    $lastMonthEnd = $now->copy()->subMonth()->endOfMonth()->day;
                    $elapsedDays = ($lastMonthEnd - $startDay) + $todayDay;
                } else {
                    // その他の場合
                    $elapsedDays = 99;  // 対象外
                }
            }

            Log::info('経過日数算出完了', [
                'park_id' => $park->park_id,
                'update_pattern' => $updatePattern,
                'start_day' => $startDay,
                'end_day' => $endDay,
                'today_day' => $todayDay,
                'elapsed_days' => $elapsedDays
            ]);

            // 内部変数.実行フラグ を設定
            $executionFlag = 0;
            
            if ($elapsedDays !== 99) {
                // DBから返る値は文字列なので、型変換して比較
                $reminderType = (int)($park->reminder_type ?? 0);
                
                if ($reminderType === 0) {
                    // 仕様書：毎日
                    $executionFlag = 1;
                } elseif ($reminderType === 1) {
                    // 仕様書：１日おき（経過日数が偶数の場合）
                    $executionFlag = ($elapsedDays % 2 === 0) ? 1 : 0;
                } elseif ($reminderType === 2) {
                    // 仕様書：２日おき（経過日数を3で割った余りが0の場合）
                    $executionFlag = ($elapsedDays % 3 === 0) ? 1 : 0;
                } else {
                    // あり得ない
                    $executionFlag = 0;
                }
            }

            $shouldExecute = ($executionFlag === 1);

            Log::info('実行タイミングチェック完了', [
                'park_id' => $park->park_id,
                'update_pattern' => $updatePattern,
                'elapsed_days' => $elapsedDays,
                'reminder_type' => $park->reminder_type,
                'execution_flag' => $executionFlag,
                'should_execute' => $shouldExecute
            ]);

            return [
                'should_execute' => $shouldExecute,
                'reason' => $shouldExecute ? '実行対象' : "実行フラグ=0（経過日数:{$elapsedDays}, リマインダー種別:{$park->reminder_type}）",
                'update_pattern' => $updatePattern,
                'elapsed_days' => $elapsedDays,
                'execution_flag' => $executionFlag
            ];

        } catch (\Exception $e) {
            Log::error('実行タイミングチェックエラー', [
                'park_id' => $park->park_id ?? 'unknown',
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);

            throw $e;
        }
    }


    /**
     * 【処理2】定期更新対象者を取得する
     * 
     * 仕様書に基づくSQLクエリ:
     * 定期契約マスタ T1 と 利用者マスタ T2 を結合して更新対象者の情報を取得する
     * 
     * WHERE条件:
     * - T1.駐輪場ID = 処理0.駐輪場ID
     * - T1.更新可能日 <= 本日の日付
     * - T1.解約フラグ = 0
     * - T2.退会フラグ = 0
     * - T1.授受フラグ = 1
     * - T1.更新済フラグ is null
     * - 更新パターンによる有効期間Eの判定
     *
     * @param object $park 駐輪場情報
     * @param string $updatePattern 更新パターン（"A" or "B"）
     * @return array 定期更新対象者情報
     */
    private function getRegularUpdateTargetUsers($park, string $updatePattern): array
    {
        try {
            $now = Carbon::now();
            $currentDate = $now->format('Y-m-d');
            $todayDay = (int)$now->format('d');  // 本日の日（1-31）
            // DBから返る値は文字列なので、型変換して使用
            $startDay = (int)$park->update_grace_period_start_date;  // 例："20" → 20
            $endDay = (int)$park->update_grace_period_end_date;      // 例："6" → 6
            
            // 有効期間E（契約終了日）の判定
            $thisMonthEnd = $now->copy()->endOfMonth()->format('Y-m-d');
            $lastMonthEnd = $now->copy()->subMonth()->endOfMonth()->format('Y-m-d');
            
            $query = DB::table('regular_contract as T1')
                ->select([
                    'T1.contract_id as 定期契約ID',
                    'T1.park_id as 駐輪場ID',
                    'T2.user_seq as 利用者ID',              // user_seqが主キー
                    'T2.user_manual_regist_flag as 手動登録フラグ',
                    'T2.user_primemail as メールアドレス',
                    'T2.user_submail as 予備メールアドレス',
                    'T2.user_name as 氏名',
                    'T1.contract_periode as 有効期間E'
                ])
                ->join('user as T2', 'T1.user_id', '=', 'T2.user_seq')  // user_seqに結合
                ->where('T1.park_id', $park->park_id)                   // 駐輪場ID
                ->where('T1.contract_updated_at', '<=', $currentDate)   // 更新可能日
                ->where('T1.contract_cancel_flag', 0)                   // 解約フラグ = 0
                ->where('T2.user_quit_flag', 0)                         // 退会フラグ = 0
                ->where('T1.contract_flag', 1)                          // 授受フラグ = 1
                ->whereNull('T1.contract_renewal');                     // 更新済フラグ is null

            // 仕様書：更新パターンによる有効期間Eの判定
            if ($updatePattern === 'A') {
                // パターンA の場合: 有効期間E = 今月末
                $query->where('T1.contract_periode', '=', $thisMonthEnd);
            } else {
                // パターンB の場合
                if ($startDay <= $todayDay) {
                    // 処理０.更新期間開始日 <= [本日の日付]の日 の場合
                    $query->where('T1.contract_periode', '=', $thisMonthEnd);
                } elseif ($endDay >= $todayDay) {
                    // 処理０.更新期間終了日 >= [本日の日付]の日 の場合
                    $query->where('T1.contract_periode', '=', $lastMonthEnd);
                }
            }

            $targetUsers = $query->get()->toArray();

            Log::info('定期更新対象者取得完了', [
                'park_id' => $park->park_id,
                'update_pattern' => $updatePattern,
                'this_month_end' => $thisMonthEnd,
                'last_month_end' => $lastMonthEnd,
                'target_users_count' => count($targetUsers)
            ]);

            return $targetUsers;

        } catch (\Exception $e) {
            Log::error('定期更新対象者取得エラー', [
                'park_id' => $park->park_id ?? 'unknown',
                'update_pattern' => $updatePattern ?? 'unknown',
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);

            throw $e;
        }
    }

    /**
     * 【処理3】対象者の処理実行
     * 
     * 手動登録フラグによって処理を分岐:
     * - = 0 (ウェブ申込み): SHJ-7メール送信を呼び出し
     * - その他: オペレーターキュー追加処理
     *
     * @param object $targetUser 対象者情報
     * @param object $park 駐輪場情報
     * @return array 処理結果
     */
    private function processTargetUser($targetUser, $park): array
    {
        try {
            $manualRegistFlag = $targetUser->手動登録フラグ ?? 1;

            if ($manualRegistFlag == 0) {
                // 仕様書：手動登録フラグ = 0（ウェブ申込み）の場合
                // SHJ-7メール送信処理を呼び出し
                return $this->sendReminderMail($targetUser);
            } else {
                // 仕様書：手動登録フラグ <> 0 の場合
                // オペレーターキュー追加処理
                // ※文書には詳細仕様なし。他のService実装を参考に実装
                return $this->addToOperatorQueue($targetUser, $park);
            }

        } catch (\Exception $e) {
            Log::error('対象者処理エラー', [
                'user_id' => $targetUser->利用者ID ?? 'unknown',
                'contract_id' => $targetUser->定期契約ID ?? 'unknown',
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);

            return [
                'type' => 'error',
                'success' => false,
                'message' => $e->getMessage()
            ];
        }
    }

    /**
     * リマインダーメール送信処理
     * 
     * 仕様書：SHJ-7メール送信を呼び出し、使用プログラムID=200を使用
     *
     * @param object $targetUser 対象者情報
     * @return array 送信結果
     */
    private function sendReminderMail($targetUser): array
    {
        try {
            $mailAddress = $targetUser->メールアドレス ?? '';
            $backupMailAddress = $targetUser->予備メールアドレス ?? '';
            $mailTemplateId = 200; // 仕様書：使用プログラムID = 200

            Log::info('SHJ-7メール送信処理呼び出し', [
                'user_id' => $targetUser->利用者ID,
                'contract_id' => $targetUser->定期契約ID,
                'mail_address' => $mailAddress,
                'backup_mail_address' => $backupMailAddress,
                'mail_template_id' => $mailTemplateId
            ]);

            // 仕様書：共通処理「SHJ-7 メール送信」を呼び出し
            $mailResult = $this->mailSendService->executeMailSend(
                $mailAddress,
                $backupMailAddress,
                $mailTemplateId
            );

            // SHJ-7仕様準拠: result === 0 が正常、それ以外は異常
            if (($mailResult['result'] ?? 1) === 0) {
                // 仕様書：処理結果 = 0（正常）の場合
                Log::info('メール送信成功', [
                    'user_id' => $targetUser->利用者ID,
                    'contract_id' => $targetUser->定期契約ID
                ]);

                return [
                    'type' => 'mail_success',
                    'success' => true,
                    'message' => 'メール送信成功',
                    'error_info' => null
                ];
            } else {
                // 仕様書：その他の場合（異常）
                // バッチコメントに「処理２.定期契約ID」+「SHJ-7 メール送信.異常情報」を設定する（後ろに足す）
                // 仕様準拠: error_info を使用
                $shjSevenErrorInfo = $mailResult['error_info'] ?? 'メール送信失敗';
                $errorInfo = "定期契約ID:{$targetUser->定期契約ID} / SHJ-7 メール送信.異常情報:{$shjSevenErrorInfo}";

                Log::warning('メール送信失敗', [
                    'user_id' => $targetUser->利用者ID,
                    'contract_id' => $targetUser->定期契約ID,
                    'error_info' => $shjSevenErrorInfo
                ]);

                return [
                    'type' => 'mail_error',
                    'success' => false,
                    'message' => 'メール送信失敗',
                    'error_info' => $errorInfo
                ];
            }

        } catch (\Exception $e) {
            Log::error('リマインダーメール送信エラー', [
                'user_id' => $targetUser->利用者ID ?? 'unknown',
                'contract_id' => $targetUser->定期契約ID ?? 'unknown',
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);

            // 仕様書準拠のエラー情報フォーマット
            $errorInfo = "定期契約ID:{$targetUser->定期契約ID} / SHJ-7 メール送信.異常情報:例外エラー - {$e->getMessage()}";

            return [
                'type' => 'mail_error',
                'success' => false,
                'message' => 'メール送信例外エラー',
                'error_info' => $errorInfo
            ];
        }
    }

    /**
     * オペレーターキュー追加処理
     * 
     * 仕様書には詳細記載なし。
     * 他のService（ShjOneService、ShjSixService）の実装を参考に実装。
     *
     * @param object $targetUser 対象者情報
     * @param object $park 駐輪場情報
     * @return array 追加結果
     */
    private function addToOperatorQueue($targetUser, $park): array
    {
        try {
            // operator_queテーブルに登録
            $operatorQue = OperatorQue::create([
                'que_class' => 5,  // 定期更新通知（OperatorQueモデルの定数参照）
                'user_id' => $targetUser->利用者ID,
                'contract_id' => $targetUser->定期契約ID,
                'park_id' => $targetUser->駐輪場ID,
                'que_comment' => sprintf(
                    '定期更新通知 / 契約ID:%s / 利用者:%s / 駐輪場:%s',
                    $targetUser->定期契約ID,
                    $targetUser->氏名 ?? '',
                    $park->park_name ?? ''
                ),
                'que_status' => 1,  // キュー発生
                'que_status_comment' => '',
                'work_instructions' => '',
                'operator_id' => 9999999,  // 仕様書準拠：固定値（他Serviceと同様）
                'created_at' => now(),
                'updated_at' => now()
            ]);

            Log::info('オペレーターキュー追加成功', [
                'que_id' => $operatorQue->que_id,
                'que_class' => 5,
                'user_id' => $targetUser->利用者ID,
                'contract_id' => $targetUser->定期契約ID,
                'park_id' => $targetUser->駐輪場ID
            ]);

            return [
                'type' => 'queue_success',
                'success' => true,
                'message' => 'オペレーターキュー追加成功',
                'que_id' => $operatorQue->que_id
            ];

        } catch (\Exception $e) {
            Log::error('オペレーターキュー追加エラー', [
                'user_id' => $targetUser->利用者ID ?? 'unknown',
                'contract_id' => $targetUser->定期契約ID ?? 'unknown',
                'park_id' => $targetUser->駐輪場ID ?? 'unknown',
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);

            return [
                'type' => 'queue_error',
                'success' => false,
                'message' => 'オペレーターキュー追加エラー: ' . $e->getMessage()
            ];
        }
    }

    /**
     * 【処理4】SHJ-8バッチ処理ログ作成
     *
     * 仕様書に基づくSHJ-8共通処理呼び出し
     * ※各駐輪場ごとに1回実行される
     *
     * @param object $park 駐輪場情報
     * @param string $batchComment バッチコメント
     * @param int $mailSuccessCount メール正常終了件数
     * @param int $mailErrorCount メール異常終了件数
     * @param int $queueSuccessCount キュー登録正常終了件数
     * @param int $queueErrorCount キュー登録異常終了件数
     * @return void
     */
    private function createShjBatchLog(
        $park,
        string $batchComment,
        int $mailSuccessCount,
        int $mailErrorCount,
        int $queueSuccessCount,
        int $queueErrorCount
    ): void {
        try {
            $device = Device::orderBy('device_id')->first();
            $deviceId = $device ? $device->device_id : 1;

            // 仕様書：ステータスコメント生成
            // 「内部変数.バッチコメント」+ "／" + 「処理１.駐輪場名」
            // + "：メール正常終了件数" + 「内部変数.メール正常終了件数」
            // + "、メール異常終了件数" + 「内部変数.メール異常終了件数」
            // + "、キュー登録正常終了件数" + 「内部変数.キュー登録正常終了件数」
            // + "、キュー登録異常終了件数" + 「内部変数.キュー登録異常終了件数」
            $statusComment = ($batchComment ? $batchComment . ' / ' : '') .
                           "{$park->park_name}：" .
                           "メール正常終了件数={$mailSuccessCount}、" .
                           "メール異常終了件数={$mailErrorCount}、" .
                           "キュー登録正常終了件数={$queueSuccessCount}、" .
                           "キュー登録異常終了件数={$queueErrorCount}";

            $today = now()->format('Y/m/d');

            Log::info('SHJ-8バッチ処理ログ作成', [
                'park_id' => $park->park_id,
                'park_name' => $park->park_name,
                'status_comment' => $statusComment
            ]);

            // SHJ-8サービスを呼び出し
            $this->shjEightService->execute(
                $deviceId,
                'SHJ-3',
                'SHJ-3定期更新リマインダー',
                'success',
                $statusComment,
                $today,
                $today
            );

        } catch (\Exception $e) {
            Log::error('SHJ-8バッチ処理ログ作成エラー', [
                'park_id' => $park->park_id ?? 'unknown',
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);

            // 仕様書：SHJ-8でエラーが発生してもメイン処理は継続
            // エラーログのみ出力
        }
    }

    /**
     * 全体の実行ログを記録する
     *
     * 駐輪場が0件の場合でも実行記録を残すための全体サマリーログ
     *
     * @param int $processedParksCount 処理した駐輪場数
     * @param int $totalTargetUsers 対象者総数
     * @param int $mailSuccessCount メール成功件数
     * @param int $mailErrorCount メール失敗件数
     * @param int $queueSuccessCount キュー成功件数
     * @param int $queueErrorCount キュー失敗件数
     * @return void
     */
    private function createOverallBatchLog(
        int $processedParksCount,
        int $totalTargetUsers,
        int $mailSuccessCount,
        int $mailErrorCount,
        int $queueSuccessCount,
        int $queueErrorCount
    ): void {
        try {
            $device = Device::orderBy('device_id')->first();
            $deviceId = $device ? $device->device_id : 1;

            // 全体サマリーのステータスコメント
            $statusComment = sprintf(
                '処理駐輪場数：%d、対象者総数：%d、メール成功：%d、メール失敗：%d、キュー追加：%d',
                $processedParksCount,
                $totalTargetUsers,
                $mailSuccessCount,
                $mailErrorCount,
                $queueSuccessCount + $queueErrorCount
            );

            $today = now()->format('Y/m/d');

            Log::info('SHJ-3 全体実行ログ作成', [
                'status_comment' => $statusComment
            ]);

            // SHJ-8サービスを呼び出し
            $this->shjEightService->execute(
                $deviceId,
                'SHJ-3',
                'SHJ-3定期更新リマインダー',
                'success',
                $statusComment,
                $today,
                $today
            );

        } catch (\Exception $e) {
            Log::error('SHJ-3 全体実行ログ作成エラー', [
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);
        }
    }
}
