<?php

namespace App\Http\Controllers\Webhook;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Queue;
use App\Models\Batch\BatchLog;
use App\Models\SettlementTransaction;
use App\Models\RegularContract;
use App\Jobs\ProcessSettlementJob;
use Carbon\Carbon;

class WellnetController extends Controller
{
    /**
     * Wellnet PUSH 受信 (SHJ-4A)
     * 受け取った SOAP/XML を解析し、settlement_transaction に登録。
     * 幂等性チェック、データ検証、キュー投入を含む完全な処理を実行。
     */
    public function receive(Request $request)
    {
        $startedAt = now();
        $raw = $request->getContent();
        $md5Hash = md5($raw);

        // IP白名单检查（如果配置了）
        if (!$this->validateClientIp($request->ip())) {
            Log::warning('SHJ-4A IP白名单验证失败', [
                'ip' => $request->ip(),
                'content_length' => strlen($raw),
            ]);
            return $this->errorResponse('Unauthorized IP', 403);
        }

        // 事前にログ記録（サイズ上限に注意）
        Log::info('SHJ-4A Wellnet PUSH received', [
            'length' => strlen($raw),
            'content_type' => $request->header('Content-Type'),
            'ip' => $request->ip(),
            'md5_hash' => $md5Hash,
        ]);

        // 共通バッチログ: start
        $batch = BatchLog::createBatchLog(
            'shj4a',
            BatchLog::STATUS_START,
            [ 
                'ip' => $request->ip(), 
                'content_type' => $request->header('Content-Type'),
                'content_length' => strlen($raw),
                'md5_hash' => $md5Hash,
            ],
            'SHJ-4A Wellnet PUSH start'
        );

        try {
            // 【処理1】幂等性检查 - MD5重复检查
            $existingByMd5 = SettlementTransaction::where('md5_string', $md5Hash)->first();
            if ($existingByMd5) {
                Log::info('SHJ-4A 幂等性: MD5重复检测', [
                    'md5_hash' => $md5Hash,
                    'existing_id' => $existingByMd5->settlement_transaction_id,
                ]);
                
                $batch->update([
                    'status' => BatchLog::STATUS_SUCCESS,
                    'end_time' => now(),
                    'message' => 'SHJ-4A 幂等性: MD5重复，直接返回成功',
                    'success_count' => 0, // 幂等返回不计入成功数
                ]);

                return $this->successResponse('処理済み（幂等性）');
            }

            // 【処理2】SOAP/XML解析
            $xml = @simplexml_load_string($raw);
            if (!$xml) {
                throw new \RuntimeException('Invalid XML/SOAP payload');
            }

            // Body 以下の最初の要素を取得
            $nsBody = $xml->children('http://schemas.xmlsoap.org/soap/envelope/')->Body ?? null;
            $payloadNode = $nsBody ? current($nsBody->children()) : $xml; // SOAPでなければ素のXML想定

            // XML -> 配列化
            $payloadArray = json_decode(json_encode($payloadNode), true) ?? [];

            // 【処理3】データ抽出と正規化
            $data = $this->extractSettlementData($payloadArray, $md5Hash);

            // 【処理4】必須フィールド検証
            $this->validateRequiredFields($data);

            // 【処理5】複合キー重复检查（contract_payment_number + pay_date + settlement_amount）
            $existingByComposite = $this->findExistingByCompositeKey($data);
            if ($existingByComposite) {
                Log::info('SHJ-4A 幂等性: 複合キー重复検出', [
                    'contract_payment_number' => $data['contract_payment_number'],
                    'pay_date' => $data['pay_date'],
                    'settlement_amount' => $data['settlement_amount'],
                    'existing_id' => $existingByComposite->settlement_transaction_id,
                ]);

                $batch->update([
                    'status' => BatchLog::STATUS_SUCCESS,
                    'end_time' => now(),
                    'message' => 'SHJ-4A 幂等性: 複合キー重复，直接返回成功',
                    'success_count' => 0,
                ]);

                return $this->successResponse('処理済み（幂等性）');
            }

            // 【処理6】データベース取込と関連処理
            $settlementId = null;
            DB::transaction(function() use ($data, $batch, &$settlementId) {
                // 決済トランザクション登録
                $settlement = SettlementTransaction::create($data);
                $settlementId = $settlement->settlement_transaction_id;

                // 契約テーブルの軽微な更新（SHJ-4Bで正式更新）
                RegularContract::where('contract_payment_number', $data['contract_payment_number'])
                    ->update(['contract_updated_at' => now()]);

                // バッチログ成功更新
                $batch->update([
                    'status' => BatchLog::STATUS_SUCCESS,
                    'end_time' => now(),
                    'message' => 'SHJ-4A Wellnet PUSH stored successfully',
                    'success_count' => 1,
                    'parameters' => json_encode([
                        'settlement_transaction_id' => $settlementId,
                        'contract_payment_number' => $data['contract_payment_number'],
                        'settlement_amount' => $data['settlement_amount'],
                    ]),
                ]);

                Log::info('SHJ-4A 決済トランザクション登録成功', [
                    'settlement_transaction_id' => $settlementId,
                    'contract_payment_number' => $data['contract_payment_number'],
                    'settlement_amount' => $data['settlement_amount'],
                ]);
            });

            // 【処理7】SHJ-4B用キュージョブ投入
            try {
                $jobContext = [
                    'contract_payment_number' => $data['contract_payment_number'],
                    'settlement_amount' => $data['settlement_amount'],
                    'pay_date' => $data['pay_date'],
                    'pay_code' => $data['pay_code'],
                    'triggered_by' => 'shj4a_webhook',
                    'triggered_at' => $startedAt->toISOString(),
                ];

                ProcessSettlementJob::dispatch($settlementId, $jobContext);

                Log::info('SHJ-4A ProcessSettlementJob投入成功', [
                    'settlement_transaction_id' => $settlementId,
                    'job_context' => $jobContext,
                ]);

            } catch (\Throwable $jobError) {
                // キュー投入失敗は警告レベル（メイン処理は成功済み）
                Log::warning('SHJ-4A ProcessSettlementJob投入失敗', [
                    'settlement_transaction_id' => $settlementId,
                    'error' => $jobError->getMessage(),
                    'note' => '兜底巡検で処理される予定',
                ]);
            }

            return $this->successResponse();

        } catch (\Throwable $e) {
            Log::error('SHJ-4A error', [ 
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
                'md5_hash' => $md5Hash,
            ]);
            
            if (isset($batch)) {
                $batch->update([
                    'status' => BatchLog::STATUS_ERROR,
                    'end_time' => now(),
                    'message' => 'SHJ-4A failed: ' . $e->getMessage(),
                    'error_details' => $e->getTraceAsString(),
                    'error_count' => 1,
                ]);
            }

            return $this->errorResponse($e->getMessage());
        }
    }

    /**
     * IP白名单验证
     *
     * @param string $clientIp
     * @return bool
     */
    private function validateClientIp(string $clientIp): bool
    {
        $whitelist = config('services.wellnet.ip_whitelist', '');
        
        if (empty($whitelist)) {
            return true; // 白名单为空时不验证
        }

        $allowedIps = array_map('trim', explode(',', $whitelist));
        
        foreach ($allowedIps as $allowedIp) {
            if (strpos($allowedIp, '/') !== false) {
                // CIDR記法対応
                if ($this->ipInRange($clientIp, $allowedIp)) {
                    return true;
                }
            } else {
                // 直接IP比較
                if ($clientIp === $allowedIp) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * CIDR範囲でのIP检查
     *
     * @param string $ip
     * @param string $range
     * @return bool
     */
    private function ipInRange(string $ip, string $range): bool
    {
        list($subnet, $bits) = explode('/', $range);
        $ip = ip2long($ip);
        $subnet = ip2long($subnet);
        $mask = -1 << (32 - $bits);
        $subnet &= $mask; # nb: in case the supplied subnet wasn't correctly aligned
        return ($ip & $mask) == $subnet;
    }

    /**
     * 決済データの抽出と正規化
     *
     * @param array $payloadArray
     * @param string $md5Hash
     * @return array
     */
    private function extractSettlementData(array $payloadArray, string $md5Hash): array
    {
        // inData/Result系の取り出し（キー名差異に寛容）
        $first = function(array $arr, array $keys, $default = null) {
            foreach ($keys as $k) {
                if (isset($arr[$k])) return is_array($arr[$k]) ? $arr[$k] : (string)$arr[$k];
            }
            return $default;
        };

        $flat = $payloadArray;
        // よくある入れ子: { YoyakuNyukin: { inData: {...} } } / { YoyakuNyukinResponse: { YoyakuNyukinResult: {...} } }
        foreach (['inData','YoyakuSyunoBarCodeResult','YoyakuNyukinResult','YoyakuSyunoETicketResult'] as $k) {
            if (isset($flat[$k]) && is_array($flat[$k])) { $flat = $flat[$k]; }
        }

        $data = [
            'pay_code' => $first($flat, ['NyukinPayCode','SyunoPayCode','BcPayCode']),
            'contract_payment_number' => $first($flat, ['NyukinRecvNum','SyunoRecvNum','RecvNum','contract_payment_number']),
            'corp_code' => $first($flat, ['NyukinCorpCode','SyunoCorpCode','BcCorpCode','CorpCode']),
            'mms_date' => $first($flat, ['NyukinReferDate','SyunoMMSNo','MmsDate']),
            'cvs_code' => $first($flat, ['NyukinCvsCode','CvsCode']),
            'shop_code' => $first($flat, ['NyukinShopCode','ShopCode']),
            'pay_date' => $first($flat, ['NyukinPaidDate','PaidDate']),
            'settlement_amount' => $first($flat, ['NyukinPaidAmount','SyunoPayAmount','PaidAmount']),
            'stamp_flag' => $first($flat, ['NyukinInshiFlag','InshiFlag']),
            'status' => 'received',
            'md5_string' => $md5Hash,
        ];

        // データ正規化処理
        $data = $this->normalizeSettlementData($data);

        return $data;
    }

    /**
     * 決済データの正規化
     *
     * @param array $data
     * @return array
     */
    private function normalizeSettlementData(array $data): array
    {
        // 金額を数値化（非負数）
        if (!empty($data['settlement_amount'])) {
            $amount = preg_replace('/[^\d.]/', '', $data['settlement_amount']);
            $data['settlement_amount'] = max(0, (float)$amount);
        } else {
            $data['settlement_amount'] = null;
        }

        // 支払日時の正規化
        if (!empty($data['pay_date'])) {
            try {
                $data['pay_date'] = Carbon::parse($data['pay_date'])->format('Y-m-d H:i:s');
            } catch (\Throwable $e) {
                Log::warning('SHJ-4A 支払日時解析失敗', [
                    'original_pay_date' => $data['pay_date'],
                    'error' => $e->getMessage(),
                ]);
                $data['pay_date'] = null;
            }
        }

        // 文字列フィールドのトリム
        $stringFields = ['pay_code', 'contract_payment_number', 'corp_code', 'mms_date', 'cvs_code', 'shop_code', 'stamp_flag'];
        foreach ($stringFields as $field) {
            if (isset($data[$field])) {
                $data[$field] = trim($data[$field]) ?: null;
            }
        }

        return $data;
    }

    /**
     * 必須フィールドの検証
     *
     * @param array $data
     * @throws \RuntimeException
     */
    private function validateRequiredFields(array $data): void
    {
        // 必須フィールドのチェック
        if (empty($data['contract_payment_number'])) {
            throw new \RuntimeException('必須フィールドが不足: contract_payment_number (RecvNum)');
        }

        if (!isset($data['settlement_amount']) || $data['settlement_amount'] === null) {
            throw new \RuntimeException('必須フィールドが不足: settlement_amount');
        }

        if (empty($data['pay_date'])) {
            throw new \RuntimeException('必須フィールドが不足: pay_date');
        }
    }

    /**
     * 複合キーによる既存レコード検索
     *
     * @param array $data
     * @return SettlementTransaction|null
     */
    private function findExistingByCompositeKey(array $data): ?SettlementTransaction
    {
        return SettlementTransaction::where('contract_payment_number', $data['contract_payment_number'])
            ->where('pay_date', $data['pay_date'])
            ->where('settlement_amount', $data['settlement_amount'])
            ->first();
    }

    /**
     * 成功レスポンスの生成
     *
     * @param string $message
     * @return \Illuminate\Http\Response
     */
    private function successResponse(string $message = '正常処理'): \Illuminate\Http\Response
    {
        $responseFormat = config('services.wellnet.response_format', 'json');

        if ($responseFormat === 'soap') {
            return $this->soapResponse(0, $message);
        } else {
            return response()->json(['result' => 0, 'message' => $message]);
        }
    }

    /**
     * エラーレスポンスの生成
     *
     * @param string $message
     * @param int $httpCode
     * @return \Illuminate\Http\Response
     */
    private function errorResponse(string $message, int $httpCode = 500): \Illuminate\Http\Response
    {
        $responseFormat = config('services.wellnet.response_format', 'json');

        if ($responseFormat === 'soap') {
            return $this->soapResponse(1, $message, $httpCode);
        } else {
            $resultCode = ($httpCode >= 500) ? 1 : 2; // サーバーエラー:1, クライアントエラー:2
            return response()->json(['result' => $resultCode, 'error' => $message], $httpCode);
        }
    }

    /**
     * SOAP形式のレスポンス生成
     *
     * @param int $resultCode
     * @param string $message
     * @param int $httpCode
     * @return \Illuminate\Http\Response
     */
    private function soapResponse(int $resultCode, string $message, int $httpCode = 200): \Illuminate\Http\Response
    {
        $soapEnvelope = '<?xml version="1.0" encoding="utf-8"?>'
            . '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">'
            . '<soap:Body>'
            . '<WellnetPushResponse>'
            . '<Result>' . htmlspecialchars($resultCode) . '</Result>'
            . '<Message>' . htmlspecialchars($message) . '</Message>'
            . '</WellnetPushResponse>'
            . '</soap:Body>'
            . '</soap:Envelope>';

        return response($soapEnvelope, $httpCode)
            ->header('Content-Type', 'text/xml; charset=utf-8');
    }
}

