<?php

namespace App\Services;

use App\Models\Ope;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;

/**
 * メール OTP 認証サービス
 * 
 * 6桁の OTP コード生成、送信、検証、および24時間免OTPの判定を担当します
 */
class EmailOtpService
{
    // OTP コード長（6桁）
    const OTP_CODE_LENGTH = 6;

    // OTP 有効期限（分）
    const OTP_VALIDITY_MINUTES = 10;

    // OTP 重発制限時間（秒）
    const OTP_RESEND_DELAY_SECONDS = 60;

    // 24時間免OTP期限（時間）
    const OTP_FREE_PERIOD_HOURS = 24;

    /**
     * OTP コードを生成・保存して送信
     * 
     * 6桁の数字コードを生成し、hash化して保存します
     * 同時に有効期限と最後の送信時刻を更新します
     * 
     * @param Ope $ope オペレータモデル
     * @return string 明文のOTPコード（メール送信用）
     */
    public function issue(Ope $ope): string
    {
        // 6桁の数字コードを生成
        $plainCode = str_pad(random_int(0, 999999), self::OTP_CODE_LENGTH, '0', STR_PAD_LEFT);

        // hash化して保存
        $ope->email_otp_code_hash = Hash::make($plainCode);
        $ope->email_otp_expires_at = now()->addMinutes(self::OTP_VALIDITY_MINUTES);
        $ope->email_otp_last_sent_at = now();
        $ope->save();

        return $plainCode;
    }

    /**
     * OTP コードを検証
     * 
     * 入力されたコードが正しく、且つ有効期限内かを確認します
     * 
     * @param Ope $ope オペレータモデル
     * @param string $inputCode ユーザーが入力したコード
     * @return bool 検証結果
     */
    public function verify(Ope $ope, string $inputCode): bool
    {
        // 有効期限チェック
        if (!$ope->email_otp_expires_at || $ope->email_otp_expires_at < now()) {
            return false;
        }

        // hash化されたコードと比較
        if (!Hash::check($inputCode, $ope->email_otp_code_hash)) {
            return false;
        }

        // 検証成功：検証完了時刻を記録し、OTPコードをクリア
        $ope->email_otp_verified_at = now();
        $ope->email_otp_code_hash = null;
        $ope->email_otp_expires_at = null;
        $ope->save();

        return true;
    }

    /**
     * OTP が最近検証されたかを確認（24時間以内）
     * 
     * @param Ope $ope オペレータモデル
     * @return bool true: 24時間以内に検証済み、false: 未検証または24時間以上経過
     */
    public function isOtpRecent(Ope $ope): bool
    {
        if (!$ope->email_otp_verified_at) {
            return false;
        }

        return $ope->email_otp_verified_at > now()->subHours(self::OTP_FREE_PERIOD_HOURS);
    }

    /**
     * 重発可能かを確認
     * 
     * 前回送信から60秒以上経過しているかを確認します
     * 
     * @param Ope $ope オペレータモデル
     * @return bool true: 重発可能、false: 待機中
     */
    public function canResend(Ope $ope): bool
    {
        if (!$ope->email_otp_last_sent_at) {
            return true;
        }

        return $ope->email_otp_last_sent_at <= now()->subSeconds(self::OTP_RESEND_DELAY_SECONDS);
    }

    /**
     * 次の重発までの待機時間を取得（秒）
     * 
     * @param Ope $ope オペレータモデル
     * @return int 待機時間（秒）、0以下は重発可能
     */
    public function getResendWaitSeconds(Ope $ope): int
    {
        if (!$ope->email_otp_last_sent_at) {
            return 0;
        }

        $diffSeconds = now()->diffInSeconds($ope->email_otp_last_sent_at, absolute: false);
        $waitSeconds = self::OTP_RESEND_DELAY_SECONDS - abs($diffSeconds);

        return max(0, $waitSeconds);
    }

    /**
     * OTP コードのマスク済みメールアドレスを取得
     * 
     * abc@example.com -> abc***@example.com のようなマスク表示用
     * 
     * @param string $email メールアドレス
     * @return string マスク済みメールアドレス
     */
    public function maskEmail(string $email): string
    {
        [$name, $domain] = explode('@', $email);

        // 名前部分の最初の1文字を残して、残りは*でマスク
        $maskedName = substr($name, 0, 1) . str_repeat('*', max(0, strlen($name) - 1));

        return "{$maskedName}@{$domain}";
    }
}
