<?php

namespace App\Services;

use App\Laravue\Models\User;
use App\Payment;
use App\Product;
use App\StorageContract;
use App\Traits\SendsTemplateEmail;
use App\UserCard;
use App\UserPaymentHistory;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class StorageChargeService
{
    use SendsTemplateEmail;
    /**
     * 保管料の初回課金（商品登録時に呼ばれる）
     *
     * @param Product $product 保管商品
     * @param User $user 保管ユーザー
     * @param string $storageStartDate 保管開始日
     * @return StorageContract
     * @throws \Exception
     */
    public function createContractAndCharge(Product $product, User $user, string $storageStartDate): StorageContract
    {
        $amount = StorageContract::DEFAULT_AMOUNT;
        $startDate = \Carbon\Carbon::parse($storageStartDate);
        $nextRenewalDate = $this->nextRenewalDateAfterToday($startDate);

        // PAY.JP課金
        $chargeId = $this->chargeUser($user, $amount, "保管料（商品ID: {$product->id}）");

        // 契約レコード作成
        $contract = DB::transaction(function () use ($product, $user, $startDate, $nextRenewalDate, $amount, $chargeId) {
            $contract = StorageContract::create([
                'user_id' => $user->id,
                'product_id' => $product->id,
                'storage_start_date' => $startDate->toDateString(),
                'next_renewal_date' => $nextRenewalDate->toDateString(),
                'amount' => $amount,
                'status' => StorageContract::STATUS_ACTIVE,
                'auto_renew' => true,
                'payjp_charge_id' => $chargeId,
            ]);

            // 決済履歴 (type: 0=レンタル, 2=保管料)
            UserPaymentHistory::create([
                'user_id' => $user->id,
                'storage_contract_id' => $contract->id,
                'type' => 2,
                'amount' => $amount,
                'payment_status' => 1,
            ]);

            return $contract;
        });

        Log::info("StorageChargeService: 保管契約作成 contract_id={$contract->id}, user_id={$user->id}, product_id={$product->id}, charge_id={$chargeId}");

        // 保管料決済完了メール送信
        try {
            $this->sendTemplateEmail('email_storage_charge_complete', $user->email, [
                'user_name' => $user->full_name ?? $user->name,
                'product_name' => $product->name ?? '',
                'amount' => number_format($contract->amount),
                'next_renewal_date' => $contract->next_renewal_date->format('Y年m月d日'),
            ]);
        } catch (\Exception $e) {
            Log::warning("StorageChargeService: 決済完了メール送信失敗 contract_id={$contract->id}, error={$e->getMessage()}");
        }

        return $contract;
    }

    /**
     * 保管契約のみ作成（課金なし、保管開始日が未来の場合用）
     * 自動更新コマンド(storage:renew)が保管開始日に課金を実行する
     */
    public function createContractOnly(Product $product, User $user, string $storageStartDate): StorageContract
    {
        $amount = StorageContract::DEFAULT_AMOUNT;
        $startDate = \Carbon\Carbon::parse($storageStartDate);

        $contract = StorageContract::create([
            'user_id' => $user->id,
            'product_id' => $product->id,
            'storage_start_date' => $startDate->toDateString(),
            'next_renewal_date' => $startDate->toDateString(),
            'amount' => $amount,
            'status' => StorageContract::STATUS_ACTIVE,
            'auto_renew' => true,
        ]);

        Log::info("StorageChargeService: 保管契約作成（課金予約） contract_id={$contract->id}, user_id={$user->id}, product_id={$product->id}, scheduled_date={$startDate->toDateString()}");

        return $contract;
    }

    /**
     * 保管契約を作成（初回課金スキップ。既にオフラインで受領済みのケース用）。
     * PAY.JP 課金も UserPaymentHistory の作成も行わず、契約レコードのみ作成。
     * next_renewal_date は通常通り「今日以降の次の年次更新日」にセットされ、
     * 以降の自動更新は普通に走る。
     */
    public function createContractWithoutCharge(Product $product, User $user, string $storageStartDate): StorageContract
    {
        $amount = StorageContract::DEFAULT_AMOUNT;
        $startDate = \Carbon\Carbon::parse($storageStartDate);
        $nextRenewalDate = $this->nextRenewalDateAfterToday($startDate);

        $contract = StorageContract::create([
            'user_id' => $user->id,
            'product_id' => $product->id,
            'storage_start_date' => $startDate->toDateString(),
            'next_renewal_date' => $nextRenewalDate->toDateString(),
            'amount' => $amount,
            'status' => StorageContract::STATUS_ACTIVE,
            'auto_renew' => true,
            'initial_charge_skipped' => true,
        ]);

        Log::info("StorageChargeService: 保管契約作成（初回課金スキップ・オフライン受領済み） contract_id={$contract->id}, user_id={$user->id}, product_id={$product->id}, start={$startDate->toDateString()}, next_renewal={$nextRenewalDate->toDateString()}");

        return $contract;
    }

    /**
     * 過去開始日の契約を登録する場合でも、過年度分を連続課金しない。
     * 初回課金は登録時に1回だけ行い、次回更新日は今日以降の次の年次更新日にする。
     */
    protected function nextRenewalDateAfterToday(\Carbon\Carbon $startDate): \Carbon\Carbon
    {
        $nextRenewalDate = $startDate->copy()->addYear();
        $today = now()->startOfDay();

        while ($nextRenewalDate->lte($today)) {
            $nextRenewalDate->addYear();
        }

        return $nextRenewalDate;
    }

    /**
     * 保管料の自動更新課金
     *
     * @param StorageContract $contract
     * @return bool
     */
    public function renewContract(StorageContract $contract): bool
    {
        $user = $contract->user;
        $amount = $contract->amount;

        // 作成直後の契約に対する誤発火を防ぐガード。
        // 初回課金と自動更新が短期間に重なる過去事故を防止するため、
        // 契約作成から 30 日未満は更新課金を実行しない。
        if ($contract->created_at && $contract->created_at->gt(now()->subDays(30))) {
            Log::warning("StorageChargeService: 更新スキップ（作成 30 日未満） contract_id={$contract->id}, created_at={$contract->created_at}");
            return false;
        }

        try {
            $chargeId = $this->chargeUser($user, $amount, "保管料更新（契約ID: {$contract->id}）");

            DB::transaction(function () use ($contract, $chargeId, $user, $amount) {
                $contract->update([
                    'next_renewal_date' => $contract->next_renewal_date->copy()->addYear()->toDateString(),
                    'payjp_charge_id' => $chargeId,
                    'renewal_notified_at' => null, // 次回通知のためリセット
                ]);

                UserPaymentHistory::create([
                    'user_id' => $user->id,
                    'storage_contract_id' => $contract->id,
                    'type' => 3, // 3=保管料更新
                    'amount' => $amount,
                    'payment_status' => 1,
                ]);
            });

            Log::info("StorageChargeService: 保管料更新成功 contract_id={$contract->id}, charge_id={$chargeId}");
            return true;
        } catch (\Exception $e) {
            Log::error("StorageChargeService: 保管料更新失敗 contract_id={$contract->id}, error={$e->getMessage()}");
            throw $e;
        }
    }

    /**
     * PAY.JPで課金を実行
     *
     * @param User $user
     * @param int $amount
     * @param string $description
     * @return string charge ID
     * @throws \Exception
     */
    protected function chargeUser(User $user, int $amount, string $description): string
    {
        if (!$user->payjp_customer_id) {
            throw new \Exception("ユーザーにPAY.JP顧客IDが登録されていません (user_id: {$user->id})");
        }

        \Payjp\Payjp::setApiKey(config('payjp.secret_key'));

        $defaultCard = UserCard::where('user_id', $user->id)
            ->where('is_default', 1)
            ->where('is_activated', 1)
            ->where('is_deleted', 0)
            ->whereIn('payjp_three_d_secure_status', ['verified', 'attempted'])
            ->first();

        if (!$defaultCard || !$defaultCard->payjp_card_id) {
            throw new \Exception("3Dセキュア認証済みカードが登録されていません (user_id: {$user->id})");
        }

        $charge = \Payjp\Charge::create([
            'customer' => $user->payjp_customer_id,
            'card' => $defaultCard->payjp_card_id,
            'amount' => $amount,
            'currency' => 'jpy',
            'description' => $description,
        ]);

        return $charge->id;
    }

    /**
     * 契約を解約する
     *
     * @param StorageContract $contract
     * @return void
     */
    public function cancelContract(StorageContract $contract): void
    {
        DB::transaction(function () use ($contract) {
            $contract->update([
                'status' => StorageContract::STATUS_CANCELLED,
                'auto_renew' => false,
                'cancelled_at' => now(),
            ]);

            $product = $contract->product;
            if ($product) {
                $product->update(['order_status' => Product::ORDER_STATUS_CANCELLED]);
            }
        });

        Log::info("StorageChargeService: 契約解約 contract_id={$contract->id}");
    }

    /**
     * 商品タイプ変更などで保管契約だけを終了する。
     * Product.order_status は呼び出し元で別途整合する想定なので触らない。
     */
    public function terminateContract(StorageContract $contract): void
    {
        $contract->update([
            'status' => StorageContract::STATUS_CANCELLED,
            'auto_renew' => false,
            'cancelled_at' => now(),
        ]);

        Log::info("StorageChargeService: 契約終了 contract_id={$contract->id}");
    }
}
