<?php

namespace App\Services\Auth;

use App\Libraries\NotificationChannels\SmsChannel;
use Config\Database;

class OtpService
{
    private const CODE_LENGTH = 6;
    private const EXPIRY_MINUTES = 10;
    private const MAX_ATTEMPTS = 5;

    public function generateAndSend(int $userId, string $channel, string $destination): array
    {
        $code = str_pad((string) random_int(0, 999999), self::CODE_LENGTH, '0', STR_PAD_LEFT);

        Database::connect()->table('otp_verifications')->insert([
            'user_id'     => $userId,
            'channel'     => $channel,
            'code'        => $code,
            'destination' => $destination,
            'expires_at'  => date('Y-m-d H:i:s', strtotime('+' . self::EXPIRY_MINUTES . ' minutes')),
            'created_at'  => date('Y-m-d H:i:s'),
        ]);

        $sent = false;
        if ($channel === 'mobile') {
            $sent = (new SmsChannel())->send($destination, null, "Your Job Portal verification code is {$code}. It expires in " . self::EXPIRY_MINUTES . " minutes.");
        } else {
            $sent = (new \App\Libraries\NotificationChannels\EmailChannel())->send($destination, 'Verify your email', "<p>Your Job Portal verification code is <strong>{$code}</strong>. It expires in " . self::EXPIRY_MINUTES . " minutes.</p>");
        }

        // SMS in particular has no working gateway in the Composer-less
        // build by default (no Guzzle). Rather than leave the account
        // permanently unverifiable when delivery fails, always log the
        // code — and outside production, return it directly so testing
        // and no-gateway deployments still work end to end.
        log_message('info', "OTP for user {$userId} ({$channel} -> {$destination}): {$code}");

        $result = ['sent' => $sent, 'expires_in_minutes' => self::EXPIRY_MINUTES];
        if (ENVIRONMENT !== 'production') {
            $result['debug_code'] = $code; // never exposed in production
        }
        return $result;
    }

    /**
     * @throws \RuntimeException on invalid/expired/exhausted code
     */
    public function verify(int $userId, string $channel, string $code): void
    {
        $db = Database::connect();
        $row = $db->table('otp_verifications')
            ->where('user_id', $userId)
            ->where('channel', $channel)
            ->where('verified_at', null)
            ->orderBy('id', 'DESC')
            ->get()->getRowArray();

        if (! $row) {
            throw new \RuntimeException('No pending verification code found. Please request a new one.');
        }
        if (strtotime($row['expires_at']) < time()) {
            throw new \RuntimeException('This code has expired. Please request a new one.');
        }
        if ($row['attempts'] >= self::MAX_ATTEMPTS) {
            throw new \RuntimeException('Too many incorrect attempts. Please request a new code.');
        }

        if (! hash_equals($row['code'], $code)) {
            $db->table('otp_verifications')->where('id', $row['id'])->update(['attempts' => $row['attempts'] + 1]);
            throw new \RuntimeException('Incorrect code. Please try again.');
        }

        $db->table('otp_verifications')->where('id', $row['id'])->update(['verified_at' => date('Y-m-d H:i:s')]);

        $userModel = new \App\Models\UserModel();
        if ($channel === 'mobile') {
            $userModel->update($userId, ['mobile_verified_at' => date('Y-m-d H:i:s')]);
        } else {
            $userModel->update($userId, ['email_verified_at' => date('Y-m-d H:i:s')]);
        }

        $this->activateIfFullyVerified($userId);
    }

    /**
     * Once BOTH email and mobile are verified, the account moves from
     * pending_verification to active. Kept as its own step (rather than
     * inline) so it's easy to see the exact activation condition in one
     * place.
     */
    private function activateIfFullyVerified(int $userId): void
    {
        $userModel = new \App\Models\UserModel();
        $user = $userModel->find($userId);

        if ($user->status === 'pending_verification' && $user->email_verified_at && $user->mobile_verified_at) {
            $userModel->update($userId, ['status' => 'active']);
        }
    }
}
