<?php

namespace App\Classes\MikrotikService;

use App\Models\Client;
use App\Models\CompanyInformation;
use App\Models\Nas;
use App\Models\Pop;
use Carbon\Carbon;
use Exception;

class SyncWithMk
{
    private $time;
    function __construct()
    {
        $this->time = collect(json_decode(siteinfo()->settings))->where('type', 'expire_time')->first()->value ?? '00:00:00';
    }

    public function mikrotikProvider(Nas $nas, $staticIp = false)
    {
        $mkIp = $nas->nasname;
        $mkUser = $nas->mikrotick_user;
        $mkPass = $nas->mikrotick_user_password;
        $mkPort = $nas->mikrotick_port;

        if ($staticIp) {
            return new MikrotikStaticIP($mkIp, $mkUser, $mkPass, $mkPort ? (int)$mkPort : 8728);
        }

        return new Mikrotik($mkIp, $mkUser, $mkPass, $mkPort ? (int)$mkPort : 8728);
    }

    public function isExpire($client)
    {
        $timeComponents = explode(':', $this->time);
        $expireDateTime = Carbon::parse($client->expire_date)
            ->addDays($client->payment_dadeline);

        if (globalPermission('expire_before_expire_date')) {
            if ($this->time != "00:00:00") $expireDateTime->setTime($timeComponents[0], $timeComponents[1], $timeComponents[2]);
        } else {
            if ($this->time != "00:00:00") $expireDateTime->setTime($timeComponents[0], $timeComponents[1], $timeComponents[2])->addDay();
        }

        return $expireDateTime < Carbon::now();
    }

    public function isDisable($client)
    {
        if ($client->clients_status == "active") {
            return false;
        } elseif ($client->clients_status == "expired") {
            if ($client->pop->experity_check == "Yes") {
                return false;
            } elseif ($client->experity_check == "No") {
                return false;
            } else {
                return $this->isExpire($client);
            }
        } else {
            return true;
        }
    }


    public function isInformationDiffer($mkClientInfo, $newClientInfo, $key)
    {
        $isKeyEmptyOrNull = (isset($mkClientInfo[$key]) == false || $mkClientInfo[$key] == '');

        if ($isKeyEmptyOrNull xor $newClientInfo[$key] == null) return true;
        else if (
            !$isKeyEmptyOrNull && isset($newClientInfo[$key]) &&
            $mkClientInfo[$key] != $newClientInfo[$key]
        ) return true;
    }

    public function isClientInformationDiffer($mkClientInfo, $newClientInfo)
    {
        $differenceCheck = ($mkClientInfo["disabled"] == 'false' xor $newClientInfo["disabled"] == 'no')
            || $mkClientInfo["password"] !== $newClientInfo["password"]
            || $mkClientInfo["service"] !== $newClientInfo["service"]
            || $mkClientInfo["profile"] !== $newClientInfo["profile"]
            || $this->isInformationDiffer($mkClientInfo, $newClientInfo, "comment")
            || (globalPermission("static-ip-address") && $this->isInformationDiffer($mkClientInfo, $newClientInfo, "remote-address"))
            || (globalPermission("client-mac-binding") && $this->isInformationDiffer($mkClientInfo, $newClientInfo, "caller-id"));

        return $differenceCheck;
    }


    public function syncClientHandler($client, Mikrotik $mikrotik, $mkSecret)
    {
        $disableStatus = $this->isDisable($client) &&  $this->isDisable(Client::with("pop")->find($client->id));

        if ($disableStatus && globalPermission("show-message-to-expire-customer") && $client->isStatic == false) {
            $this->expireProfileChange($mikrotik, $client, $mkSecret != null);

            if ($disableStatus) {
                try {
                    $mikrotik->disconnectSecret($client->userid);
                } catch (\Exception $err) {
                }
            }
            return;
        }


        // working with static ip
        if ($client->isStatic) {
            $clientInterface = null;
            foreach (json_decode($client->pop->nas->ip_block) as $array) {
                if ((int)$array->id === (int)$client->ethernet_port) {
                    $clientInterface = $array->port;
                    break;
                }
            }

            $mkStatic = $this->mikrotikProvider($client->pop->nas, true);
            $uploadSpeed = (int)$client->packages->speed_up / 8;
            $downloadSpeed = (int)$client->packages->speed_down / 8;
            $mkStatic->createStaticIP([
                "ip_address" => $client->ip_address,
                "max-limit" => "{$uploadSpeed}M/{$downloadSpeed}M",
                "action" => $disableStatus ? "reject" : "accept",
                "interface" => $clientInterface,
                "mac_address" => $client->mac,
                "comment" => str_replace("\n", " ", $client->clientsinfo->remarks)
            ]);

            return;
        }


        $userData = [
            "username" => $client->userid,
            "password" => $client->password,
            "service" => "pppoe",
            "profile" => $client->packages->profile_name,
            "comment" => str_replace("\n", " ", $client->clientsinfo->remarks),
            "disabled" =>  $disableStatus ? "yes" : "no",
            "remote-address" => $client->ip_address,
            "caller-id" => $client->mac
        ];

        $isOnline = (function () use ($mikrotik, $client) {
            try {
                return $mikrotik->getActiveConnection($client->userid);
            } catch (\Throwable $th) {
                return null;
            }
        })();


        if (($mkSecret && $this->isClientInformationDiffer($mkSecret, $userData)) || ($isOnline && $disableStatus)) {
            $mikrotik->updateSecret($client->userid, $userData);

            if ($disableStatus) {
                try {
                    $mikrotik->disconnectSecret($client->userid);
                } catch (\Exception $err) {
                }
            }
        } else if ($mkSecret == null) {
            $mikrotik->createSecret($userData);
        }
    }

    public function syncSingleClient($clientId)
    {
        if (!checkAPI()) return;

        $client = Client::with("packages", "pop.nas", "clientsinfo")->find($clientId);
        if ($client->client_approval != "approved") {
            return;
        }

        $mk = $this->mikrotikProvider($client->pop->nas);
        $mkSecret = $mk->getUserOrNull($client->userid);
        return $this->syncClientHandler($client, $mk, $mkSecret);
    }

    public function syncSinglePop($popId)
    {
        if (!checkAPI()) return;

        $clients = Client::with("packages")->where("pop_id", $popId)->get();
        $pop = Pop::with("nas")->find($popId);

        $mk = $this->mikrotikProvider($pop->nas);

        $mkSecrets = $mk->getSecret();
        $userMap = [];
        foreach ($mkSecrets as $user) $userMap[$user["name"]] = $user;

        foreach ($clients as $user) {
            if ($user->client_approval != "approved") {
                continue;
            }

            try {
                $checkIfExist = array_key_exists($user->userid, $userMap);

                $this->syncClientHandler($user, $mk, $checkIfExist ? $userMap[$user->userid] : null);
            } catch (Exception $err) {
                continue;
            }
        }
    }

    public function syncAllSecretsToMk()
    {
        if (!checkAPI()) return;

        $clients = Client::with("pop.nas", "pop", "packages")->get();
        $clientsWithPop = $clients->groupBy("pop_id");

        foreach ($clientsWithPop as $pop_id => $pop_users) {

            $pop = $pop_users->where('pop_id', $pop_id)->first()->pop;
            $nas = $pop->nas;

            try {
                $mk = $this->mikrotikProvider($nas);
            } catch (Exception $err) {
                continue;
            }

            $mkSecrets = $mk->getSecret();
            $userMap = [];
            foreach ($mkSecrets as $user) $userMap[$user["name"]] = $user;

            foreach ($pop_users as $user) {
                if ($user->client_approval != "approved") {
                    continue;
                }
                try {
                    $checkIfExist = array_key_exists($user->userid, $userMap);
                    $this->syncClientHandler($user, $mk, $checkIfExist ? $userMap[$user->userid] : null);
                } catch (Exception $err) {
                    continue;
                }
            }
        }
    }

    public function expireProfileChange(Mikrotik $mikrotik, $client, bool $exist)
    {
        $expireProfileName = cache()->remember('expire_profile', 60 * 60, function () {
            $companyInfo = CompanyInformation::latest()->first();
            return $companyInfo->expire_profile;
        });

        $userData = [
            "username" => $client->userid,
            "password" => $client->password,
            "service" => "pppoe",
            "profile" => $expireProfileName,
            "disabled" => "no",
            "remote-address" => "",
            "caller-id" => $client->mac
        ];

        if ($exist) {
            $mikrotik->updateSecret($client->userid, $userData);
        } else {
            $mikrotik->createSecret($userData);
        }
    }
}
