<?php

namespace App\Services;

use App\Models\Park;
use App\Models\RegularContract;
use App\Models\EarningsSummary;
use App\Models\Psection;
use App\Models\Batch\BatchLog;
use App\Models\OperatorQue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;

/**
 * SHJ-9 売上集計処理サービス
 * 
 * 日次・月次・年次の売上集計処理を実行するビジネスロジック
 * バッチ処理「SHJ-9売上集計」の核となる処理を担当
 */
class ShjNineService
{
    /**
     * Park モデル
     *
     * @var Park
     */
    protected $parkModel;

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

    /**
     * EarningsSummary モデル
     *
     * @var EarningsSummary
     */
    protected $earningsSummaryModel;

    /**
     * Psection モデル
     *
     * @var Psection
     */
    protected $psectionModel;

    /**
     * BatchLog モデル
     *
     * @var BatchLog
     */
    protected $batchLogModel;

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

    /**
     * コンストラクタ
     *
     * @param Park $parkModel
     * @param RegularContract $contractModel
     * @param EarningsSummary $earningsSummaryModel
     * @param Psection $psectionModel
     * @param BatchLog $batchLogModel
     * @param OperatorQue $operatorQueModel
     */
    public function __construct(
        Park $parkModel,
        RegularContract $contractModel,
        EarningsSummary $earningsSummaryModel,
        Psection $psectionModel,
        BatchLog $batchLogModel,
        OperatorQue $operatorQueModel
    ) {
        $this->parkModel = $parkModel;
        $this->contractModel = $contractModel;
        $this->earningsSummaryModel = $earningsSummaryModel;
        $this->psectionModel = $psectionModel;
        $this->batchLogModel = $batchLogModel;
        $this->operatorQueModel = $operatorQueModel;
    }

    /**
     * SHJ-9 売上集計処理メイン実行
     * 
     * 処理フロー:
     * 【処理1】集計対象を設定する
     * 【処理2】駐輪場マスタを取得する
     * 【判断1】取得件数判定
     * 【処理3】車種区分毎に算出する
     * 【判断2】取得判定
     * 【処理4】売上集計結果を削除→登録する
     * 【処理5】オペレータキュー作成およびバッチ処理ログを作成する
     *
     * @param string $type 集計種別（daily/monthly/yearly）
     * @param string $aggregationDate 集計対象日
     * @return array 処理結果
     */
    public function executeEarningsAggregation(string $type, string $aggregationDate): array
    {
        $batchLogId = null;
        
        try {
            // 【処理1】集計対象を設定する
            $aggregationTarget = $this->setAggregationTarget($type, $aggregationDate);
            
            // バッチ処理開始ログ作成
            $batchLog = BatchLog::createBatchLog(
                'shj9',
                BatchLog::STATUS_START,
                [
                    'type' => $type,
                    'aggregation_date' => $aggregationDate,
                    'aggregation_target' => $aggregationTarget
                ],
                "SHJ-9 売上集計処理開始 ({$type})"
            );
            $batchLogId = $batchLog->id;

            Log::info('SHJ-9 売上集計処理開始', [
                'batch_log_id' => $batchLogId,
                'type' => $type,
                'aggregation_date' => $aggregationDate,
                'aggregation_target' => $aggregationTarget
            ]);

            // 【処理2】駐輪場マスタを取得する
            $parkInfo = $this->getParkInformation();

            // 【判断1】取得件数判定
            if (empty($parkInfo)) {
                $message = '売上集計（' . $this->getTypeLabel($type) . '）：駐輪場マスタが存在していません。';
                
                // バッチログ更新
                $batchLog->update([
                    'status' => BatchLog::STATUS_WARNING,
                    'end_time' => now(),
                    'message' => $message,
                    'success_count' => 1  // 処理は成功したが対象なし
                ]);
                
                // 【処理5】オペレータキュー作成
                $this->createOperatorQueue($message, $batchLogId);
                
                return [
                    'success' => true,
                    'message' => $message,
                    'processed_parks' => 0,
                    'summary_records' => 0,
                    'batch_log_id' => $batchLogId
                ];
            }

            // 【処理3】車種区分毎に算出する & 【処理4】売上集計結果を削除→登録する
            $summaryRecords = 0;
            $processedParks = 0;

            foreach ($parkInfo as $park) {
                $parkSummaryRecords = $this->processEarningsForPark($park, $aggregationTarget, $type);
                
                if ($parkSummaryRecords > 0) {
                    $processedParks++;
                    $summaryRecords += $parkSummaryRecords;
                }
            }

            // 【判断2】取得判定
            if ($summaryRecords === 0) {
                $message = '対象なしの結果を設定する（契約）';
                
                // バッチログ更新
                $batchLog->update([
                    'status' => BatchLog::STATUS_WARNING,
                    'end_time' => now(),
                    'message' => $message,
                    'success_count' => 1
                ]);
                
                // 【処理5】オペレータキュー作成
                $this->createOperatorQueue($message, $batchLogId);
                
                return [
                    'success' => true,
                    'message' => $message,
                    'processed_parks' => $processedParks,
                    'summary_records' => 0,
                    'batch_log_id' => $batchLogId
                ];
            }

            // バッチ処理完了ログ更新
            $completionMessage = "SHJ-9 売上集計処理正常完了 ({$type}) - 駐輪場数: {$processedParks}, 集計レコード数: {$summaryRecords}";
            $batchLog->update([
                'status' => BatchLog::STATUS_SUCCESS,
                'end_time' => now(),
                'message' => $completionMessage,
                'success_count' => 1
            ]);

            // 【処理5】オペレータキュー作成
            $this->createOperatorQueue($completionMessage, $batchLogId);

            Log::info('SHJ-9 売上集計処理完了', [
                'batch_log_id' => $batchLogId,
                'processed_parks' => $processedParks,
                'summary_records' => $summaryRecords
            ]);

            return [
                'success' => true,
                'message' => 'SHJ-9 売上集計処理が正常に完了しました',
                'processed_parks' => $processedParks,
                'summary_records' => $summaryRecords,
                'batch_log_id' => $batchLogId
            ];

        } catch (\Exception $e) {
            $errorMessage = 'SHJ-9 売上集計処理でエラーが発生: ' . $e->getMessage();
            
            if (isset($batchLog) && $batchLog) {
                $batchLog->update([
                    'status' => BatchLog::STATUS_ERROR,
                    'end_time' => now(),
                    'message' => $errorMessage,
                    'error_details' => $e->getMessage(),
                    'error_count' => 1
                ]);
            }

            Log::error('SHJ-9 売上集計処理エラー', [
                'batch_log_id' => $batchLogId,
                'exception' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);

            return [
                'success' => false,
                'message' => $errorMessage,
                'details' => $e->getMessage(),
                'batch_log_id' => $batchLogId
            ];
        }
    }

    /**
     * 【処理1】集計対象を設定する
     * 
     * @param string $type 集計種別
     * @param string $aggregationDate 集計対象日
     * @return array 集計対象情報
     */
    private function setAggregationTarget(string $type, string $aggregationDate): array
    {
        $date = Carbon::parse($aggregationDate);
        
        switch ($type) {
            case 'daily':
                return [
                    'type' => 'daily',
                    'start_date' => $date->format('Y-m-d'),
                    'end_date' => $date->format('Y-m-d'),
                    'summary_type' => '日次'
                ];
                
            case 'monthly':
                return [
                    'type' => 'monthly',
                    'start_date' => $date->startOfMonth()->format('Y-m-d'),
                    'end_date' => $date->endOfMonth()->format('Y-m-d'),
                    'summary_type' => '月次'
                ];
                
            case 'yearly':
                return [
                    'type' => 'yearly',
                    'start_date' => $date->startOfYear()->format('Y-m-d'),
                    'end_date' => $date->endOfYear()->format('Y-m-d'),
                    'summary_type' => '年次'
                ];
                
            default:
                throw new \InvalidArgumentException("不正な集計種別: {$type}");
        }
    }

    /**
     * 【処理2】駐輪場マスタを取得する
     * 
     * 仕様書のSQLクエリに基づく駐輪場情報取得
     * SELECT 駐輪場ID, 駐輪場名
     * FROM 駐輪場マスタ
     * WHERE 閉設フラグ <> 1
     * ORDER BY 駐輪場ふりがな
     *
     * @return array 駐輪場情報
     */
    private function getParkInformation(): array
    {
        try {
            $parkInfo = DB::table('park')
                ->select(['park_id', 'park_name'])
                ->where('park_close_flag', '<>', 1)
                ->orderBy('park_ruby')
                ->get()
                ->toArray();

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

            return $parkInfo;

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

            throw $e;
        }
    }

    /**
     * 駐輪場毎の売上集計処理
     * 
     * @param object $park 駐輪場情報
     * @param array $aggregationTarget 集計対象
     * @param string $type 集計種別
     * @return int 作成された集計レコード数
     */
    private function processEarningsForPark($park, array $aggregationTarget, string $type): int
    {
        try {
            // 【処理4】既存の売上集計結果を削除
            $this->deleteExistingSummary($park->park_id, $aggregationTarget);
            
            // 【処理3】車種区分毎に算出する
            $psections = $this->getPsectionInformation();
            $summaryRecords = 0;
            
            foreach ($psections as $psection) {
                $earningsData = $this->calculateEarningsForPsection(
                    $park->park_id, 
                    $psection->psection_id, 
                    $aggregationTarget
                );
                
                if ($this->hasEarningsData($earningsData)) {
                    // 売上集計結果を登録
                    $this->createEarningsSummary($park, $psection, $aggregationTarget, $earningsData, $type);
                    $summaryRecords++;
                }
            }
            
            Log::info('駐輪場売上集計完了', [
                'park_id' => $park->park_id,
                'park_name' => $park->park_name,
                'summary_records' => $summaryRecords
            ]);
            
            return $summaryRecords;
            
        } catch (\Exception $e) {
            Log::error('駐輪場売上集計エラー', [
                'park_id' => $park->park_id,
                'error' => $e->getMessage()
            ]);
            
            throw $e;
        }
    }

    /**
     * 車種区分情報取得
     * 
     * @return array 車種区分情報
     */
    private function getPsectionInformation(): array
    {
        return DB::table('psection')
            ->select(['psection_id', 'psection_subject'])
            ->get()
            ->toArray();
    }

    /**
     * 【処理3】車種区分毎に売上を算出する
     * 
     * 4つの項目を計算:
     * ①売上・件数
     * ②一時金売上  
     * ③解約返戻金
     * ④再発行金額・件数
     *
     * @param int $parkId 駐輪場ID
     * @param int $psectionId 車種区分ID
     * @param array $aggregationTarget 集計対象
     * @return array 売上データ
     */
    private function calculateEarningsForPsection(int $parkId, int $psectionId, array $aggregationTarget): array
    {
        $startDate = $aggregationTarget['start_date'];
        $endDate = $aggregationTarget['end_date'];
        
        // ①売上・件数（billing_amount）
        $salesData = DB::table('regular_contract')
            ->select([
                DB::raw('COUNT(*) as sales_count'),
                DB::raw('COALESCE(SUM(billing_amount), 0) as sales_amount')
            ])
            ->where('park_id', $parkId)
            ->where('psection_id', $psectionId)
            ->where('contract_flag', 1)
            ->whereBetween('contract_payment_day', [$startDate, $endDate])
            ->whereNull('contract_cancelday')
            ->first();

        // ②一時金売上（contract_money）
        $temporaryData = DB::table('regular_contract')
            ->select([
                DB::raw('COUNT(*) as temporary_count'),
                DB::raw('COALESCE(SUM(contract_money), 0) as temporary_amount')
            ])
            ->where('park_id', $parkId)
            ->where('psection_id', $psectionId)
            ->where('contract_flag', 1)
            ->whereBetween('contract_payment_day', [$startDate, $endDate])
            ->whereNotNull('contract_money')
            ->where('contract_money', '>', 0)
            ->first();

        // ③解約返戻金（refunds）
        $refundData = DB::table('regular_contract')
            ->select([
                DB::raw('COUNT(*) as refund_count'),
                DB::raw('COALESCE(SUM(refunds), 0) as refund_amount')
            ])
            ->where('park_id', $parkId)
            ->where('psection_id', $psectionId)
            ->whereBetween('contract_cancelday', [$startDate, $endDate])
            ->whereNotNull('refunds')
            ->where('refunds', '>', 0)
            ->first();

        // ④再発行金額・件数（seal_reissue_request）
        $reissueData = DB::table('regular_contract')
            ->select([
                DB::raw('COUNT(*) as reissue_count'),
                DB::raw('COALESCE(SUM(contract_seal_issue), 0) as reissue_amount')
            ])
            ->where('park_id', $parkId)
            ->where('psection_id', $psectionId)
            ->where('seal_reissue_request', 1)
            ->whereBetween('updated_at', [$startDate, $endDate])
            ->first();

        return [
            'sales_count' => $salesData->sales_count ?? 0,
            'sales_amount' => $salesData->sales_amount ?? 0,
            'temporary_count' => $temporaryData->temporary_count ?? 0,
            'temporary_amount' => $temporaryData->temporary_amount ?? 0,
            'refund_count' => $refundData->refund_count ?? 0,
            'refund_amount' => $refundData->refund_amount ?? 0,
            'reissue_count' => $reissueData->reissue_count ?? 0,
            'reissue_amount' => $reissueData->reissue_amount ?? 0
        ];
    }

    /**
     * 売上データの存在チェック
     * 
     * @param array $earningsData 売上データ
     * @return bool データが存在するかどうか
     */
    private function hasEarningsData(array $earningsData): bool
    {
        return $earningsData['sales_count'] > 0 ||
               $earningsData['temporary_count'] > 0 ||
               $earningsData['refund_count'] > 0 ||
               $earningsData['reissue_count'] > 0;
    }

    /**
     * 【処理4】既存の売上集計結果を削除
     * 
     * @param int $parkId 駐輪場ID
     * @param array $aggregationTarget 集計対象
     * @return void
     */
    private function deleteExistingSummary(int $parkId, array $aggregationTarget): void
    {
        DB::table('earnings_summary')
            ->where('park_id', $parkId)
            ->where('summary_start_date', $aggregationTarget['start_date'])
            ->where('summary_end_date', $aggregationTarget['end_date'])
            ->delete();
    }

    /**
     * 売上集計結果を登録
     * 
     * @param object $park 駐輪場情報
     * @param object $psection 車種区分情報
     * @param array $aggregationTarget 集計対象
     * @param array $earningsData 売上データ
     * @param string $type 集計種別
     * @return void
     */
    private function createEarningsSummary($park, $psection, array $aggregationTarget, array $earningsData, string $type): void
    {
        DB::table('earnings_summary')->insert([
            'park_id' => $park->park_id,
            'summary_type' => $aggregationTarget['summary_type'],
            'summary_start_date' => $aggregationTarget['start_date'],
            'summary_end_date' => $aggregationTarget['end_date'],
            'earnings_date' => $aggregationTarget['end_date'], // 集計日として終了日を使用
            'psection_id' => $psection->psection_id,
            'usertype_subject1' => $psection->psection_subject,
            'regular_new_count' => $earningsData['sales_count'],
            'regular_new_amount' => $earningsData['sales_amount'],
            'turnsum' => $earningsData['temporary_amount'],
            'turnsum_count' => $earningsData['temporary_count'],
            'refunds' => $earningsData['refund_amount'],
            'reissue_count' => $earningsData['reissue_count'],
            'reissue_amount' => $earningsData['reissue_amount'],
            'summary_note' => "SHJ-9 {$type} 売上集計結果",
            'created_at' => now(),
            'updated_at' => now(),
            'operator_id' => 0 // システム処理
        ]);
    }

    /**
     * 【処理5】オペレータキュー作成
     * 
     * @param string $message メッセージ
     * @param int $batchLogId バッチログID
     * @return void
     */
    private function createOperatorQueue(string $message, int $batchLogId): void
    {
        try {
            DB::table('operator_que')->insert([
                'que_class' => 9, // SHJ-9用のクラス
                'user_id' => null,
                'contract_id' => null,
                'park_id' => null,
                'que_comment' => $message,
                'que_status' => 1, // 完了
                'que_status_comment' => 'バッチ処理完了',
                'work_instructions' => "SHJ-9売上集計処理 BatchLogID: {$batchLogId}",
                'created_at' => now(),
                'updated_at' => now()
            ]);
            
            Log::info('オペレータキュー作成完了', [
                'batch_log_id' => $batchLogId,
                'message' => $message
            ]);
            
        } catch (\Exception $e) {
            Log::error('オペレータキュー作成エラー', [
                'batch_log_id' => $batchLogId,
                'error' => $e->getMessage()
            ]);
        }
    }

    /**
     * 集計種別のラベル取得
     * 
     * @param string $type 集計種別
     * @return string ラベル
     */
    private function getTypeLabel(string $type): string
    {
        switch ($type) {
            case 'daily':
                return '日次';
            case 'monthly':
                return '月次';
            case 'yearly':
                return '年次';
            default:
                return $type;
        }
    }
}
