<?php

namespace App\Services\PwApi\Server;

class ServerService extends \App\Services\PwApi\Core\PwApiService
{
    public function gmRestartServer($restart_time = 100, $gsid = 1)
    {
        $localsid = 0;
        $gmroleid = 0;

        $pack = pack('N*', $localsid, $gmroleid, $gsid, $restart_time);
        $pack = $this->gamed->createHeader($this->data['code']['gmRestartServer'], $pack);

        $send = $this->gamed->SendToDelivery($pack);

        if ($send === false) {
            return ['success' => false, 'message' => 'Failed to connect to gdeliveryd'];
        }

        $data = $this->gamed->deleteHeader($send);

        if (! $data || strlen($data) < 4) {
            return [
                'success' => true,
                'message' => 'Restart command sent',
                'gsid' => $gsid,
                'restart_time' => $restart_time,
            ];
        }

        $result = unpack('N', substr($data, 0, 4));
        $retcode = $result[1] ?? -1;

        return [
            'success' => $retcode == 0,
            'retcode' => $retcode,
            'message' => $retcode == 0 ? 'Restart command accepted' : 'Restart command failed',
            'gsid' => $gsid,
            'restart_time' => $restart_time,
        ];
    }

    public function gmControlGame($command, $worldtag = -1, $xid = 0)
    {
        $opcode = $this->data['code']['gmControlGame'] ?? 380;

        if ($xid == 0) {
            $xid = time() % 100000;
        }

        $pack = pack('NN', $xid, $worldtag).$this->gamed->packString($command);
        $data = $this->gamed->createHeader($opcode, $pack);

        $response = $this->gamed->SendToDelivery($data);

        if (! $response) {
            return [
                'success' => false,
                'message' => 'No response from server',
                'command' => $command,
            ];
        }

        $pos = 0;
        $respOpcode = $this->gamed->unpackCuint($response, $pos);
        $respLen = $this->gamed->unpackCuint($response, $pos);
        $respData = substr($response, $pos);

        if ($respOpcode != 381) {
            return [
                'success' => false,
                'message' => 'Unexpected response opcode: '.$respOpcode,
                'command' => $command,
            ];
        }

        $result = [
            'success' => false,
            'command' => $command,
            'xid' => 0,
            'retcode' => -1,
            'gs_results' => [],
        ];

        if (strlen($respData) >= 8) {
            $result['xid'] = unpack('N', substr($respData, 0, 4))[1];
            $result['retcode'] = unpack('N', substr($respData, 4, 4))[1];

            if ($result['retcode'] > 2147483647) {
                $result['retcode'] = $result['retcode'] - 4294967296;
            }

            $result['success'] = ($result['retcode'] == 0);

            if (strlen($respData) > 8) {
                $vectorPos = 8;
                $vectorData = substr($respData, 8);
                $vectorCount = $this->gamed->unpackCuint($vectorData, $tmp);
                $vectorData = substr($vectorData, $tmp);

                for ($i = 0; $i < $vectorCount && strlen($vectorData) >= 8; $i++) {
                    $gsid = unpack('N', substr($vectorData, 0, 4))[1];
                    $gsRetcode = unpack('N', substr($vectorData, 4, 4))[1];
                    if ($gsRetcode > 2147483647) {
                        $gsRetcode = $gsRetcode - 4294967296;
                    }
                    $result['gs_results'][] = [
                        'gsid' => $gsid,
                        'retcode' => $gsRetcode,
                    ];
                    $vectorData = substr($vectorData, 8);
                }
            }

            $result['message'] = $result['success']
                ? 'Command executed successfully'
                : 'Command failed (retcode: '.$result['retcode'].')';
        }

        return $result;
    }

    public function setExpRateCmd($multiplier = 2)
    {
        return $this->gmControlGame('exp '.intval($multiplier));
    }

    public function setSpRateCmd($multiplier = 2)
    {
        return $this->gmControlGame('sp '.intval($multiplier));
    }

    public function setDropRateCmd($multiplier = 2)
    {
        return $this->gmControlGame('drop '.intval($multiplier));
    }

    public function setMoneyRateCmd($multiplier = 2)
    {
        return $this->gmControlGame('money '.intval($multiplier));
    }

    public function getGameAttri($attribute = 1, $gmRoleId = 0, $localSid = 0)
    {
        $opcode = $this->data['code']['gmGetGameAttri'] ?? 376;

        $pack = pack('NNC', $gmRoleId, $localSid, $attribute);
        $pack = $this->gamed->createHeader($opcode, $pack);

        $response = $this->gamed->SendToDelivery($pack);

        if (! $response) {
            return [
                'success' => false,
                'message' => 'No response from server',
                'attribute' => $attribute,
            ];
        }

        $data = $this->gamed->deleteHeader($response);

        if (! $data || strlen($data) < 1) {
            return [
                'success' => false,
                'message' => 'Empty response from server',
                'attribute' => $attribute,
            ];
        }

        $tmp = 0;
        $valueHex = $this->gamed->unpackOctet($data, $tmp);

        $value = 0;
        if (! empty($valueHex)) {
            if (strlen($valueHex) <= 8) {
                $value = hexdec($valueHex);
            }
        }

        return [
            'success' => true,
            'attribute' => $attribute,
            'value' => $value,
            'raw_value' => $valueHex,
            'message' => 'Attribute retrieved successfully',
        ];
    }

    public function setGameAttri($attribute = 1, $value = 1, $gmRoleId = 0, $localSid = 0)
    {
        $opcode = $this->data['code']['gmSetGameAttri'] ?? 377;

        if (is_int($value)) {
            if ($value >= 0 && $value <= 255) {
                $valueOctets = $this->gamed->packOctet(sprintf('%02x', $value));
            } else {
                $valueOctets = $this->gamed->packOctet(sprintf('%08x', $value));
            }
        } elseif (is_string($value) && ctype_xdigit($value)) {
            $valueOctets = $this->gamed->packOctet($value);
        } else {
            return [
                'success' => false,
                'message' => 'Invalid value format. Use int or hex string.',
                'attribute' => $attribute,
            ];
        }

        $pack = pack('NNC', $gmRoleId, $localSid, $attribute).$valueOctets;
        $pack = $this->gamed->createHeader($opcode, $pack);

        $response = $this->gamed->SendToDelivery($pack);

        if (! $response) {
            return [
                'success' => false,
                'message' => 'No response from server',
                'attribute' => $attribute,
            ];
        }

        $data = $this->gamed->deleteHeader($response);

        $retcode = -1;
        if ($data && strlen($data) >= 4) {
            $result = unpack('N', substr($data, 0, 4));
            $retcode = $result[1] ?? -1;

            if ($retcode > 2147483647) {
                $retcode = $retcode - 4294967296;
            }
        }

        return [
            'success' => ($retcode == 0),
            'retcode' => $retcode,
            'attribute' => $attribute,
            'value' => $value,
            'message' => ($retcode == 0) ? 'Attribute set successfully' : 'Failed to set attribute (retcode: '.$retcode.')',
        ];
    }

    public function setExpRateParam($value = 20)
    {
        $opcode = $this->data['code']['gmSetGameAttri'] ?? 377;
        $attribute = 204;

        $valueOctets = $this->gamed->cuint(1).pack('C', $value);
        $pack = pack('N', -1).pack('NNC', 0, 0, $attribute).$valueOctets;
        $data = $this->gamed->createHeader($opcode, $pack);

        $host = config('pw-api.local');
        $port = config('pw-api.ports.gdeliveryd');

        $sock = @socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        if (! $sock) {
            return [
                'success' => false,
                'message' => 'Failed to create socket',
                'value' => $value,
            ];
        }

        socket_set_option($sock, SOL_SOCKET, SO_RCVTIMEO, ['sec' => 2, 'usec' => 0]);
        socket_set_option($sock, SOL_SOCKET, SO_SNDTIMEO, ['sec' => 2, 'usec' => 0]);

        if (! @socket_connect($sock, $host, $port)) {
            socket_close($sock);

            return [
                'success' => false,
                'message' => 'Failed to connect to gdeliveryd',
                'value' => $value,
            ];
        }

        @socket_recv($sock, $greeting, 8192, 0);

        socket_send($sock, $data, strlen($data), 0);

        socket_close($sock);

        return [
            'success' => true,
            'message' => 'EXP rate set to '.$value.' ('.($value / 10).'x)',
            'requested_value' => $value,
            'multiplier' => $value / 10,
        ];
    }

    public function getExpRateParam()
    {
        $opcode = $this->data['code']['gmGetGameAttri'] ?? 376;
        $attribute = 204;

        $pack = pack('NNC', 0, 0, $attribute);
        $data = $this->gamed->createHeader($opcode, $pack);

        $response = $this->gamed->SendToDelivery($data);

        if (! $response) {
            return [
                'success' => false,
                'message' => 'No response from server',
            ];
        }

        $pos = 0;
        $respOpcode = $this->gamed->unpackCuint($response, $pos);
        $respLen = $this->gamed->unpackCuint($response, $pos);
        $respData = substr($response, $pos);

        if ($respOpcode == 132 && strlen($respData) >= 9) {
            $expRate = unpack('C', substr($respData, 8, 1))[1];

            return [
                'success' => true,
                'exp_rate' => $expRate,
                'multiplier' => $expRate / 10,
                'message' => 'Current EXP rate: '.$expRate.' ('.($expRate / 10).'x)',
            ];
        }

        return [
            'success' => false,
            'message' => 'Could not parse response',
        ];
    }

    public function enableDoubleExp()
    {
        return $this->setExpRateParam(20);
    }

    public function enableTripleExp()
    {
        return $this->setExpRateParam(30);
    }

    public function disableDoubleExp()
    {
        return $this->setExpRateParam(10);
    }

    public function getExpRate()
    {
        return $this->getExpRateParam();
    }

    const GAME_ATTRIBUTES = [
        'double_exp' => ['id' => 204, 'name' => 'EXP Rate', 'type' => 'rate'],
        'lambda' => ['id' => 205, 'name' => 'Lambda', 'type' => 'rate'],
        'no_trade' => ['id' => 207, 'name' => 'No Trade', 'type' => 'toggle'],
        'no_auction' => ['id' => 208, 'name' => 'No Auction', 'type' => 'toggle'],
        'no_mail' => ['id' => 209, 'name' => 'No Mail', 'type' => 'toggle'],
        'no_faction' => ['id' => 210, 'name' => 'No Faction', 'type' => 'toggle'],
        'double_money' => ['id' => 211, 'name' => 'Money Rate', 'type' => 'rate'],
        'double_drop' => ['id' => 212, 'name' => 'Drop Rate', 'type' => 'rate'],
        'double_sp' => ['id' => 213, 'name' => 'SP Rate', 'type' => 'rate'],
        'no_sell_point' => ['id' => 214, 'name' => 'No Sell Point', 'type' => 'toggle'],
    ];

    public function setGameAttribute($attribute, $value)
    {
        $opcode = $this->data['code']['gmSetGameAttri'] ?? 377;

        $valueOctets = $this->gamed->cuint(1).pack('C', $value);
        $pack = pack('N', -1).pack('NNC', 0, 0, $attribute).$valueOctets;
        $data = $this->gamed->createHeader($opcode, $pack);

        $host = config('pw-api.local');
        $port = config('pw-api.ports.gdeliveryd');

        $sock = @socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        if (! $sock) {
            return ['success' => false, 'message' => 'Failed to create socket'];
        }

        socket_set_option($sock, SOL_SOCKET, SO_RCVTIMEO, ['sec' => 2, 'usec' => 0]);
        socket_set_option($sock, SOL_SOCKET, SO_SNDTIMEO, ['sec' => 2, 'usec' => 0]);

        if (! @socket_connect($sock, $host, $port)) {
            socket_close($sock);

            return ['success' => false, 'message' => 'Failed to connect to gdeliveryd'];
        }

        @socket_recv($sock, $greeting, 8192, 0);
        socket_send($sock, $data, strlen($data), 0);
        socket_close($sock);

        return [
            'success' => true,
            'message' => 'Attribute '.$attribute.' set to '.$value,
            'attribute' => $attribute,
            'value' => $value,
        ];
    }

    public function getGameAttribute($attribute)
    {
        $opcode = $this->data['code']['gmGetGameAttri'] ?? 376;

        $pack = pack('NNC', 0, 0, $attribute);
        $data = $this->gamed->createHeader($opcode, $pack);

        $response = $this->gamed->SendToDelivery($data);

        if (! $response) {
            return ['success' => false, 'message' => 'No response from server', 'value' => 0];
        }

        $pos = 0;
        $respOpcode = $this->gamed->unpackCuint($response, $pos);
        $respLen = $this->gamed->unpackCuint($response, $pos);
        $respData = substr($response, $pos);

        if ($respOpcode == 132 && strlen($respData) >= 9) {
            $value = unpack('C', substr($respData, 8, 1))[1];

            return [
                'success' => true,
                'attribute' => $attribute,
                'value' => $value,
            ];
        }

        return ['success' => false, 'message' => 'Invalid response', 'value' => 0];
    }

    public function getAllGameAttributes()
    {
        $results = [];
        foreach (self::GAME_ATTRIBUTES as $key => $attr) {
            $result = $this->getGameAttribute($attr['id']);
            $results[$key] = [
                'id' => $attr['id'],
                'name' => $attr['name'],
                'type' => $attr['type'],
                'value' => $result['value'] ?? 0,
                'success' => $result['success'] ?? false,
            ];
        }

        return $results;
    }

    public function setExpRate($rate)
    {
        $rateMap = [
            1 => 10,
            2 => 20,
            3 => 30,
            4 => 40,
            5 => 50,
            10 => 100,
            15 => 150,
            20 => 200,
            50 => 500,
            100 => 1000,
        ];

        $serverValue = $rateMap[$rate] ?? ($rate * 10);

        return $this->setGameAttribute(204, $serverValue);
    }

    public function setSpRate($enabled)
    {
        return $this->setGameAttribute(213, $enabled ? 1 : 0);
    }

    public function setMoneyRate($enabled)
    {
        return $this->setGameAttribute(211, $enabled ? 1 : 0);
    }

    public function setDropRate($enabled)
    {
        return $this->setGameAttribute(212, $enabled ? 1 : 0);
    }

    public function setNoTrade($enabled)
    {
        return $this->setGameAttribute(207, $enabled ? 1 : 0);
    }

    public function setNoAuction($enabled)
    {
        return $this->setGameAttribute(208, $enabled ? 1 : 0);
    }

    public function setNoMail($enabled)
    {
        return $this->setGameAttribute(209, $enabled ? 1 : 0);
    }

    public function setNoFaction($enabled)
    {
        return $this->setGameAttribute(210, $enabled ? 1 : 0);
    }
}
