<?php

namespace App\Console\Commands;

use App\Events\NotifyEvent;
use App\Laravue\Models\User;
use App\Notify;
use App\Order;
use App\OwnerReward;
use App\Payment;
use App\Product;
use App\UserPaymentHistory;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use App\Traits\SendsTemplateEmail;
use Illuminate\Support\Facades\Log;

class ChargeScheduledPaymentsCommand extends Command
{
    use SendsTemplateEmail;

    protected $signature = 'orders:charge-scheduled
                            {--sync : Run synchronously (default)}
                            {--date= : Target date for payment (Y-m-d format, default: today)}
                            {--code= : Charge a specific order by code (for testing)}
                            {--force : Skip confirmation when using --code}
                            {--dry-run : Show what would be charged without actually charging}';

    protected $description = '着用日11日前の注文に対してクレジットカード自動決済を実行する';

    /** リトライ上限（この回数を超えた注文は対象外にし、管理者に通知） */
    const MAX_CHARGE_ATTEMPTS = 3;

    public function handle()
    {
        // payment_status=3（処理中）のままスタックした注文をリカバリー
        // プロセスクラッシュ等で処理中のまま残った注文を30分後にリセット
        // ※2 は「返金済み」（管理画面UIラベルと一致）なので処理中マーカーには使わない
        $stuckOrders = Order::where('payment_status', 3)
            ->where('updated_at', '<', now()->subMinutes(30))
            ->get();

        if ($stuckOrders->isNotEmpty()) {
            foreach ($stuckOrders as $stuckOrder) {
                $stuckOrder->update(['payment_status' => 0, 'payment_error' => null]);
                Log::warning('Reset stuck order from processing to unpaid', [
                    'order_id'   => $stuckOrder->id,
                    'order_code' => $stuckOrder->code,
                ]);
            }
            $this->warn("Reset {$stuckOrders->count()} stuck order(s) from processing status.");
        }

        // 特定注文の決済（テスト用）
        if ($orderCode = $this->option('code')) {
            return $this->chargeSpecificOrder($orderCode);
        }

        $targetDate = $this->option('date')
            ? Carbon::parse($this->option('date'))
            : Carbon::today();

        $paymentDeadlineDays = (int) config('rental_dates.payment_deadline_days', 11);
        $isDryRun = $this->option('dry-run');

        // 着用日が決済期限日以前（= 今日 + paymentDeadlineDays 以内）の未決済注文を取得
        // 範囲検索にすることで、cron障害等による決済漏れも翌日以降に拾える
        $wearingDateMax = $targetDate->copy()->addDays($paymentDeadlineDays);

        $this->info("Target date: {$targetDate->format('Y-m-d')}");
        $this->info("Looking for unpaid credit card orders with wearing date <= {$wearingDateMax->format('Y-m-d')} and > {$targetDate->format('Y-m-d')}...");

        if ($isDryRun) {
            $this->warn('[DRY RUN] No charges will be made.');
        }

        // 対象注文を取得
        $orders = $this->getTargetOrders($targetDate, $wearingDateMax);

        if ($orders->isEmpty()) {
            $this->info('No orders found for scheduled payment.');
            return 0;
        }

        $this->info("Found {$orders->count()} order(s) to charge.");

        $results = ['success' => 0, 'failed' => 0, 'skipped' => 0];

        foreach ($orders as $order) {
            if ($isDryRun) {
                $this->info("[DRY RUN] Would charge Order #{$order->code} - ¥" . number_format($order->total_price));
                $results['skipped']++;
                continue;
            }

            $result = $this->chargeOrder($order);

            if ($result['success']) {
                $results['success']++;
                $this->info("✓ Order #{$order->code} charged successfully (¥" . number_format($order->total_price) . ")");
            } else {
                $results['failed']++;
                $this->error("✗ Order #{$order->code} charge failed: {$result['error']}");
            }
        }

        $this->info('');
        $this->info("Results: {$results['success']} success, {$results['failed']} failed, {$results['skipped']} skipped");

        return $results['failed'] > 0 ? 1 : 0;
    }

    /**
     * 対象注文を取得する
     * - クレジットカード払い (payment_method = 0)
     * - 未決済 (payment_status = 0)
     * - カードID保存済み (payment_card_id IS NOT NULL)
     * - キャンセルされていない (status != 8)
     * - 着用日が今日より後 かつ 決済期限日以内
     * - リトライ上限未到達
     * - ユーザーの本人確認が承認済み
     */
    protected function getTargetOrders(Carbon $targetDate, Carbon $wearingDateMax)
    {
        return Order::where('payment_method', 0) // クレジットカード
            ->where('payment_status', 0) // 未決済（1=決済済/2=返金済/3=処理中は除外）
            ->whereNotNull('payment_card_id') // カードID保存済み
            ->where('status', '!=', Order::STATUS_CANCELLED) // キャンセル以外
            ->where('payment_charge_attempts', '<', self::MAX_CHARGE_ATTEMPTS) // リトライ上限未到達
            ->where(function ($q) use ($targetDate, $wearingDateMax) {
                // payment_deadline が手動設定されている場合はその日付で判定
                $q->where(function ($qq) use ($targetDate) {
                    $qq->whereNotNull('payment_deadline')
                       ->whereDate('payment_deadline', '<=', $targetDate->format('Y-m-d'));
                })
                // 未設定の場合は着用日 - 11日（payment_deadline_days）で判定
                ->orWhere(function ($qq) use ($targetDate, $wearingDateMax) {
                    $qq->whereNull('payment_deadline')
                       ->whereHas('order_detail', function ($query) use ($targetDate, $wearingDateMax) {
                           $query->whereDate('wear_date', '<=', $wearingDateMax->format('Y-m-d'))
                                 ->whereDate('wear_date', '>', $targetDate->format('Y-m-d'));
                       });
                });
            })
            ->whereHas('user', function ($query) {
                $query->where('identity_status', 'approved');
            })
            ->with(['user', 'order_detail'])
            ->get();
    }

    /**
     * 注文に対してPAY.JP決済を実行する
     */
    protected function chargeOrder(Order $order): array
    {
        // Order::user() リレーションは select() でカラム制限しているため、
        // payjp_customer_id を含む全カラムを取得するために直接クエリする
        $user = User::find($order->user_id);

        if (!$user) {
            return $this->failOrder($order, 'ユーザーが見つかりません');
        }

        if (!$user->payjp_customer_id) {
            return $this->failOrder($order, 'PAY.JP顧客IDが登録されていません');
        }

        try {
            // Step 1: 行ロックで payment_status を「処理中(2)」に変更
            // これにより他のプロセスが同じ注文を処理することを防止
            $canProceed = DB::transaction(function () use ($order) {
                $locked = Order::where('id', $order->id)
                    ->lockForUpdate()
                    ->first();

                if (!$locked || $locked->payment_status != 0) {
                    return false; // すでに決済済みまたは処理中
                }

                $locked->update([
                    'payment_status'          => 3, // 処理中
                    'payment_charge_attempts' => $locked->payment_charge_attempts + 1,
                ]);

                return true;
            });

            if (!$canProceed) {
                $this->info("  Order #{$order->code} already paid or in progress, skipping.");
                return ['success' => true];
            }

            // $order を最新状態にリフレッシュ
            $order->refresh();

            // Step 1.5: 課金対象カードが現在も有効か再確認
            // 注文後にカードの再認証が失敗した等のケースで、無効なカードでの課金を防ぐ。
            $card = \App\UserCard::where('id', $order->payment_card_id)
                ->where('is_deleted', 0)
                ->where('is_activated', 1)
                ->whereIn('payjp_three_d_secure_status', ['verified', 'attempted'])
                ->first();
            if (!$card) {
                // 課金不可。失敗扱いにしてリトライカウントを進める（payment_status=0 に戻す）
                $order->update([
                    'payment_status'        => 0,
                    'payment_error'         => 'カードの本人認証が必要です。お客様にカード再認証をご依頼ください。',
                ]);
                Log::warning("ChargeScheduledPayments: カード3DS未認証のため課金スキップ order_id={$order->id}, payment_card_id={$order->payment_card_id}");
                return ['success' => false, 'message' => 'カード3DS未認証'];
            }

            // Step 2: PAY.JP課金実行
            \Payjp\Payjp::setApiKey(config('payjp.secret_key'));

            $payjpCustomer = \Payjp\Customer::retrieve($user->payjp_customer_id);

            // オーナー商品の場合はPayouts型決済（platform_fee付き）
            $chargeParams = [
                'customer'    => $payjpCustomer->id,
                'card'        => $order->payment_card_id,
                'amount'      => (int) $order->total_price,
                'currency'    => 'jpy',
                'description' => "Scheduled payment for Order #{$order->code}",
            ];

            $ownerRewardData = null;
            $orderDetail = $order->order_detail()->first();
            if ($orderDetail && $orderDetail->product_id) {
                $product = Product::find($orderDetail->product_id);
                if ($product && $product->isOwnerProduct() && $product->owner_id && $product->reward_amount > 0) {
                    $owner = User::find($product->owner_id);
                    if ($owner && $owner->payjp_tenant_id) {
                        $platformFee = (int) $order->total_price - (int) $product->reward_amount;
                        if ($platformFee > 0) {
                            $chargeParams['platform_fee'] = $platformFee;
                            $chargeParams['tenant'] = $owner->payjp_tenant_id;
                            $ownerRewardData = [
                                'owner_id' => $owner->id,
                                'product_id' => $product->id,
                                'rental_amount' => (int) $order->total_price,
                                'reward_amount' => (int) $product->reward_amount,
                                'platform_fee' => $platformFee,
                                'payment_method' => OwnerReward::PAYMENT_PAYJP,
                            ];
                        }
                    } elseif ($owner) {
                        // テナント未登録の場合は通常決済 + 手動振込用報酬レコード
                        $platformFee = (int) $order->total_price - (int) $product->reward_amount;
                        if ($platformFee < 0) {
                            // 報酬額がレンタル金額を上回る（＝赤字払い）設定ミスはスキップして警告
                            Log::warning('OwnerReward skipped: reward_amount exceeds rental amount (negative platform_fee)', [
                                'order_id'      => $order->id,
                                'product_id'    => $product->id,
                                'rental_amount' => (int) $order->total_price,
                                'reward_amount' => (int) $product->reward_amount,
                            ]);
                        } else {
                            $ownerRewardData = [
                                'owner_id' => $owner->id,
                                'product_id' => $product->id,
                                'rental_amount' => (int) $order->total_price,
                                'reward_amount' => (int) $product->reward_amount,
                                'platform_fee' => $platformFee,
                                'payment_method' => OwnerReward::PAYMENT_MANUAL,
                            ];
                        }
                    }
                }
            }

            $charge = \Payjp\Charge::create($chargeParams);

            // Step 3: 決済成功 → DB更新
            try {
                DB::transaction(function () use ($order, $charge, $ownerRewardData) {
                    $order->update([
                        'payment_status'          => 1, // 決済完了
                        'payment_charged_at'      => now(),
                        'payment_card_id'         => null,
                        'payment_error'           => null,
                    ]);

                    $payment = Payment::create([
                        'order_id'        => $order->id,
                        'code'            => $charge->id,
                        'total'           => $charge->amount,
                        'amount_captured' => $charge->captured ? $charge->amount : 0,
                        'amount_refunded' => $charge->amount_refunded,
                        'application_fee' => 0,
                        'application_fee_amount' => 0,
                        'currency'        => $charge->currency,
                        'type'            => $charge->card->brand ?? '',
                        'provider'        => $charge->card->brand ?? '',
                        'customer'        => $charge->customer,
                        'payment_method'  => $charge->card->brand ?? '',
                        'brand'           => $charge->card->brand ?? '',
                        'funding'         => '',
                        'network'         => '',
                        'country'         => $charge->card->country ?? '',
                        'last4'           => $charge->card->last4 ?? '',
                        'exp_month'       => $charge->card->exp_month ?? '',
                        'exp_year'        => $charge->card->exp_year ?? '',
                        'failure_code'    => $charge->failure_code,
                        'failure_message' => $charge->failure_message,
                        'content'         => $charge->card->id ?? '',
                        'customer_info'   => '',
                        'finished_at'     => date('Y-m-d H:i:s', $charge->created),
                        'status'          => $charge->paid ? 1 : 0,
                        'created_at'      => now(),
                        'updated_at'      => now(),
                    ]);

                    UserPaymentHistory::create([
                        'user_id'        => $order->user_id,
                        'type'           => 0,
                        'order_id'       => $order->id,
                        'payment_id'     => $payment->id,
                        'amount'         => (int) $order->total_price,
                        'payment_status' => 1,
                        'created_at'     => now(),
                        'updated_at'     => now(),
                    ]);

                    // オーナー報酬レコード作成（既に存在する場合は二重作成しない）
                    if ($ownerRewardData && !OwnerReward::where('order_id', $order->id)->exists()) {
                        OwnerReward::create(array_merge($ownerRewardData, [
                            'order_id' => $order->id,
                            'transfer_status' => $ownerRewardData['payment_method'] === OwnerReward::PAYMENT_PAYJP
                                ? OwnerReward::TRANSFER_COMPLETED
                                : OwnerReward::TRANSFER_PENDING,
                            'transferred_at' => $ownerRewardData['payment_method'] === OwnerReward::PAYMENT_PAYJP
                                ? now()
                                : null,
                            'payjp_transfer_id' => $ownerRewardData['payment_method'] === OwnerReward::PAYMENT_PAYJP
                                ? $charge->id
                                : null,
                        ]));
                    }
                });
            } catch (\Exception $dbException) {
                // PAY.JP課金成功だがDB更新失敗 → 返金して管理者に通知
                Log::critical('PAY.JP charged but DB update failed, attempting refund', [
                    'order_id'  => $order->id,
                    'charge_id' => $charge->id,
                    'error'     => $dbException->getMessage(),
                ]);

                try {
                    $refund = \Payjp\Charge::retrieve($charge->id);
                    $refund->refund();
                    Log::info('Refund successful after DB failure', ['charge_id' => $charge->id]);
                } catch (\Exception $refundException) {
                    Log::critical('REFUND ALSO FAILED - manual intervention required', [
                        'charge_id' => $charge->id,
                        'error'     => $refundException->getMessage(),
                    ]);
                }

                // payment_status を0に戻す（次回リトライ対象に）
                try {
                    $order->update(['payment_status' => 0]);
                } catch (\Exception $resetException) {
                    Log::critical('Failed to reset payment_status after refund', [
                        'order_id' => $order->id,
                        'error'    => $resetException->getMessage(),
                    ]);
                }

                return $this->failOrder($order, 'DB更新失敗（PAY.JP返金済み）: ' . $dbException->getMessage());
            }

            // 決済成功メール送信
            $this->sendPaymentSuccessEmail($order, $user);

            Log::info('Scheduled payment charged successfully', [
                'order_id'  => $order->id,
                'order_code' => $order->code,
                'amount'    => $order->total_price,
                'charge_id' => $charge->id,
            ]);

            return ['success' => true];

        } catch (\Payjp\Error\Card $e) {
            // PAY.JP課金失敗（カードエラー）→ payment_status を0に戻す
            $this->resetPaymentStatus($order);
            return $this->failOrder($order, 'カードエラー: ' . $e->getMessage());
        } catch (\Payjp\Error\InvalidRequest $e) {
            $this->resetPaymentStatus($order);
            return $this->failOrder($order, 'リクエストエラー: ' . $e->getMessage());
        } catch (\Exception $e) {
            $this->resetPaymentStatus($order);
            return $this->failOrder($order, $e->getMessage());
        }
    }

    /**
     * payment_status を安全に0（未決済）にリセットする
     */
    protected function resetPaymentStatus(Order $order): void
    {
        try {
            $order->refresh();
            // payment_status=3（処理中）の場合のみリセット
            if ($order->payment_status == 3) {
                $order->update(['payment_status' => 0]);
            }
        } catch (\Exception $e) {
            Log::critical('Failed to reset payment_status', [
                'order_id' => $order->id,
                'error'    => $e->getMessage(),
            ]);
        }
    }

    /**
     * 決済失敗の処理
     */
    protected function failOrder(Order $order, string $error): array
    {
        $order->update([
            'payment_error' => $error,
        ]);

        $attempts = $order->payment_charge_attempts;
        $isMaxAttempts = $attempts >= self::MAX_CHARGE_ATTEMPTS;

        // 管理画面に通知（リトライ上限到達時は警告レベルを変更）
        $notifyMessage = $isMaxAttempts
            ? "[決済失敗] 注文#{$order->code} リトライ上限({$attempts}回)到達 — 手動対応が必要です"
            : "[決済失敗] 注文#{$order->code} (試行{$attempts}/" . self::MAX_CHARGE_ATTEMPTS . "回) — 明日再試行されます";

        try {
            Notify::create([
                'content'      => $notifyMessage,
                'module'       => 'order',
                'item_id'      => $order->code,
                'item_content' => json_encode([
                    'code'     => $order->code,
                    'error'    => $error,
                    'attempts' => $attempts,
                    'amount'   => $order->total_price,
                ]),
                'created_at'   => now(),
                'updated_at'   => now(),
            ]);
            event(new NotifyEvent());
        } catch (\Exception $e) {
            Log::warning('Failed to create admin notification for payment failure', [
                'order_code' => $order->code,
                'error'      => $e->getMessage(),
            ]);
        }

        // 決済失敗メール送信（ユーザーへ）
        $user = User::find($order->user_id);
        if ($user) {
            $this->sendPaymentFailedEmail($order, $user, $error);
        }

        Log::error('Scheduled payment charge failed', [
            'order_id'   => $order->id,
            'order_code' => $order->code,
            'amount'     => $order->total_price,
            'error'      => $error,
            'attempts'   => $attempts,
            'max_reached' => $isMaxAttempts,
        ]);

        return ['success' => false, 'error' => $error];
    }

    /**
     * 決済成功メールを送信（CMS テンプレート）
     */
    protected function sendPaymentSuccessEmail(Order $order, $user)
    {
        try {
            $orderDetail = $order->order_detail->first();
            $wearDate = $orderDetail && $orderDetail->wear_date
                ? Carbon::parse($orderDetail->wear_date)->format('Y/m/d')
                : '未定';

            $this->sendTemplateEmail('u_payment_success', $user->email, [
                'full_name'   => $user->full_name ?? $user->name,
                'order_code'  => $order->code,
                'total_price' => number_format($order->total_price),
                'wear_date'   => $wearDate,
            ]);
        } catch (\Exception $e) {
            Log::error('Failed to send payment success email', [
                'order_id' => $order->id,
                'error'    => $e->getMessage(),
            ]);
        }
    }

    /**
     * 決済失敗メールを送信（CMS テンプレート）
     */
    protected function sendPaymentFailedEmail(Order $order, $user, string $error)
    {
        try {
            $orderDetail = $order->order_detail->first();
            $wearDate = $orderDetail && $orderDetail->wear_date
                ? Carbon::parse($orderDetail->wear_date)->format('Y/m/d')
                : '未定';

            $this->sendTemplateEmail('u_payment_failed', $user->email, [
                'full_name'     => $user->full_name ?? $user->name,
                'order_code'    => $order->code,
                'total_price'   => number_format($order->total_price),
                'wear_date'     => $wearDate,
                'error_message' => $error,
            ]);
        } catch (\Exception $e) {
            Log::error('Failed to send payment failed email', [
                'order_id' => $order->id,
                'error'    => $e->getMessage(),
            ]);
        }
    }

    /**
     * 特定注文の決済（テスト用）
     */
    protected function chargeSpecificOrder(string $orderCode): int
    {
        $this->info("Looking for order with code: {$orderCode}");

        $order = Order::where('code', $orderCode)
            ->with(['user', 'order_detail'])
            ->first();

        if (!$order) {
            $this->error("Order with code '{$orderCode}' not found.");
            return 1;
        }

        $this->info("Found order #{$order->id}");
        $this->info("  - User ID: {$order->user_id}");
        $this->info("  - Status: {$order->status}");
        $this->info("  - Payment Method: {$order->payment_method}");
        $this->info("  - Payment Status: {$order->payment_status}");
        $this->info("  - Payment Card ID: " . ($order->payment_card_id ?? 'N/A'));
        $this->info("  - Total Price: ¥" . number_format($order->total_price));

        if ($order->payment_status == 1) {
            $this->warn("Order is already paid.");
            return 1;
        }

        if ($order->payment_status == 3) {
            $this->warn("Order is currently being processed.");
            return 1;
        }

        if ($order->payment_method != 0) {
            $this->warn("Order is not a credit card payment (payment_method={$order->payment_method}).");
            return 1;
        }

        if (!$order->payment_card_id) {
            $this->warn("Order has no payment card ID stored.");
            return 1;
        }

        if (!$this->option('force') && !$this->confirm("Charge ¥" . number_format($order->total_price) . " for this order?")) {
            $this->info("Cancelled by user.");
            return 0;
        }

        $result = $this->chargeOrder($order);

        if ($result['success']) {
            $this->info("✓ Payment charged successfully.");
            return 0;
        } else {
            $this->error("✗ Payment failed: {$result['error']}");
            return 1;
        }
    }
}
