<?php

namespace App\Services;

use App\RegistrationOtp;
use Illuminate\Support\Str;
use App\Tools\SmsTools;
use Carbon\Carbon;
use Illuminate\Http\Request;

class OTPService
{
    /**
     * Generate a secure OTP
     *
     * @return string
     */
    public function generateOTP(): string
    {
        return str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT);
    }

    /**
     * Generate a session token for OTP operations
     *
     * @return string
     */
    public function generateSessionToken(): string
    {
        return Str::random(64);
    }

    /**
     * Get client IP address
     *
     * @param Request|null $request
     * @return string|null
     */
    public function getClientIP(?Request $request = null): ?string
    {
        if (!$request) {
            $request = request();
        }

        return $request->ip();
    }

    /**
     * Get client user agent
     *
     * @param Request|null $request
     * @return string|null
     */
    public function getClientUserAgent(?Request $request = null): ?string
    {
        if (!$request) {
            $request = request();
        }

        return $request->userAgent();
    }

    /**
     * Check rate limiting for OTP requests
     *
     * @param string $phone
     * @param int $maxAttempts
     * @param int $windowMinutes
     * @return bool
     */
    public function isRateLimited(string $phone, int $maxAttempts = 5, int $windowMinutes = 60): bool
    {
        $rateLimitInfo = RegistrationOtp::getRateLimitInfo($phone, $windowMinutes);

        return $rateLimitInfo['total_requests'] >= $maxAttempts;
    }

    /**
     * Get rate limit information
     *
     * @param string $phone
     * @param int $windowMinutes
     * @return array
     */
    public function getRateLimitInfo(string $phone, int $windowMinutes = 60): array
    {
        return RegistrationOtp::getRateLimitInfo($phone, $windowMinutes);
    }

        /**
     * Create registration session in database
     *
     * @param string $sessionToken
     * @param array $additionalData
     * @return RegistrationOtp
     */
    public function createRegistrationSession(string $sessionToken, array $additionalData = []): RegistrationOtp
    {
        $request = request();

        // Create a placeholder OTP record (will be updated when actual OTP is generated)
        return RegistrationOtp::create([
            'session_token' => $sessionToken,
            'phone_number' => $additionalData['phone_number'] ?? '',
            'otp_code' => '', // Empty initially
            'ip_address' => $this->getClientIP($request),
            'user_agent' => $this->getClientUserAgent($request),
            'expires_at' => now()->addMinutes($additionalData['expiration_minutes'] ?? 5),
            'status' => 'pending',
            'attempts_count' => 0,
            'max_attempts' => 3
        ]);
    }

    /**
     * Verify session token exists and is valid
     *
     * @param string $sessionToken
     * @return bool
     */
    public function verifySessionToken(string $sessionToken): bool
    {
        $otp = RegistrationOtp::findBySessionToken($sessionToken);

        if (!$otp) {
            return false;
        }

        // Check if OTP is expired
        if ($otp->isExpired()) {
            $otp->markAsExpired();
            return false;
        }

        return true;
    }

    /**
     * Get session data
     *
     * @param string $sessionToken
     * @return array|null
     */
    public function getSessionData(string $sessionToken): ?array
    {
        $otp = RegistrationOtp::findBySessionToken($sessionToken);

        if (!$otp) {
            return null;
        }

        return [
            'phone_number' => $otp->phone_number,
            'status' => $otp->status,
            'otp_verified' => $otp->isVerified(),
            'phone_verified' => $otp->isVerified(),
            'created_at' => $otp->created_at,
            'expires_at' => $otp->expires_at,
            'verified_at' => $otp->verified_at,
            'ip_address' => $otp->ip_address,
            'user_agent' => $otp->user_agent,
            'attempts_count' => $otp->attempts_count,
            'max_attempts' => $otp->max_attempts
        ];
    }

    /**
     * Update session data
     *
     * @param string $sessionToken
     * @param array $data
     * @return bool
     */
    public function updateSessionData(string $sessionToken, array $data): bool
    {
        $otp = RegistrationOtp::findBySessionToken($sessionToken);

        if (!$otp) {
            return false;
        }

        $otp->update($data);
        return true;
    }

    /**
     * Mark phone as verified in session
     *
     * @param string $sessionToken
     * @param string $phone
     * @return bool
     */
    public function markPhoneVerified(string $sessionToken, string $phone): bool
    {
        $otp = RegistrationOtp::findBySessionToken($sessionToken);

        if (!$otp || $otp->phone_number !== $phone) {
            return false;
        }

        $otp->markAsVerified();
        return true;
    }

    /**
     * Mark OTP as verified in session
     *
     * @param string $sessionToken
     * @return bool
     */
    public function markOTPVerified(string $sessionToken): bool
    {
        $otp = RegistrationOtp::findBySessionToken($sessionToken);

        if (!$otp) {
            return false;
        }

        $otp->markAsVerified();
        return true;
    }

    /**
     * Clean up session
     *
     * @param string $sessionToken
     * @return bool
     */
    public function cleanupSession(string $sessionToken): bool
    {
        $otp = RegistrationOtp::findBySessionToken($sessionToken);

        if (!$otp) {
            return false;
        }

        $otp->delete();
        return true;
    }

    /**
     * Send SMS with OTP
     *
     * @param string $phone
     * @param string $otp
     * @param string $source
     * @param string $messageTemplate
     * @return array
     */
    public function sendOTPSMS(string $phone, string $otp, string $source = 'DEYWURO', string $messageTemplate = null): array
    {
        $formattedPhone = SmsTools::format_gh_number($phone);

        if (!$messageTemplate) {
            $messageTemplate = "Hello, your registration OTP is {otp}. Valid for 5 minutes.";
        }

        $message = str_replace('{otp}', $otp, $messageTemplate);

        return $this->sendSMS($message, $formattedPhone, $source);
    }

    /**
     * Send SMS using the SMS API
     *
     * @param string $message
     * @param string $phone
     * @param string $source
     * @return array
     */
    public function sendSMS(string $message, string $phone, string $source): array
    {
        $message = urlencode($message);
        $num = urlencode($phone);

        $url = "https://deywuro.com/api/sms?username=sammy&password=cse%402021_npontu&source=$source&destination=" . $num . "&message=" . $message;

        $curl = curl_init($url);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        $output = curl_exec($curl);
        curl_close($curl);

        return json_decode($output, true) ?? ['code' => 500, 'msg' => 'SMS sending failed'];
    }

    /**
     * Verify OTP
     *
     * @param string $phone
     * @param string $sessionToken
     * @param string $userOTP
     * @return bool
     */
    public function verifyOTP(string $phone, string $sessionToken, string $userOTP): bool
    {
        $otp = RegistrationOtp::findByPhoneAndSession($phone, $sessionToken);

        if (!$otp) {
            return false;
        }

        return $otp->verifyOtp($userOTP);
    }

    /**
     * Complete OTP verification process
     *
     * @param string $phone
     * @param string $sessionToken
     * @param string $userOTP
     * @return array
     */
    public function completeOTPVerification(string $phone, string $sessionToken, string $userOTP): array
    {
        if (!$this->verifySessionToken($sessionToken)) {
            return ['code' => 401, 'msg' => 'Invalid session'];
        }

        $otp = RegistrationOtp::findByPhoneAndSession($phone, $sessionToken);

        if (!$otp) {
            return ['code' => 401, 'msg' => 'OTP not found'];
        }

        if ($otp->isExpired()) {
            $otp->markAsExpired();
            return ['code' => 401, 'msg' => 'OTP has expired'];
        }

        if ($otp->isVerified()) {
            return ['code' => 401, 'msg' => 'OTP already verified'];
        }

        if ($otp->isMaxAttemptsExceeded()) {
            return ['code' => 401, 'msg' => 'Maximum attempts exceeded'];
        }

        if ($otp->verifyOtp($userOTP)) {
            $this->markPhoneVerified($sessionToken, $phone);
            return ['code' => 0, 'msg' => 'OTP verified successfully'];
        }

        return ['code' => 401, 'msg' => 'Invalid OTP'];
    }

    /**
     * Process OTP request with all security checks
     *
     * @param string $phone
     * @param string $sessionToken
     * @param string $source
     * @param string $messageTemplate
     * @return array
     */
    public function processOTPRequest(string $phone, string $sessionToken, string $source = 'DEYWURO', string $messageTemplate = null): array
    {
        // Verify session token
        if (!$this->verifySessionToken($sessionToken)) {
            return ['code' => 401, 'msg' => 'Invalid session'];
        }

        // Check rate limiting
        if ($this->isRateLimited($phone)) {
            $remainingTime = $this->getRemainingTimeUntilReset($phone);
            return ['code' => 429, 'msg' => "Too many OTP requests. Please try again in {$remainingTime} minutes."];
        }

        // Check if phone is already registered (for registration OTPs)
        if ($this->isPhoneAlreadyRegistered($phone)) {
            return ['code' => 401, 'msg' => 'Phone number already registered'];
        }

        // Get existing OTP record
        $otpRecord = RegistrationOtp::findBySessionToken($sessionToken);
        if (!$otpRecord) {
            return ['code' => 401, 'msg' => 'Invalid session'];
        }

        // Generate OTP
        $otpCode = $this->generateOTP();

        // Update existing OTP record with actual OTP code and phone number
        $otpRecord->update([
            'phone_number' => $phone,
            'otp_code' => $otpCode,
            'expires_at' => now()->addMinutes(5)
        ]);

        // Send SMS
        $result = $this->sendOTPSMS($phone, $otpCode, $source, $messageTemplate);

        if ($result['code'] === 0) {
            return ['code' => 0, 'msg' => 'OTP sent successfully'];
        }

        // If SMS failed, delete the OTP record
        $otpRecord->delete();
        return ['code' => 500, 'msg' => 'Failed to send OTP'];
    }

    /**
     * Check if phone number is already registered
     *
     * @param string $phone
     * @return bool
     */
    public function isPhoneAlreadyRegistered(string $phone): bool
    {
        // This would typically check against your database
        // For now, we'll return false as this should be handled by the controller
        return false;
    }

    /**
     * Validate phone number format
     *
     * @param string $phone
     * @return bool
     */
    public function validatePhoneNumber(string $phone): bool
    {
        $phone = SmsTools::format_gh_number($phone);
        return preg_match('/^233[0-9]{9}$/', $phone);
    }

    /**
     * Get remaining attempts for a phone number
     *
     * @param string $phone
     * @param int $maxAttempts
     * @return int
     */
    public function getRemainingAttempts(string $phone, int $maxAttempts = 3): int
    {
        $rateLimitInfo = $this->getRateLimitInfo($phone);
        $attempts = $rateLimitInfo['total_requests'];

        return max(0, $maxAttempts - $attempts);
    }

    /**
     * Get detailed rate limit status for a phone number
     *
     * @param string $phone
     * @param int $maxAttempts
     * @param int $windowMinutes
     * @return array
     */
    public function getRateLimitStatus(string $phone, int $maxAttempts = 3, int $windowMinutes = 60): array
    {
        $rateLimitInfo = $this->getRateLimitInfo($phone, $windowMinutes);
        $remainingAttempts = $this->getRemainingAttempts($phone, $maxAttempts);
        $remainingTime = $this->getRemainingTimeUntilReset($phone, $windowMinutes);

        return [
            'phone_number' => $phone,
            'total_requests' => $rateLimitInfo['total_requests'],
            'failed_attempts' => $rateLimitInfo['failed_attempts'],
            'remaining_attempts' => $remainingAttempts,
            'remaining_time_minutes' => $remainingTime,
            'is_rate_limited' => $this->isRateLimited($phone, $maxAttempts, $windowMinutes),
            'window_minutes' => $windowMinutes,
            'max_attempts' => $maxAttempts
        ];
    }

    /**
     * Clear rate limit for a phone number (admin function)
     *
     * @param string $phone
     * @return void
     */
    public function clearRateLimit(string $phone): void
    {
        // Delete all OTP records for this phone number
        RegistrationOtp::where('phone_number', $phone)->delete();
    }

    /**
     * Get remaining time until rate limit resets
     *
     * @param string $phone
     * @param int $windowMinutes
     * @return int
     */
    public function getRemainingTimeUntilReset(string $phone, int $windowMinutes = 60): int
    {
        $oldestRecord = RegistrationOtp::where('phone_number', $phone)
            ->orderBy('created_at', 'asc')
            ->first();

        if (!$oldestRecord) {
            return 0;
        }

        $resetTime = $oldestRecord->created_at->addMinutes($windowMinutes);
        $remainingMinutes = now()->diffInMinutes($resetTime, false);

        return max(0, $remainingMinutes);
    }

    /**
     * Get session expiration time
     *
     * @param string $sessionToken
     * @return Carbon|null
     */
    public function getSessionExpiration(string $sessionToken): ?Carbon
    {
        $otp = RegistrationOtp::findBySessionToken($sessionToken);
        return $otp ? $otp->expires_at : null;
    }

    /**
     * Check if session is expired
     *
     * @param string $sessionToken
     * @return bool
     */
    public function isSessionExpired(string $sessionToken): bool
    {
        $expiration = $this->getSessionExpiration($sessionToken);
        return $expiration ? $expiration->isPast() : true;
    }

    /**
     * Get OTP statistics
     *
     * @return array
     */
    public function getStatistics(): array
    {
        return [
            'total' => RegistrationOtp::count(),
            'pending' => RegistrationOtp::pending()->count(),
            'verified' => RegistrationOtp::verified()->count(),
            'expired' => RegistrationOtp::expired()->count(),
            'failed' => RegistrationOtp::failed()->count(),
        ];
    }

    /**
     * Clean up expired OTPs
     *
     * @return int
     */
    public function cleanupExpiredOtps(): int
    {
        return RegistrationOtp::cleanupExpired();
    }

    /**
     * Validate phone number has verified OTP for registration
     *
     * @param string $phoneNumber
     * @param int $timeRangeMinutes
     * @return array
     */
    public function validatePhoneForRegistration(string $phoneNumber, int $timeRangeMinutes = 10): array
    {
        $formattedPhone = SmsTools::format_gh_number($phoneNumber);
        $timeRangeStart = now()->subMinutes($timeRangeMinutes);

        // Check if phone has a verified OTP within the time range
        $verifiedOtp = RegistrationOtp::where('phone_number', $formattedPhone)
            ->where('status', 'verified')
            ->where('verified_at', '>=', $timeRangeStart)
            ->orderBy('verified_at', 'desc')
            ->first();

        if (!$verifiedOtp) {
            return [
                'valid' => false,
                'message' => 'Phone number has not been verified with OTP within the last ' . $timeRangeMinutes . ' minutes. Please request a new OTP.',
                'code' => 401
            ];
        }

        // Check if the verification was done today
        if (!$verifiedOtp->verified_at->isToday()) {
            return [
                'valid' => false,
                'message' => 'Phone verification was not completed today. Please request a new OTP.',
                'code' => 401
            ];
        }

        // Check if verification is within the specified time range
        $verificationAge = now()->diffInMinutes($verifiedOtp->verified_at);
        if ($verificationAge > $timeRangeMinutes) {
            return [
                'valid' => false,
                'message' => 'Phone verification has expired. Please request a new OTP.',
                'code' => 401
            ];
        }

        return [
            'valid' => true,
            'message' => 'Phone number is verified and valid for registration.',
            'code' => 0,
            'verified_at' => $verifiedOtp->verified_at,
            'verification_age_minutes' => $verificationAge
        ];
    }

    /**
     * Get phone verification history
     *
     * @param string $phoneNumber
     * @param int $days
     * @return array
     */
    public function getPhoneVerificationHistory(string $phoneNumber, int $days = 7): array
    {
        $formattedPhone = SmsTools::format_gh_number($phoneNumber);
        $startDate = now()->subDays($days);

        $verifications = RegistrationOtp::where('phone_number', $formattedPhone)
            ->where('status', 'verified')
            ->where('verified_at', '>=', $startDate)
            ->orderBy('verified_at', 'desc')
            ->get();

        return [
            'phone_number' => $formattedPhone,
            'total_verifications' => $verifications->count(),
            'verifications' => $verifications->map(function ($otp) {
                return [
                    'verified_at' => $otp->verified_at->format('Y-m-d H:i:s'),
                    'ip_address' => $otp->ip_address,
                    'user_agent' => $otp->user_agent,
                    'age_minutes' => now()->diffInMinutes($otp->verified_at)
                ];
            })->toArray()
        ];
    }
}
