<?php
declare(strict_types=1);

namespace App\Models;

use App\Lib\Database;
use App\Helpers\EmailValidator;
use App\Helpers\{EmailQueue, Template, TextHelper};
use App\Util\{Helper, FileSystem, Locate, Random, JWTManager, DeviceManager};
use App\Models\{Users, Restaurant};

class LoginHandler
{
    protected $db;
    private $maxLoginAttempts = 4;
    private $lockoutTime = 2 * 60; // 10 minutes in seconds
    protected DeviceManager $deviceManager;

    public function __construct()
    {
        $this->db = Database::connectDB();
        $this->deviceManager = new DeviceManager;
    }

    public function getTokenData()
    {
        $bearerToken = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
        $serverToken = str_replace('Bearer ', '', $bearerToken);
        $jwtManager = new JWTManager;
        return $jwtManager->getTokenData($serverToken);
    }

    public function addLoginHistory(array $data, string $userType, string $is_active = '1'): void
    {
        $clientSession = \session_id();
        $clientIP = Locate::getUserIP();
        $clientDevice = Helper::getDevice();
        $clientDeviceHash = Helper::getDeviceId();

        $this->db->query(
            "INSERT IGNORE INTO zoop_session_history",
            [
                'id' => NULL,
                'user_id' => $data['id'],
                'user_type' => $userType,
                'user_email' => $data['email'],
                'user_session' => $clientSession,
                'user_ip' => $clientIP,
                'user_device' => $clientDevice,
                'user_device_hash' => $clientDeviceHash,
                'user_active' => $is_active
            ]
        );
    }

    public function restaurantMobileLogin(string $email, string $avatarURL, string $fullName)
    {
        $restaurant = new Restaurant;
        $emailQueue = new EmailQueue;
        $template = new Template;
        $textHelper = new TextHelper;
        $jwtManager = new JWTManager;

        $newUser = false;

        if (!$restaurant->hasEmail($email)) {
            if (!$this->validateEmail($email)) {
                return ['status' => false, 'message' => 'Sorry, this email address is not valid.'];
            }

            $result = $this->db->transaction(function ($database) use ($restaurant, $email, $avatarURL, $fullName) {

                $restaurantUpload = Random::generate(20) . '.jpg';
                $restaurantImage = Helper::downloadImageFromURL($avatarURL, \UPLOADS['restaurant_upload'] . '/' . $restaurantUpload);

                if ($restaurantImage) {

                    $IPData = Locate::getUserDataByIP();
                    $countryData = Locate::getCountry($IPData['countryCode']);
                    $detectedCurrency = $countryData['currencyCode'];
                    $detectedCountry = $countryData['countryName'];
                    $isCountrySupported = $restaurant->isSupportOnlinePayment($detectedCountry);

                    $database->query("INSERT INTO restaurant",
                        [
                            'id' => NULL,
                            'restaurant_uuid' => Random::generate(10, '0-9A-Z'),
                            'restaurant_name' => '',
                            'restaurant_owner' => $fullName,
                            'restaurant_avatar' => $restaurantUpload,
                            'restaurant_email' => $email,
                            'restaurant_phone' => '',
                            'restaurant_address' => '',
                            'restaurant_currency' => $detectedCurrency,
                            'restaurant_latitude' => '',
                            'restaurant_longitude' => '',
                            'restaurant_password' => '',
                            'restaurant_type' => 'restaurant',
                            'restaurant_slug' => '',
                            'restaurant_active' => 'closed',
                            'restaurant_status' => 'pending',
                        ]
                    );

                    // get inserted partner id
                    $partnerId = (int) $database->getInsertId();

                    $database->query(
                        "INSERT INTO restaurant_image",
                        [
                            'image_id' => NULL,
                            'restaurant_id' => $partnerId,
                            'image_type' => 'logo',
                            'image_file' => 'default-logo.png',
                        ],
                        [
                            'image_id' => NULL,
                            'restaurant_id' => $partnerId,
                            'image_type' => 'header',
                            'image_file' => 'default-header.png',
                        ]
                    );

                    $database->query("INSERT INTO restaurant_setting", [
                        "setting_id" => NULL,
                        "restaurant_id" => $partnerId,
                        "dinein_minimum_order_price" => 0.00,
                        "dinein_table" => 0,
                        "takeaway_minimum_order_price" => 0.00,
                        "delivery_minimum_order_price" => 0.00,
                        "delivery_food_prepare" => "30 Minutes",
                        "delivery_distance" => 20,
                        "delivery_charge" => 0.00,
                        "allow_online_payment" => $isCountrySupported == true ? 'true' : 'false',
                        "allow_cash_payment" => $isCountrySupported == true ? 'false': 'true',
                    ]);

                    $database->query("INSERT INTO restaurant_hours", [
                        "hour_id" => NULL,
                        "restaurant_id" => $partnerId,
                        "opening_time_morning" => "12:00 AM",
                        "closing_time_morning" => "12:00 PM",
                        "opening_time_evening" => "12:00 PM",
                        "closing_time_evening" => "12:00 AM",
                    ]);

                    $database->query("INSERT INTO restaurant_charges", [
                        "charge_id" => NULL,
                        "restaurant_id" => $partnerId,
                    ]);

                    // loop through monday to sunday and insert into database
                    foreach(['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'] as $day) {
                        $database->query("INSERT INTO restaurant_discount_delivery",
                            [
                                'discount_delivery_id' => NULL,
                                'restaurant_id' => $partnerId,
                                'discount_period' => 'both',
                                'discount_day' => $day,
                                'discount_percent' => 0,
                                'discount_minimum' => 0,
                            ]
                        );

                        $database->query("INSERT INTO restaurant_discount",
                            [
                                'discount_id' => NULL,
                                'restaurant_id' => $partnerId,
                                'discount_type' => 'dine-in',
                                'discount_period' => 'both',
                                'discount_day' => $day,
                                'discount_percent' => 0,
                                'discount_minimum' => 0,
                            ]
                        );

                        $database->query("INSERT INTO restaurant_discount",
                            [
                                'discount_id' => NULL,
                                'restaurant_id' => $partnerId,
                                'discount_type' => 'takeaway',
                                'discount_period' => 'both',
                                'discount_day' => $day,
                                'discount_percent' => 0,
                                'discount_minimum' => 0,
                            ]
                        );

                        $database->query("INSERT INTO restaurant_discount",
                            [
                                'discount_id' => NULL,
                                'restaurant_id' => $partnerId,
                                'discount_type' => 'delivery',
                                'discount_period' => 'both',
                                'discount_day' => $day,
                                'discount_percent' => 0,
                                'discount_minimum' => 0,
                            ]
                        );
                    }

                    return true;
                }
            });

            $newUser = true;

            $welcomeTemplate = $template->email('restaurant-welcome');
            if (!empty($welcomeTemplate)) {
                $subject = "Welcome Onboard - " . APP['name'];
                $bodyParameters = array_merge([
                    "[subject]" => $subject,
                    "[person_name]" => $fullName,
                    "[redirect_link]" => \URLROOT . '/restaurant/login'
                ], MAIL_TEXT);
                $body = $textHelper::replaceText($welcomeTemplate, $bodyParameters);

                $emailSender = ['email' => SMTP['email'], 'name' => SMTP['name']];
                $emailReceiver = ['email' => $email, 'name' => $fullName];
                $emailQueue->addQueue($emailSender, $emailReceiver, $subject, $body); // add email to queue
            }

            if (!$result) {
                return ['status' => false, 'message' => 'Something went wrong. Please try again.'];
            }
        }

        $restaurantData = $restaurant->getByEmail($email);
        $restaurantId = (int) $restaurantData->id;
        
        $partnerSession = [
            'id' => $restaurantId,
            'avatar' => $restaurantData->restaurant_avatar,
            'name' => $restaurantData->restaurant_name,
            'email' => $restaurantData->restaurant_email,
            'phone' => $restaurantData->restaurant_phone,
            'address' => $restaurantData->restaurant_address,
            'currency' => $restaurantData->restaurant_currency,
            'slug' => $restaurantData->restaurant_slug,
            'latitude' => $restaurantData->restaurant_latitude,
            'longitude' => $restaurantData->restaurant_longitude,
            'status' => $restaurantData->restaurant_status,
            'active' => $restaurantData->restaurant_active,
        ];

        $jwtToken = $jwtManager->generateToken($partnerSession);

        // create session data
        $sessionData = [
            'session_id' => \session_id(),
            'session_token' => $jwtToken,
            'wizard' => $restaurantData->restaurant_wizard,
        ];

        // create user session
        $_SESSION[SESSION['restaurant']] = $sessionData;
        // add device to session
        $this->deviceManager->addDevice($restaurantId, 'restaurant');

        $isWizardCompleted = $restaurant->isOnboardingCompleted($restaurantId);
        if (!$isWizardCompleted) {
            $newUser = true;
        }

        return [
            "status" => true,
            "message" => "You've successfully logged in.",
            "id" => $restaurantData->id,
            'isSignUp' => $newUser,
            'token' => $jwtToken
        ];
    }

    private function validateEmail(string $email): bool
    {
        $validEmail = new EmailValidator;
        return (!$validEmail->isDisposable($email) && $validEmail->isValidFormat($email));
    }

    private function getAttemptPath(string $customerEmail, string $userType): string
    {
        return \APPROOT . "/Secret/{$userType}-attempts-{$customerEmail}.json";
    }

    private function resetLoginAttempts(string $customerEmail, string $userType): void
    {
        $userEmail = Helper::trim($customerEmail);
        $attemptPath = $this->getAttemptPath($userEmail, $userType);

        if (Helper::isFileExists($attemptPath)) {
            // Read the data from the file
            $attemptData = json_decode(FileSystem::read($attemptPath), true);

            // Update the failed attempt counter
            $attemptData['loginAttempts'] = 0;
            $attemptData['loginTimeout'] = \time() + $this->lockoutTime;

            // Write the updated data to the file
            FileSystem::write($attemptPath, json_encode($attemptData));
        }
    }

    private function deleteLoginAttempts(string $customerEmail, string $userType): void
    {
        $userEmail = Helper::trim($customerEmail);
        $attemptPath = $this->getAttemptPath($userEmail, $userType);

        if (Helper::isFileExists($attemptPath)) {
            FileSystem::delete($attemptPath);
        }
    }

    private function isMaxLoginAttemptsReached(string $customerEmail, string $userType): bool
    {
        $userEmail = Helper::trim($customerEmail);
        $attemptPath = $this->getAttemptPath($userEmail, $userType);

        // Read the data from the file
        $data = json_decode(FileSystem::read($attemptPath), true);
        return $data['loginAttempts'] >= $this->maxLoginAttempts;
    }

    private function getRemainingLockoutTime(string $customerEmail, string $userType)
    {
        $userEmail = Helper::trim($customerEmail);
        $attemptPath = $this->getAttemptPath($userEmail, $userType);

        // Read the data from the file
        $data = json_decode(FileSystem::read($attemptPath), true);

        $remainingTime = ceil(($data['loginTimeout'] - time()) / 60);
        return max(0, $remainingTime); // Ensure the remaining time is not negative
    }

    private function isLockedOut(string $customerEmail, string $userType): bool
    {
        $userEmail = Helper::trim($customerEmail);
        $attemptPath = $this->getAttemptPath($userEmail, $userType);

        if (Helper::isFileExists($attemptPath)) {
            // Read the data from the file
            $data = json_decode(FileSystem::read($attemptPath), true);

            $currentTime = time();
            $maxLoginAttempts = $this->isMaxLoginAttemptsReached($userEmail, $userType);
            $timeout = $currentTime < $data['loginTimeout'] && $maxLoginAttempts;

            if ($timeout) {
                return true;
            } elseif ($maxLoginAttempts && $currentTime > $data['loginTimeout']) {
                $this->resetLoginAttempts($userEmail, $userType);
                return false;
            }
        }

        return false;
    }

    private function incrementLoginAttempts(string $customerEmail, string $userType)
    {
        $userEmail = Helper::trim($customerEmail);
        $attemptPath = $this->getAttemptPath($userEmail, $userType);

        if (!Helper::isFileExists($attemptPath)) {
            $this->lockoutUser($customerEmail, $userType);
        }

        // Read the data from the file
        $data = json_decode(FileSystem::read($attemptPath), true);

        // Increment failed attempts
        $data['loginAttempts']++;

        // Check if max login attempts reached
        if ($this->isMaxLoginAttemptsReached($customerEmail, $userType)) {
            // Add lockout time
            $this->lockoutUser($customerEmail, $userType);
        }

        // Save JSON file
        $attemptData = json_encode($data);
        FileSystem::write($attemptPath, $attemptData);
    }

    private function lockoutUser(string $customerEmail, $userType): void
    {
        $userEmail = Helper::trim($customerEmail);
        $attemptPath = $this->getAttemptPath($userEmail, $userType);

        // Check if attempt file exists
        if (!Helper::isFileExists($attemptPath)) {
            $data = array(
                'userEmail' => $userEmail,
                'userDevice' => $_SERVER['HTTP_USER_AGENT'],
                'loginTimeout' => \time() + $this->lockoutTime,
                'loginAttempts' => 0,
            );

            // Save JSON file
            FileSystem::write($attemptPath, json_encode($data));
        } else {
            // Load existing attempt data
            $attemptData = FileSystem::read($attemptPath);
            $data = json_decode($attemptData, true);

            // Check if lockout time has expired
            $currentTime = \time();
            $lockoutTimeElapsed = $currentTime - $data['loginTimeout'];
            if ($lockoutTimeElapsed >= $this->lockoutTime) {
                $data['loginAttempts'] = 0; // Reset failed attempts
            }
        }

        // Increment failed attempts
        $data['loginAttempts']++;

        // Check if max login attempts reached
        if ($data['loginAttempts'] >= $this->maxLoginAttempts) {
            // Add lockout time
            $data['loginTimeout'] = \time() + ($data['loginAttempts'] - $this->maxLoginAttempts) * 600;
            exit;
        } else {
            $data['loginTimeout'] = \time(); // Update last failed attempt time
        }
    }
}
