<?php

namespace App\Http\Controllers;

use App\Models\Customer;
use App\Models\CustomerPortalOtp;
use App\Services\EmailService;
use App\Services\SettingService;
use App\Services\SmsMessageService;
use App\Services\SmsTemplateService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;

class CustomerPortalLoginController extends Controller
{
    public function __construct(private SettingService $settings, private SmsMessageService $sms, private EmailService $email, private SmsTemplateService $smsTemplates) {}

    public function show()
    {
        return view('portal.login', ['identifierMode' => $this->settings->get('customer_portal.identifier_mode', 'code_or_pppoe'), 'verificationMode' => $this->settings->get('customer_portal.verification_mode', 'none')]);
    }

    public function identify(Request $request)
    {
        $key = 'customer-portal-login:'.$request->ip();
        if (RateLimiter::tooManyAttempts($key, 10)) {
            throw ValidationException::withMessages(['identifier' => 'Too many attempts. Please try again later.']);
        }
        RateLimiter::hit($key, 60);
        $verification = $this->settings->get('customer_portal.verification_mode', 'none');
        $validated = $request->validate(['identifier' => ['required', 'string', 'max:191'], 'channel' => [Rule::requiredIf($verification === 'sms_or_email'), 'nullable', Rule::in(['sms', 'email'])]]);
        $identifier = trim($validated['identifier']);
        $mode = $this->settings->get('customer_portal.identifier_mode', 'code_or_pppoe');
        $customer = Customer::query()->whereIn('status', [1, 2, 3])->where(function ($query) use ($identifier, $mode) {
            if ($mode !== 'pppoe_only') {
                $query->where('customer_code', $identifier);
            }
            if ($mode === 'code_or_pppoe') {
                $query->orWhere('ip_address', $identifier);
            }
            if ($mode === 'pppoe_only') {
                $query->where('ip_address', $identifier);
            }
        })->first();
        if (! $customer) {
            throw ValidationException::withMessages(['identifier' => 'Customer information did not match.']);
        }

        if ($verification === 'none') {
            return $this->login($request, $customer->id);
        }
        $channel = $verification === 'sms_or_email' ? $validated['channel'] : $verification;
        $destination = $channel === 'sms' ? $customer->contact_phone : $customer->email;
        if (! filled($destination) || $destination === 'N/A') {
            throw ValidationException::withMessages(['identifier' => strtoupper($channel).' verification is not available for this customer.']);
        }
        $code = (string) random_int(100000, 999999);
        CustomerPortalOtp::where('customer_id', $customer->id)->whereNull('used_at')->update(['used_at' => now()]);
        $otp = CustomerPortalOtp::create(['customer_id' => $customer->id, 'channel' => $channel, 'code_hash' => Hash::make($code), 'expires_at' => now()->addMinutes(5), 'request_ip' => $request->ip()]);
        if ($channel === 'sms') {
            $message = $this->smsTemplates->render('portal_otp', ['OTP' => $code, 'OTP_MINUTES' => 5], "Your customer portal verification code is {$code}. It expires in 5 minutes.");
            $this->sms->createAndSend(['recipient' => $destination, 'message' => $message, 'source' => 'portal_otp']);
        } else {
            $this->email->queueMessage(['recipient' => $destination, 'subject' => 'Customer Portal Verification Code', 'body' => "Your verification code is {$code}. It expires in 5 minutes."]);
        }
        $request->session()->put(['customer_portal_pending_otp' => $otp->id]);

        return redirect()->route('customer.login.verify')->with('otp_destination', $this->mask($destination, $channel));
    }

    public function showVerify(Request $request)
    {
        abort_unless($request->session()->has('customer_portal_pending_otp'), 403);

        return view('portal.verify');
    }

    public function verify(Request $request)
    {
        $validated = $request->validate(['code' => ['required', 'digits:6']]);
        $otp = CustomerPortalOtp::findOrFail($request->session()->get('customer_portal_pending_otp'));
        if ($otp->used_at || $otp->expires_at->isPast() || $otp->attempts >= 5) {
            throw ValidationException::withMessages(['code' => 'This verification code has expired. Please start again.']);
        }
        if (! Hash::check($validated['code'], $otp->code_hash)) {
            $otp->increment('attempts');
            throw ValidationException::withMessages(['code' => 'Verification code is incorrect.']);
        }
        $otp->update(['used_at' => now()]);
        $request->session()->forget('customer_portal_pending_otp');

        return $this->login($request, $otp->customer_id);
    }

    public function logout(Request $request)
    {
        $request->session()->forget(['customer_portal_customer_id', 'customer_portal_pending_otp']);
        $request->session()->regenerateToken();

        return redirect()->route('customer.login');
    }

    private function login(Request $request, int $customerId)
    {
        $request->session()->regenerate();
        $request->session()->put('customer_portal_customer_id', $customerId);
        RateLimiter::clear('customer-portal-login:'.$request->ip());

        return redirect()->route('portal.dashboard');
    }

    private function mask(string $value, string $channel): string
    {
        if ($channel === 'email') {
            [$name, $domain] = array_pad(explode('@', $value, 2), 2, '');

            return mb_substr($name, 0, 1).'***@'.$domain;
        }

        return mb_substr($value, 0, 3).'*****'.mb_substr($value, -2);
    }
}
