<?php

class LibreAuth
{
    public $name;
    public $ownerid;
    public $version;
    public $url;
    public $sessionid;
    public $enckey;
    private $nonce;
    public $hash;

    public function __construct($name, $ownerid, $version, $url)
    {
        $this->name = $name;
        $this->ownerid = $ownerid;
        $this->version = $version;
        $this->url = rtrim($url, '/') . '/';
    }

    public function init($enckey = null)
    {
        if ($enckey === null) {
            $enckey = substr(bin2hex(random_bytes(18)), 0, 35);
        }
        $this->enckey = substr($enckey, 0, 35);

        $res = $this->req([
            'type' => 'init',
            'ver' => $this->version,
            'enckey' => $this->enckey,
            'hash' => $this->hash ?? '',
        ]);
        if (!$res['success']) die($res['message']);
        $this->sessionid = $res['sessionid'];
        return $res;
    }

    public function loginEmail($email, $password, $hwid = '', $code = null)
    {
        $data = [
            'type' => 'loginEmail',
            'email' => $email,
            'pass' => $password,
            'hwid' => $hwid,
        ];
        if ($code) $data['code'] = $code;
        return $this->auth($data);
    }

    public function login($username, $password, $hwid = '', $code = null)
    {
        $data = [
            'type' => 'login',
            'username' => $username,
            'pass' => $password,
            'hwid' => $hwid,
        ];
        if ($code) $data['code'] = $code;
        return $this->auth($data);
    }

    public function register($username, $password, $key, $hwid = '', $email = '')
    {
        $data = [
            'type' => 'register',
            'username' => $username,
            'pass' => $password,
            'key' => $key,
            'hwid' => $hwid,
        ];
        if ($email) $data['email'] = $email;
        return $this->auth($data);
    }

    public function license($key, $hwid = '', $code = null)
    {
        $data = ['type' => 'license', 'key' => $key, 'hwid' => $hwid];
        if ($code) $data['code'] = $code;
        return $this->auth($data);
    }

    public function upgrade($username, $key)
    {
        $res = $this->req(['type' => 'upgrade', 'username' => $username, 'key' => $key]);
        return $res['success'];
    }

    public function forgot($username, $email)
    {
        $res = $this->req(['type' => 'forgot', 'username' => $username, 'email' => $email]);
        return $res['success'];
    }

    public function logout()
    {
        $this->req(['type' => 'logout']);
        $this->sessionid = null;
    }

    public function check()
    {
        $res = $this->req(['type' => 'check']);
        return $res['success'];
    }

    public function ban($reason = '')
    {
        $res = $this->req(['type' => 'ban', 'reason' => $reason]);
        return $res['success'];
    }

    public function var($varid)
    {
        $res = $this->req(['type' => 'var', 'varid' => $varid]);
        return $res['success'] ? $res['message'] : null;
    }

    public function getvar($var)
    {
        $res = $this->req(['type' => 'getvar', 'var' => $var]);
        return $res['success'] ? $res['response'] : null;
    }

    public function setvar($var, $data)
    {
        $this->req(['type' => 'setvar', 'var' => $var, 'data' => $data]);
    }

    public function log($message, $pcuser = '')
    {
        $this->req(['type' => 'log', 'message' => $message, 'pcuser' => $pcuser]);
    }

    public function webhook($webid, $params, $body = '', $conttype = '')
    {
        $res = $this->req([
            'type' => 'webhook',
            'webid' => $webid,
            'params' => $params,
            'body' => $body,
            'conttype' => $conttype,
        ]);
        return $res['success'] ? $res['response'] : null;
    }

    public function file($fileid)
    {
        $res = $this->req(['type' => 'file', 'fileid' => $fileid]);
        if (!$res['success']) return null;
        return hex2bin($res['contents']);
    }

    public function fetchOnline()
    {
        $res = $this->req(['type' => 'fetchOnline']);
        return $res['success'] ? $res['users'] : null;
    }

    public function fetchStats()
    {
        return $this->req(['type' => 'fetchStats']);
    }

    public function checkblack()
    {
        $res = $this->req(['type' => 'checkblacklist', 'hwid' => '']);
        return $res['success'];
    }

    public function chatget($channel)
    {
        $res = $this->req(['type' => 'chatget', 'channel' => $channel]);
        return $res['success'] ? $res['messages'] : null;
    }

    public function chatsend($message, $channel)
    {
        $res = $this->req(['type' => 'chatsend', 'message' => $message, 'channel' => $channel]);
        return $res['success'];
    }

    public function changeUsername($newUsername)
    {
        $res = $this->req(['type' => 'changeUsername', 'newUsername' => $newUsername]);
        return $res['success'];
    }

    public function enable2fa($code = null)
    {
        $data = ['type' => '2faenable'];
        if ($code) $data['code'] = $code;
        return $this->req($data);
    }

    public function disable2fa($code)
    {
        return $this->req(['type' => '2fadisable', 'code' => $code]);
    }

    private function auth($data)
    {
        $res = $this->req($data);
        if (!$res['success']) return false;
        return $res['info'];
    }

    private function req($data)
    {
        $data['name'] = $this->name;
        $data['ownerid'] = $this->ownerid;
        if ($this->sessionid) $data['sessionid'] = $this->sessionid;
        if ($this->nonce) $data['nonce'] = $this->nonce;

        $ch = curl_init($this->url);
        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => http_build_query($data),
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HEADER => true,
            CURLOPT_TIMEOUT => 20,
            CURLOPT_USERAGENT => 'LibreAuth',
        ]);

        $raw = curl_exec($ch);
        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $hsize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
        curl_close($ch);

        if ($code === 429) die('Rate limited');

        $body = substr($raw, $hsize);
        if ($body === 'LibreAuth_Invalid') die('LibreAuth_Invalid');

        $headers = [];
        foreach (explode("\r\n", substr($raw, 0, $hsize)) as $line) {
            $p = explode(': ', $line, 2);
            if (isset($p[1])) $headers[strtolower($p[0])] = trim($p[1]);
        }

        $this->verifySig($body, $headers, $data['type']);

        if ($this->enckey && $this->looksEnc($body)) {
            $body = $this->decryptBody($body, $this->enckey);
        }

        $json = json_decode($body, true) ?: ['success' => false, 'message' => 'Bad response'];
        if (isset($json['ownerid']) && $json['ownerid'] !== $this->ownerid) {
            die('Application mismatch');
        }
        if (!empty($json['nonce'])) $this->nonce = $json['nonce'];
        return $json;
    }

    private function looksEnc($body)
    {
        if ($body === '' || ($body[0] ?? '') === '{') return false;
        return base64_decode($body, true) !== false;
    }

    private function decryptBody($blob, $enckey)
    {
        $raw = base64_decode($blob, true);
        if ($raw === false || strlen($raw) < 17) return $blob;
        $iv = substr($raw, 0, 16);
        $cipher = substr($raw, 16);
        $key = hash('sha256', $enckey, true);
        $out = openssl_decrypt($cipher, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
        return $out !== false ? $out : $blob;
    }

    private function verifySig($body, $headers, $type)
    {
        $skip = ['log', 'file', '2faenable', '2fadisable'];
        if (in_array($type, $skip, true)) return;

        global \$LibreAuthPublicKey;
        if (empty(\$LibreAuthPublicKey) || !extension_loaded('sodium')) return;

        $ts = $headers['x-signature-timestamp'] ?? null;
        $sig = $headers['x-signature-ed25519'] ?? null;
        if (!$ts || !$sig) return;

        if (abs(time() - (int) $ts) > 300) return;

        try {
            $ok = sodium_crypto_sign_verify_detached(
                sodium_hex2bin($sig),
                $ts . $body,
                sodium_hex2bin(\$LibreAuthPublicKey)
            );
            if (!$ok) die('Signature verification failed');
        } catch (Throwable $e) {
            die('Signature verification failed');
        }
    }
}
