<?php

namespace App\Services;

use App\Jobs\SendClientSmsJob;
use App\Jobs\SendEmailJob;
use App\Models\Admin;
use App\Models\Client;
use Illuminate\Support\Facades\Hash;

/**
 * PRC-770 — Çok-kanallı 2FA giriş-OTP kanal servisi. TOTP dışı yöntemlerin
 * (email/sms) giriş-anı OTP mantığını Client + Admin için ortak toplar; iki
 * TwoFactorController bu servisi çağırır. TOTP işi mevcut TotpService'te kalır.
 *
 * OTP DB'de Hash'li saklanır; asıl savunma rate-limit + 5 dk expiry + tek-kullanım.
 */
class TwoFactorChannelService
{
    public const OTP_TTL_MINUTES = 5;
    public const RESEND_MIN_SECONDS = 30;

    public function __construct(private SmsService $smsService) {}

    /** İzin verilen 2FA yöntemleri. */
    public static function methods(): array
    {
        return ['totp', 'email', 'sms'];
    }

    /** 6 haneli sayısal giriş OTP'si üretir. */
    public function generateLoginOtp(): string
    {
        return str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT);
    }

    /** OTP'yi Hash'li sakla + 5 dk expiry + sent_at (resend throttle). */
    public function storeOtp(Client|Admin $user, string $code): void
    {
        $user->forceFill([
            'two_factor_otp_hash' => Hash::make($code),
            'two_factor_otp_expires_at' => now()->addMinutes(self::OTP_TTL_MINUTES),
            'two_factor_otp_sent_at' => now(),
        ])->saveQuietly();
    }

    /** Tek-kullanım + expiry doğrula; başarıda OTP'yi invalidate et. */
    public function verifyOtp(Client|Admin $user, string $code): bool
    {
        if (empty($user->two_factor_otp_hash) || empty($user->two_factor_otp_expires_at)) {
            return false;
        }
        if (now()->greaterThan($user->two_factor_otp_expires_at)) {
            return false;
        }
        if (! Hash::check($code, $user->two_factor_otp_hash)) {
            return false;
        }

        // Tek-kullanım: doğrulanan OTP'yi tüket.
        $user->forceFill([
            'two_factor_otp_hash' => null,
            'two_factor_otp_expires_at' => null,
        ])->saveQuietly();

        return true;
    }

    /** Geçerli (süresi dolmamış) bir OTP hâlihazırda gönderilmiş mi. */
    public function hasActiveOtp(Client|Admin $user): bool
    {
        return ! empty($user->two_factor_otp_hash)
            && ! empty($user->two_factor_otp_expires_at)
            && now()->lessThan($user->two_factor_otp_expires_at);
    }

    /** Kanal hedefini maskele (e-posta/telefon) — challenge ekranında ipucu. */
    public function maskedTarget(Client|Admin $user, ?string $method = null): string
    {
        $method = $method ?? $user->two_factor_method;

        if ($method === 'sms') {
            $phone = (string) ($user->phone ?? '');

            return $phone === '' ? '' : ('•••• ' . substr($phone, -4));
        }

        $email = (string) ($user->email ?? '');
        if (! str_contains($email, '@')) {
            return $email;
        }
        [$local, $domain] = explode('@', $email, 2);
        $head = substr($local, 0, 1);
        $tail = strlen($local) > 1 ? substr($local, -1) : '';

        return $head . str_repeat('•', 3) . $tail . '@' . $domain;
    }

    /** Resend min-aralık geçti mi (throttle). */
    public function canResend(Client|Admin $user): bool
    {
        if (empty($user->two_factor_otp_sent_at)) {
            return true;
        }

        return $user->two_factor_otp_sent_at->diffInSeconds(now()) >= self::RESEND_MIN_SECONDS;
    }

    /**
     * Aktif kanala göre OTP üret + sakla + gönder (email/sms). TOTP çağırmaz.
     * `$method` verilirse onu kullanır (enable akışında method henüz kayıtlı
     * değil); yoksa kullanıcının kayıtlı `two_factor_method`'una düşer.
     */
    public function dispatchOtp(Client|Admin $user, ?string $method = null): void
    {
        $code = $this->generateLoginOtp();
        $this->storeOtp($user, $code);

        $method = $method ?? $user->two_factor_method;
        $merge = ['OTP_CODE' => $code, 'OTP_EXPIRY_MINUTES' => self::OTP_TTL_MINUTES];

        if ($user instanceof Client) {
            if ($method === 'email') {
                SendEmailJob::dispatch('Two-Factor Login Code', 'client', $user->id, $merge);
            } elseif ($method === 'sms') {
                SendClientSmsJob::dispatch('Two-Factor Login Code', 'client', $user->id, $merge);
            }

            return;
        }

        // Admin: e-posta system entity + toEmail override; SMS doğrudan SmsService.
        if ($method === 'email') {
            SendEmailJob::dispatch('Two-Factor Login Code', 'system', $user->id, $merge, null, $user->email);
        } elseif ($method === 'sms' && ! empty($user->phone)) {
            $this->smsService->send(
                $user->phone,
                __('admin/common.two_factor_otp_sms', ['code' => $code, 'minutes' => self::OTP_TTL_MINUTES])
            );
        }
    }
}
