<?php

namespace App\Services\PwApi\Core;

use App\Services\CacheService;

class PwApiService
{
    public $online;

    public $data = [];

    public $gamed;

    protected array $pwConfig;

    public function __construct()
    {
        $this->pwConfig = require '/var/www/html/cp/config/pw-api.php';
        $this->gamed = new Gamed;
        $this->online = $this->serverOnline();

        $version = $this->pwConfig['game_version'];

        if (file_exists(app_path('Services/protocols/pw_v'.$version.'.php'))) {
            require app_path('Services/protocols/pw_v'.$version.'.php');
            if (isset($PROTOCOL)) {
                $this->data = $PROTOCOL;
            }
        } else {
            throw new \Exception('Falha ao obter arquivo de versao '.$version);
        }
    }

    /**
     * Check if server is online using client port from config.
     * Uses config/pw-api.php ports.client (default 29000)
     * No caching — mirrors the original hrace009 API behavior for real-time status.
     */
    public function serverOnline(): bool
    {
        $host = $this->pwConfig['local'] ?? '127.0.0.1';
        $port = $this->pwConfig['ports']['client'] ?? 29000;

        $connection = @fsockopen($host, $port, $errCode, $errStr, 1);

        if ($connection) {
            fclose($connection);

            return true;
        }

        return false;
    }

    public function ports()
    {
        $host = $this->pwConfig['local'] ?? '127.0.0.1';
        $ports = [];
        $port_list = $this->pwConfig['ports'] ?? [];

        foreach ($port_list as $name => $port) {
            $ports[$name]['port'] = $port;
            $ports[$name]['open'] = $this->checkPort($host, $port);
        }

        return $ports;
    }

    /**
     * Check if a specific port is open.
     */
    private function checkPort(string $host, int $port): bool
    {
        $connection = @fsockopen($host, $port, $errCode, $errStr, 2);

        if ($connection) {
            fclose($connection);

            return true;
        }

        return false;
    }

    public function checkRoleOnline($roleId)
    {
        $onlineList = $this->getOnlineList();
        foreach ($onlineList as $user) {
            if (isset($user['roleid']) && $user['roleid'] == $roleId) {
                return true;
            }
        }

        return false;
    }

    /**
     * Get online player list from game server.
     * Cached for 30 seconds to prevent excessive socket iterations.
     */
    public function getOnlineList()
    {
        return CacheService::remember(
            CacheService::onlineKey('list'),
            CacheService::TTL_SHORT,
            function () {
                $online = [];
                $id = 0;
                $maxIterations = (int) ($this->pwConfig['max_online_list_iterations'] ?? 500);
                $emptyResponses = 0;
                $maxEmptyResponses = 3;
                $maxRetries = 3;

                while ($maxIterations-- > 0) {
                    try {
                        $pack = pack('N*', -1, 1, $id).$this->gamed->packString('1');
                        $pack = $this->gamed->createHeader(352, $pack);

                        for ($retry = 0; $retry < $maxRetries; $retry++) {
                            $send = $this->gamed->SendToDelivery($pack);
                            if ($send !== false && strlen($send) > 8) {
                                break;
                            }
                            if ($retry < $maxRetries - 1) {
                                usleep(200000);
                            }
                        }

                        if ($send === false || !is_string($send) || strlen($send) <= 8) {
                            $emptyResponses++;
                            if ($emptyResponses >= $maxEmptyResponses) {
                                break;
                            }
                            continue;
                        }

                        $data = $this->gamed->deleteHeader($send);
                        if (!is_string($data) || strlen($data) < 4) {
                            $emptyResponses++;
                            if ($emptyResponses >= $maxEmptyResponses) {
                                break;
                            }
                            continue;
                        }

                        $data = $this->gamed->unmarshal($data, $this->data['RoleList']);
                        $users = isset($data['users']) ? $data['users'] : [];
                        if (count($users) > 0) {
                            $online = array_merge($online, $users);
                            $id = $this->gamed->MaxOnlineUserID($users);
                            $emptyResponses = 0;
                        } else {
                            if (++$emptyResponses >= $maxEmptyResponses) {
                                break;
                            }
                        }
                    } catch (\Exception $e) {
                        $emptyResponses++;
                        if ($emptyResponses >= $maxEmptyResponses) {
                            break;
                        }
                    }
                }

                return $online;
            }
        );
    }

    public function kickRole(int $roleId): bool
    {
        // Use disconnectPlayer opcode (106) via gamedbd
        $pack = pack('N', $roleId);
        $pack = $this->gamed->createHeader(106, $pack);
        $this->gamed->SendToGamedBD($pack);

        return true;
    }

    public function kickRoleAndWait(int $roleId, int $maxWait = 5): bool
    {
        $this->kickRole($roleId);

        for ($i = 0; $i < $maxWait; $i++) {
            sleep(1);
            if (! $this->checkRoleOnline($roleId)) {
                return true;
            }
        }

        return false;
    }

    public function getRole($role)
    {
        if ($this->pwConfig['game_version'] == '07') {
            $user['base'] = $this->getRoleBase($role);
            $user['status'] = $this->getRoleStatus($role);
            $user['pocket'] = $this->getRoleInventory($role);
            $user['equipment'] = $this->getRoleEquipment($role);
            $user['storehouse'] = $this->getRoleStorehouse($role);
            $user['task'] = $this->getRoleTask($role);
        } else {
            $pack = pack('N*', -1, $role);
            $pack = $this->gamed->createHeader($this->data['code']['getRole'], $pack);
            $send = $this->gamed->SendToGamedBD($pack);
            $data = $this->gamed->deleteHeader($send);
            $user = $this->gamed->unmarshal($data, $this->data['role']);

            if (! is_array($user)) {
                $user['base'] = $this->getRoleBase($role);
                $user['status'] = $this->getRoleStatus($role);
                $user['pocket'] = $this->getRoleInventory($role);
                $user['equipment'] = $this->getRoleEquipment($role);
                $user['storehouse'] = $this->getRoleStorehouse($role);
                $user['task'] = $this->getRoleTask($role);
            }
        }

        return $user;
    }

    public function getRoleBase($role): ?array
    {
        $pack = pack('N*', -1, $role);
        $pack = $this->gamed->createHeader($this->data['code']['getRoleBase'], $pack);
        $send = $this->gamed->SendToGamedBD($pack);
        $data = $this->gamed->deleteHeader($send);

        $result = $this->gamed->unmarshal($data, $this->data['role']['base']);

        return is_array($result) ? $result : null;
    }

    public function getRoleStatus($role): ?array
    {
        $pack = pack('N*', -1, $role);
        $pack = $this->gamed->createHeader($this->data['code']['getRoleStatus'], $pack);
        $send = $this->gamed->SendToGamedBD($pack);
        $data = $this->gamed->deleteHeader($send);

        $result = $this->gamed->unmarshal($data, $this->data['role']['status']);

        return is_array($result) ? $result : null;
    }

    public function getRoleInventory($role)
    {
        $pack = pack('N*', -1, $role);
        $pack = $this->gamed->createHeader($this->data['code']['getRoleInventory'], $pack);
        $send = $this->gamed->SendToGamedBD($pack);
        $data = $this->gamed->deleteHeader($send);

        return $this->gamed->unmarshal($data, $this->data['role']['pocket']);
    }

    public function getRoleEquipment($role)
    {
        $pack = pack('N*', -1, $role);
        $pack = $this->gamed->createHeader($this->data['code']['getRoleEquipment'], $pack);
        $send = $this->gamed->SendToGamedBD($pack);
        $data = $this->gamed->deleteHeader($send);

        return $this->gamed->unmarshal($data, $this->data['role']['equipment']);
    }

    public function getRoleStorehouse($role)
    {
        $pack = pack('N*', -1, $role);
        $pack = $this->gamed->createHeader($this->data['code']['getRoleStoreHouse'], $pack);
        $send = $this->gamed->SendToGamedBD($pack);
        $data = $this->gamed->deleteHeader($send);

        return $this->gamed->unmarshal($data, $this->data['role']['storehouse']);
    }

    public function getRoleTask($role)
    {
        $pack = pack('N*', -1, $role);
        $pack = $this->gamed->createHeader($this->data['code']['getRoleTask'], $pack);
        $send = $this->gamed->SendToGamedBD($pack);
        $data = $this->gamed->deleteHeader($send);

        return $this->gamed->unmarshal($data, $this->data['role']['task']);
    }

    public function getRolePetBadge($role)
    {
        $pack = pack('N*', -1, $role);
        $pack = $this->gamed->createHeader(3088, $pack);
        $send = $this->gamed->SendToGamedBD($pack);
        $data = $this->gamed->deleteHeader($send);

        return $this->gamed->unmarshal($data, $this->data['role']['pocket']['petbadge']);
    }

    public function getUserCash($userid)
    {
        $pack = pack('N*', -1, $userid);
        $pack = $this->gamed->createHeader($this->data['code']['GetCashTotal'], $pack);
        $send = $this->gamed->SendToGamedBD($pack);
        $data = $this->gamed->deleteHeader($send);

        return $this->gamed->unmarshal($data, $this->data['getCashTotal']);
    }

    public function getRolePvp($var_data = '')
    {
        $result = [
            'pk_count' => 0,
            'dead_flag' => 0,
        ];
        if ($var_data !== '') {
            $data = $this->parseOctet($var_data, 'var_data');
            $result = [
                'pk_count' => $data['pk_count'],
                'dead_flag' => $data['dead_flag'],
            ];
        }

        return $result;
    }

    public function parseOctet($octet, $name)
    {
        $data = pack('H*', $octet);

        return $this->gamed->unmarshal($data, $this->data['octet'][$name]);
    }

    public function getRoles($user)
    {
        $pack = pack('N*', -1, $user);
        $pack = $this->gamed->createHeader($this->data['code']['getUserRoles'], $pack);
        $send = $this->gamed->SendToGamedBD($pack);
        $data = $this->gamed->deleteHeader($send);

        return $this->gamed->unmarshal($data, $this->data['user']['roles']);
    }

    public function getUser($id)
    {
        $pack = pack('N*', -1, $id, 1, 1);
        $data = $this->gamed->cuint($this->data['code']['getUser']).$this->gamed->cuint(strlen($pack)).$pack;
        $send = $this->gamed->SendToGamedBD($data);
        $strlarge = unpack('H', substr($send, 2, 1));
        if (substr($strlarge[1], 0, 1) == 8) {
            $tmp = 12;
        } else {
            $tmp = 11;
        }
        $send = substr($send, $tmp);
        $user = $this->gamed->unmarshal($send, $this->data['user']['info']);
        $user['login_ip'] = $this->gamed->getIp($this->gamed->reverseOctet(substr($user['login_record'], 8, 8)));
        $user['login_time'] = $this->gamed->getTime(substr($user['login_record'], 0, 8));

        return $user;
    }

    public function putRole($role, $params)
    {
        if (isset($params['equipment']['eqp']['id'])) {
            $tmp = $params['equipment']['eqp'];
            $params['equipment']['eqp'] = [];
            $params['equipment']['eqp'][] = $tmp;
        }
        if (isset($params['pocket']['inv']['id'])) {
            $tmp = $params['pocket']['inv'];
            $params['pocket']['inv'] = [];
            $params['pocket']['inv'][] = $tmp;
        }
        if (isset($params['storehouse']['store']['id'])) {
            $tmp = $params['storehouse']['store'];
            $params['storehouse']['store'] = [];
            $params['storehouse']['store'][] = $tmp;
        }
        if (isset($params['task']['task_inventory']['id'])) {
            $tmp = $params['task']['task_inventory'];
            $params['task']['task_inventory'] = [];
            $params['task']['task_inventory'][] = $tmp;
        }
        if (isset($params['storehouse']['dress']['id'])) {
            $tmp = $params['storehouse']['dress'];
            $params['storehouse']['dress'] = [];
            $params['storehouse']['dress'][] = $tmp;
        }
        if (isset($params['storehouse']['material']['id'])) {
            $tmp = $params['storehouse']['material'];
            $params['storehouse']['material'] = [];
            $params['storehouse']['material'][] = $tmp;
        }
        if ($this->pwConfig['game_version'] != '07') {
            $pack = pack('NNC*', -1, $role, 1).$this->gamed->marshal($params, $this->data['role']);

            return $this->gamed->SendToGamedBD($this->gamed->createHeader($this->data['code']['putRole'], $pack));
        } else {
            $pack = pack('NNC*', -1, $role).$this->gamed->marshal($params['base'], $this->data['role']['base']);
            $this->gamed->SendToGamedBD($this->gamed->createHeader($this->data['code']['putRoleBase'], $pack));
            $pack = pack('NNC*', -1, $role).$this->gamed->marshal($params['status'], $this->data['role']['status']);
            $this->gamed->SendToGamedBD($this->gamed->createHeader($this->data['code']['putRoleStatus'], $pack));
            $pack = pack('NNC*', -1, $role).$this->gamed->marshal($params['pocket'], $this->data['role']['pocket']);
            $this->gamed->SendToGamedBD($this->gamed->createHeader($this->data['code']['putRoleInventory'], $pack));
            $pack = pack('NNC*', -1, $role).$this->gamed->marshal($params['equipment'], $this->data['role']['equipment']);
            $this->gamed->SendToGamedBD($this->gamed->createHeader($this->data['code']['putRoleEquipment'], $pack));
            $pack = pack('NNC*', -1, $role).$this->gamed->marshal($params['storehouse'], $this->data['role']['storehouse']);
            $this->gamed->SendToGamedBD($this->gamed->createHeader($this->data['code']['putRoleStoreHouse'], $pack));
            $pack = pack('NNC*', -1, $role).$this->gamed->marshal($params['task'], $this->data['role']['task']);

            return $this->gamed->SendToGamedBD($this->gamed->createHeader($this->data['code']['putRoleTask'], $pack));
        }
    }

    public function sendMail($receiver, $title, $context, $money, $item = [])
    {
        if ($item === []) {
            $item = [
                'id' => 0,
                'pos' => 0,
                'count' => 0,
                'max_count' => 0,
                'data' => '',
                'proctype' => 0,
                'expire_date' => 0,
                'guid1' => 0,
                'guid2' => 0,
                'mask' => 0,
            ];
        }

        $pack = pack('NNCN', 344, 1025, 3, $receiver).$this->gamed->packString($title).$this->gamed->packString($context);
        $pack .= $this->gamed->marshal($item, $this->data['role']['pocket']['inv']);
        $pack .= pack('N', $money);

        return $this->gamed->SendToDelivery($this->gamed->createHeader($this->data['code']['sendMail'], $pack));
    }

    public function territoryWar($param)
    {
        $pack = pack('N*', 1).pack('N', 1).$this->gamed->packString2($param);

        return $this->gamed->SendToProvider($this->gamed->createHeader($this->data['code']['territoryWar'], $pack));
    }

    public function worldChat($role, $msg, $channel)
    {
        $pack = pack('CCN', $channel, 0, $role).$this->gamed->packString($msg).$this->gamed->packOctet('');

        return $this->gamed->SendToProvider($this->gamed->createHeader($this->data['code']['worldChat'], $pack));
    }

    public function forbidAcc($role, $time, $reason)
    {
        $pack = pack('N*', -1, 0, $role, $time).$this->gamed->packString($reason);

        return $this->gamed->SendToDelivery($this->gamed->createHeader($this->data['code']['forbidAcc'], $pack));
    }

    public function forbidRole($role, $time, $reason)
    {
        $pack = pack('N*', -1, 0, $role, $time).$this->gamed->packString($reason);

        return $this->gamed->SendToDelivery($this->gamed->createHeader($this->data['code']['forbidRole'], $pack));
    }

    public function muteAcc($role, $time, $reason)
    {
        $pack = pack('N*', -1, 0, $role, $time).$this->gamed->packString($reason);

        return $this->gamed->SendToDelivery($this->gamed->createHeader($this->data['code']['muteAcc'], $pack));
    }

    public function muteRole($role, $time, $reason)
    {
        $pack = pack('N*', -1, 0, $role, $time).$this->gamed->packString($reason);

        return $this->gamed->SendToDelivery($this->gamed->createHeader($this->data['code']['muteRole'], $pack));
    }

    public function getRoleid($rolename)
    {
        $pack = pack('N', -1).$this->gamed->packString($rolename).pack('C', 1);
        $data = $this->gamed->deleteHeader($this->gamed->SendToGamedBD($this->gamed->createHeader($this->data['code']['getRoleid'], $pack)));
        $var = unpack('l', $data);
        if ($var[1] !== -1) {
            $var = unpack('N', $data);
        }

        return $var[1];
    }

    public function renameRole($role, $oldname, $newname)
    {
        $pack = pack('N*', -1, $role).$this->gamed->packString($oldname).$this->gamed->packString($newname);

        return $this->gamed->SendToGamedBD($this->gamed->createHeader($this->data['code']['renameRole'], $pack));
    }

    public function getRoleFriends($role)
    {
        $tmp = 0;
        $pack = pack('N*', -1, $role);
        $data = $this->gamed->SendToGamedBD($this->gamed->createHeader($this->data['code']['getRoleFriend'], $pack));
        $this->gamed->unpackCuint($data, $tmp);
        $this->gamed->unpackCuint($data, $tmp);
        $data = substr($data, $tmp + 5);

        return $this->gamed->unmarshal($data, $this->data['FriendList']);
    }

    public function addFaction($roleid, $name, $fid)
    {
        $pack = pack('N*', -1).$this->gamed->packString($name).pack('NN', $roleid, $fid);
        $pack = $this->gamed->createHeader($this->data['code']['AddFaction'], $pack);

        return $this->gamed->SendToGamedBD($pack);
    }

    public function delFaction($fid)
    {
        $pack = pack('N*', -1, $fid);
        $pack = $this->gamed->createHeader($this->data['code']['DelFaction'], $pack);

        return $this->gamed->SendToGamedBD($pack);
    }

    public function upgradeFaction($roleid, $fid, $level)
    {
        $pack = pack('N*', -1, $fid, $roleid, 0).pack('C', $level);
        $pack = $this->gamed->createHeader($this->data['code']['FactionUpgrade'], $pack);

        return $this->gamed->SendToGamedBD($pack);
    }

    public function getFactionInfo($id)
    {
        $pack = pack('N*', -1, $id);
        $pack = $this->gamed->createHeader($this->data['code']['getFactionInfo'], $pack);
        $send = $this->gamed->SendToGamedBD($pack);
        $data = $this->gamed->deleteHeader($send);

        return $this->gamed->unmarshal($data, $this->data['FactionInfo']);
    }

    public function getFactionDetail($id)
    {
        $pack = pack('N*', -1, $id);
        $pack = $this->gamed->createHeader($this->data['code']['getFactionDetail'], $pack);
        $send = $this->gamed->SendToGamedBD($pack);
        $data = $this->gamed->deleteHeader($send);

        return $this->gamed->unmarshal($data, $this->data['FactionDetail']);
    }

    public function getTerritories()
    {
        $pack = pack('N*', -1, 1);
        $data = $this->gamed->SendToGamedBD($this->gamed->createHeader($this->data['code']['getTerritory'], $pack));
        $length = 0;
        $this->gamed->unpackCuint($data, $length);
        $this->gamed->unpackCuint($data, $length);
        $length += 6;
        $data = substr($data, $length);

        return $this->gamed->unmarshal($data, $this->data['GTerritoryDetail']);
    }

    public function getRaw($table, $handler = '', $key = '')
    {
        $pack = pack('N*', -1).$this->gamed->packLongOctet($table).$this->gamed->packOctet($handler).$this->gamed->packOctet($key);
        $data = $this->gamed->deleteHeader($this->gamed->SendToGamedBD($this->gamed->createHeader(3055, $pack)));

        return $this->gamed->unmarshal($data, $this->data['RawRead']);
    }

    public function getUserFaction($id)
    {
        $tmp = 0;
        $pack = pack('N*', -1, 1, $id);
        $data = $this->gamed->SendToGamedBD($this->gamed->createHeader($this->data['code']['getUserFaction'], $pack));
        $this->gamed->unpackCuint($data, $tmp);
        $this->gamed->unpackCuint($data, $tmp);
        $data = substr($data, $tmp + 8);

        return $this->gamed->unmarshal($data, $this->data['getUserFaction']);
    }

    public function generateSkill($params = [])
    {
        $skills = substr($params['skills'], 8);
        $id = isset($params['id']) ? dechex($params['id']) : 1;
        $level = isset($params['level']) ? dechex($params['level']) : 1;
        $progress = isset($params['progress']) ? dechex($params['progress']) : 0;
        $skills .= $this->gamed->reverseOctet($this->gamed->hex2octet($id));
        $skills .= $this->gamed->reverseOctet($this->gamed->hex2octet($progress));
        $skills .= $this->gamed->reverseOctet($this->gamed->hex2octet($level));
        $count = dechex(strlen($skills) / 24);
        $skills = $this->gamed->reverseOctet($this->gamed->hex2octet($count)).$skills;

        return $skills;
    }
}
