#!/usr/bin/python
# -*- coding: utf-8 -*-

# --------------------------------------------------------------------------- #
#                                                                             #
#    Plugin for iSida Jabber Bot                                              #
#    Copyright (C) diSabler <dsy@dsy.name>                                    #
#                                                                             #
#    This program is free software: you can redistribute it and/or modify     #
#    it under the terms of the GNU General Public License as published by     #
#    the Free Software Foundation, either version 3 of the License, or        #
#    (at your option) any later version.                                      #
#                                                                             #
#    This program is distributed in the hope that it will be useful,          #
#    but WITHOUT ANY WARRANTY; without even the implied warranty of           #
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            #
#    GNU General Public License for more details.                             #
#                                                                             #
#    You should have received a copy of the GNU General Public License        #
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.    #
#                                                                             #
# --------------------------------------------------------------------------- #

import datetime
import time
import base64
import sys
import re

VCARD_LIMIT_LONG = 256
VCARD_LIMIT_SHORT = 128
iq_ping_minimal = GT('timeout')

# --------------------------------------------------------------------------- #
# IQ HANDLER (incoming requests)                                              #
# --------------------------------------------------------------------------- #

def iq_iq_get(iq,id,room,acclvl,query,towh,al):
    if iq.getTag(name='query', namespace=xmpp.NS_VERSION) and GT('iq_version_enable'):
        pprint('*** iq:version from %s' % str(room),'magenta')
        i=xmpp.Iq(to=room, typ='result')
        i.setAttr(key='id', val=id)
        i.setQueryNS(namespace=xmpp.NS_VERSION)
        i.getTag('query').setTagData(tag='name', val=botName)
        i.getTag('query').setTagData(tag='version', val=botVersion)
        i.getTag('query').setTagData(tag='os', val=botOs)
        return i

    elif iq.getTag(name='query', namespace=xmpp.NS_TIME) and GT('iq_time_enable'):
        pprint('*** iq:time from %s' % str(room),'magenta')
        t_utc,t_tz,t_display = nice_time(time.time(),room)
        i=xmpp.Iq(to=room, typ='result')
        i.setAttr(key='id', val=id)
        i.setQueryNS(namespace=xmpp.NS_TIME)
        i.getTag('query').setTagData(tag='utc', val=t_utc)
        i.getTag('query').setTagData(tag='tz', val=t_tz)
        i.getTag('query').setTagData(tag='display', val=t_display)
        return i

    elif iq.getTag(name='time', namespace=xmpp.NS_URN_TIME) and GT('iq_time_enable'):
        pprint('*** iq:urn:time from %s' % str(room),'magenta')
        if timeofset in [-12,-11,-10]: t_tz = '-%s:00' % timeofset
        elif timeofset in range(-9,-1): t_tz = '-0%s:00' % timeofset
        elif timeofset in range(0,9): t_tz = '+0%s:00' % timeofset
        else: t_tz = '+%s:00' % timeofset
        i=xmpp.Iq(to=room, typ='result')
        i.setAttr(key='id', val=id)
        i.setTag('time',namespace=xmpp.NS_URN_TIME)
        i.getTag('time').setTagData(tag='tzo', val=t_tz)
        i.getTag('time').setTagData(tag='utc', val=str(time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())))
        return i

    elif iq.getTag(name='ping', namespace=xmpp.NS_URN_PING) and GT('iq_ping_enable'):
        pprint('*** iq:urn:ping from %s' % str(room),'magenta')
        i=xmpp.Iq(to=room, typ='result')
        i.setAttr(key='id', val=id)
        return i

    elif iq.getTag(name='query', namespace=xmpp.NS_LAST) and GT('iq_uptime_enable'):
        pprint('*** iq:uptime from %s' % str(room),'magenta')
        i=xmpp.Iq(to=room, typ='result')
        i.setAttr(key='id', val=id)
        i.setTag('query',namespace=xmpp.NS_LAST,attrs={'seconds':str(int(time.time())-starttime)})
        i.setTagData('query','%s (%s) [%s]' % (Settings['message'],Settings['status'],Settings['priority']))
        return i
    return None

# --------------------------------------------------------------------------- #
# HELPERS                                                                     #
# --------------------------------------------------------------------------- #

def get_who_iq(text,jid,nick):
    if text == '':
        return '%s/%s' % (getRoom(jid),nick)
    else:
        who = text
        for mega1 in megabase:
            if mega1[0] == jid and mega1[1] == text:
                who = '%s/%s' % (getRoom(jid),text)
                break
        return who

def get_caps(room,nick):
    try:
        (id_node,id_ver,id_bmver) = capses['%s/%s' % (room,nick)].split('\n')
        if id_bmver: return '%s %s (%s)' % (id_node,id_ver,id_bmver)
        else: return '%s %s' % (id_node,id_ver)
    except:
        return None

def _parse_tzo(tzo_str):
    """Парсит строку tzo вида +04:00 или -05:00, возвращает смещение в минутах."""
    try:
        sign = 1
        if tzo_str[0] == '-':
            sign = -1
            tzo_str = tzo_str[1:]
        elif tzo_str[0] == '+':
            tzo_str = tzo_str[1:]
        parts = tzo_str.split(':')
        if len(parts) == 2:
            hours = int(parts[0])
            minutes = int(parts[1])
            return sign * (hours * 60 + minutes)
    except:
        pass
    return 0

def _get_server_from_text(text, default_jid):
    if text and text.strip():
        return text.strip().split()[0]
    else:
        return getServer(default_jid)

# --------------------------------------------------------------------------- #
# CAPS                                                                        #
# --------------------------------------------------------------------------- #

def noiq_caps(type, jid, nick, text):
    text = [text,nick][text == '']
    caps = get_caps(jid,text)
    if not caps: msg = L('I can\'t get caps of %s','%s/%s'%(jid,nick)) % text
    elif len(caps) == caps.count(' ') + caps.count('\n'): msg = L('%s has empty caps!','%s/%s'%(jid,nick)) % text
    elif text == nick: msg = L('Your caps is %s','%s/%s'%(jid,nick)) % caps
    else: msg = L('Caps %s is %s','%s/%s'%(jid,nick)) % (text,caps)
    send_msg(type, jid, nick, msg)

# --------------------------------------------------------------------------- #
# VCARD                                                                       #
# --------------------------------------------------------------------------- #

def iq_vcard(type, jid, nick, text):
    global iq_request
    if '\n' in text: text,args = text.split('\n',1)
    else: args = ''
    who,iqid = get_who_iq(text,jid,nick),get_id()
    i = xmpp.Node('iq', {'id': iqid, 'type': 'get', 'to':who}, payload = [xmpp.Node('vCard', {'xmlns': xmpp.NS_VCARD},[])])
    iq_request[iqid]=(time.time(),vcard_async,[type, jid, nick, text, args],xmpp.NS_VCARD)
    sender(i)

def vcard_async(type, jid, nick, text, args, is_answ):
    isa = is_answ[1]
    if len(isa) >= 2 and isa[1] == 'error': msg = L('Error! %s','%s/%s'%(jid,nick)) % L(isa[0].capitalize().replace('-',' '),'%s/%s'%(jid,nick))
    else:
        try: vc,err = isa[1].getTag('vCard',namespace=xmpp.NS_VCARD),False
        except: vc,err = L('Error! %s','%s/%s'%(jid,nick)) % L(isa[0].capitalize().replace('-',' '),'%s/%s'%(jid,nick)),True
        if not vc or str(vc) == '<vCard xmlns="vcard-temp" />': msg = '%s %s' % (L('vCard:','%s/%s'%(jid,nick)),L('Empty!','%s/%s'%(jid,nick)))
        elif err: msg = '%s %s' % (L('vCard:','%s/%s'%(jid,nick)),vc[:VCARD_LIMIT_LONG])
        else:
            data = []
            for t in vc.getChildren():
                if t.getChildren():
                    cm = []
                    for r in t.getChildren():
                        if r.getData(): cm.append(('%s.%s' % (t.getName(),r.getName()),str(r.getData())))
                    data += cm
                elif t.getData(): data.append((t.getName(),t.getData()))
            if data:
                try:
                    photo_size = sys.getsizeof(base64.b64decode(get_value_from_array2(data,'PHOTO.BINVAL')))
                    photo_type = get_value_from_array2(data,'PHOTO.TYPE')
                    data_photo = L('type %s, %s','%s/%s'%(jid,nick)) % (photo_type,get_size_human(photo_size))
                    data = [t for t in list(data) if t[0] not in ['PHOTO.BINVAL','PHOTO.TYPE']]
                    data.append(('PHOTO',data_photo))
                except: pass
                args = args.lower()
                if not args:
                    dd = get_array_from_array2(data,['NICKNAME','FN','BDAY','URL','PHOTO','DESC'])
                    if dd: msg = '%s\n%s' % (L('vCard:','%s/%s'%(jid,nick)),'\n'.join(['%s: %s' % ([L(t[0]),t[0].capitalize()][L(t[0])==t[0]],[u'%s…' % t[1][:VCARD_LIMIT_LONG].strip(),t[1].strip()][len(t[1])<VCARD_LIMIT_LONG]) for t in dd]))
                    else: msg = '%s %s' % (L('vCard:','%s/%s'%(jid,nick)),L('Not found!','%s/%s'%(jid,nick)))
                elif args == 'all': msg = '%s\n%s' % (L('vCard:','%s/%s'%(jid,nick)),'\n'.join(['%s: %s' % ([L(t[0]),t[0].capitalize()][L(t[0])==t[0]],[u'%s…' % t[1][:VCARD_LIMIT_SHORT].strip(),t[1].strip()][len(t[1])<VCARD_LIMIT_SHORT]) for t in data]))
                elif args == 'show':
                    dd = []
                    for t in data:
                        if t[0] not in dd: dd.append(t[0])
                    msg = '%s %s' % (L('vCard:','%s/%s'%(jid,nick)),', '.join([[t.capitalize(),'%s (%s)' % (t.capitalize(),L(t))][L(t)!=t] for t in dd]))
                else:
                    args,dd = args.split('|'),[]
                    for t in args:
                        if ':' in t: val,loc = t.split(':',1)
                        else: val,loc = t,t.upper()
                        val = val.upper()
                        dv = get_array_from_array2(data,(val))
                        if dv: dd += [[loc,dv[0][1]]]
                    if dd: msg = '%s\n%s' % (L('vCard:','%s/%s'%(jid,nick)),'\n'.join(['%s: %s' % ([L(t[0]),t[0].capitalize()][L(t[0])==t[0]],[u'%s…' % t[1][:VCARD_LIMIT_LONG],t[1]][len(t[1])<VCARD_LIMIT_LONG]) for t in dd]))
                    else: msg = '%s %s' % (L('vCard:','%s/%s'%(jid,nick)),L('Not found!','%s/%s'%(jid,nick)))
            else: msg = '%s %s' % (L('vCard:','%s/%s'%(jid,nick)),L('Empty!','%s/%s'%(jid,nick)))
    send_msg(type, jid, nick, msg)

# --------------------------------------------------------------------------- #
# UPTIME                                                                      #
# --------------------------------------------------------------------------- #

def iq_uptime(type, jid, nick, text):
    global iq_request
    who,iqid = get_who_iq(text,jid,nick),get_id()
    i = xmpp.Node('iq', {'id': iqid, 'type': 'get', 'to':who}, payload = [xmpp.Node('query', {'xmlns': xmpp.NS_LAST},[])])
    iq_request[iqid]=(time.time(),uptime_async,[type, jid, nick, text],xmpp.NS_LAST)
    sender(i)

def uptime_async(type, jid, nick, text, is_answ):
    if not text: text = nick
    isa = is_answ[1][0]
    try:
        tstr = un_unix(int(get_tag_item(isa,'query','seconds')),'%s/%s'%(jid,nick))
        if text == nick: msg = L('Your uptime: %s','%s/%s'%(jid,nick)) % tstr
        else: msg = L('Uptime %s: %s','%s/%s'%(jid,nick)) % (text,tstr)
        up_stat = esc_min(get_tag(isa,'query'))
        if len(up_stat): msg = '%s // %s' % (msg,up_stat)
    except: msg = L('I can\'t do it','%s/%s'%(jid,nick))
    send_msg(type, jid, nick, msg)

# --------------------------------------------------------------------------- #
# PING                                                                        #
# --------------------------------------------------------------------------- #

def urn_ping(type, jid, nick, text):
    global iq_request
    who,iqid = get_who_iq(text,jid,nick),get_id()
    i = xmpp.Node('iq', {'id': iqid, 'type': 'get', 'to':who}, payload = [xmpp.Node('ping', {'xmlns': xmpp.NS_URN_PING},[])])
    iq_request[iqid]=(time.time(),ping_async,[type, jid, nick, text],xmpp.NS_URN_PING)
    sender(i)

def ping(type, jid, nick, text):
    global iq_request
    who,iqid = get_who_iq(text,jid,nick),get_id()
    i = xmpp.Node('iq', {'id': iqid, 'type': 'get', 'to':who}, payload = [xmpp.Node('query', {'xmlns': xmpp.NS_VERSION},[])])
    iq_request[iqid]=(time.time(),ping_async,[type, jid, nick, text],xmpp.NS_VERSION)
    sender(i)

def ping_async(type, jid, nick, text, is_answ):
    global iq_ping_minimal
    isa = is_answ[1]
    if len(isa) >= 2 and isa[1] == 'error' and isa[0] == 'remote-server-not-found': msg = L('Error! %s','%s/%s'%(jid,nick)) % L(isa[0].capitalize().replace('-',' '),'%s/%s'%(jid,nick))
    else:
        p_digits = GT('ping_digits')
        original_ping = float(is_answ[0])
        if iq_ping_minimal > original_ping: iq_ping_minimal = original_ping
        fixed_ping = round(original_ping - iq_ping_minimal,p_digits)
        if fixed_ping <= 0: fixed_ping = original_ping
        f = '%'+'.0%sf' % p_digits
        if text in ['',nick]: msg = L('Ping from you %s sec.','%s/%s'%(jid,nick)) % f % fixed_ping
        else: msg = L('Ping from %s %s sec.','%s/%s'%(jid,nick)) % (text, f % fixed_ping)
    send_msg(type, jid, nick, msg)

# --------------------------------------------------------------------------- #
# TIME                                                                        #
# --------------------------------------------------------------------------- #

def iq_time(type, jid, nick, text):
    iq_time_get(type, jid, nick, text, None)

def iq_time_raw(type, jid, nick, text):
    iq_time_get(type, jid, nick, text, True)

def iq_time_get(type, jid, nick, text, mode):
    global iq_request
    who,iqid = get_who_iq(text,jid,nick),get_id()
    i = xmpp.Node('iq', {'id': iqid, 'type': 'get', 'to':who}, payload = [xmpp.Node('query', {'xmlns': xmpp.NS_TIME},[])])
    iq_request[iqid]=(time.time(),time_async,[type, jid, nick, text, mode],xmpp.NS_TIME)
    sender(i)

def time_async(type, jid, nick, text, mode, is_answ):
    if not text: text = nick
    isa = is_answ[1]
    if len(isa) == 3:
        msg = isa[0]
        if mode: msg += ', Raw time: %s, TimeZone: %s' % (isa[1],isa[2])
    else: msg = ' '.join(isa)
    if text == nick: msg = L('Your time is %s','%s/%s'%(jid,nick)) % msg
    else: msg = L('Time %s is %s','%s/%s'%(jid,nick)) % (text,msg)
    send_msg(type, jid, nick, msg)

def iq_utime(type, jid, nick, text):
    iq_utime_get(type, jid, nick, text, None)

def iq_utime_raw(type, jid, nick, text):
    iq_utime_get(type, jid, nick, text, True)

def iq_utime_get(type, jid, nick, text, mode):
    global iq_request
    who,iqid = get_who_iq(text,jid,nick),get_id()
    i = xmpp.Node('iq', {'id': iqid, 'type': 'get', 'to':who}, payload = [xmpp.Node('time', {'xmlns': xmpp.NS_URN_TIME},[])])
    iq_request[iqid]=(time.time(),utime_async,[type, jid, nick, text, mode],xmpp.NS_URN_TIME)
    sender(i)

def utime_async(type, jid, nick, text, mode, is_answ):
    if not text: text = nick
    isa = is_answ[1]
    if len(isa) >= 2 and isa[1] == 'error':
        msg = L('Error! %s','%s/%s'%(jid,nick)) % L(isa[0].capitalize().replace('-',' '),'%s/%s'%(jid,nick))
    else:
        try:
            utc_str = isa[0]
            tzo_str = isa[1] if len(isa) > 1 else '+00:00'

            utc = datetime.datetime.fromisoformat(utc_str.replace('Z', '+00:00'))
            tzo_minutes = _parse_tzo(tzo_str)
            local_time = utc + datetime.timedelta(minutes=tzo_minutes)

            tstr = local_time.strftime('%H:%M:%S | %d.%m.%Y')
            if text in ['',nick]:
                msg = L('Your time is %s','%s/%s'%(jid,nick)) % tstr
            else:
                msg = L('Time %s is %s','%s/%s'%(jid,nick)) % (text,tstr)
            if mode:
                msg = '%s | UTC: %s TZO: %s' % (msg, utc_str, tzo_str)
        except Exception as e:
            pprint('*** utime_async error: %s' % str(e), 'red')
            msg = L('Unknown server answer! %s','%s/%s'%(jid,nick)) % isa[0] if isa else 'Unknown error'
    send_msg(type, jid, nick, msg)

# --------------------------------------------------------------------------- #
# VERSION                                                                     #
# --------------------------------------------------------------------------- #

def iq_version(type, jid, nick, text): iq_version_raw(type, jid, nick, text, False)
def iq_version_caps(type, jid, nick, text): iq_version_raw(type, jid, nick, text, True)

def iq_version_raw(type, jid, nick, text, with_caps):
    global iq_request
    who,iqid = get_who_iq(text,jid,nick),get_id()
    i = xmpp.Node('iq', {'id': iqid, 'type': 'get', 'to':who}, payload = [xmpp.Node('query', {'xmlns': xmpp.NS_VERSION},[])])
    iq_request[iqid]=(time.time(),version_async,[type, jid, nick, text, with_caps],xmpp.NS_VERSION)
    sender(i)

def version_async(type, jid, nick, text, with_caps, is_answ):
    if not text: text = nick
    isa = is_answ[1]
    if len(isa) >= 2 and isa[1] == 'error': msg = L('Error! %s','%s/%s'%(jid,nick)) % L(isa[0].capitalize().replace('-',' '),'%s/%s'%(jid,nick))
    else: msg = isa[0]
    if with_caps:
        caps = get_caps(jid,[text,nick][text == ''])
        if caps: msg += ' || %s' % caps
    if text in ['',nick]: msg = L('Your version is %s','%s/%s'%(jid,nick)) % msg
    else: msg = L('Version %s is %s','%s/%s'%(jid,nick)) % (text,msg)
    send_msg(type, jid, nick, msg)

# --------------------------------------------------------------------------- #
# STATS (fixed)                                                              #
# --------------------------------------------------------------------------- #

def iq_stats(type, jid, nick, text):
    if not text:
        send_msg(type, jid, nick, L('What?', '%s/%s' % (jid, nick)))
        return
    server = _get_server_from_text(text, jid)

    iqid = get_id()
    i = xmpp.Node('iq', {'id': iqid, 'type': 'get', 'to': server}, payload=[xmpp.Node('query', {'xmlns': xmpp.NS_STATS}, [])])
    iq_request[iqid] = (time.time(), stats_async_features, [type, jid, nick, server], xmpp.NS_STATS)
    sender(i)

def stats_async_features(type, jid, nick, server, is_answ):
    isa = is_answ[1]
    if len(isa) >= 2 and isa[1] == 'error':
        msg = L('Error! %s', '%s/%s' % (jid, nick)) % L(isa[0].capitalize().replace('-', ' '), '%s/%s' % (jid, nick))
        send_msg(type, jid, nick, msg)
        return

    try:
        query = isa[1].getTag('query', namespace=xmpp.NS_STATS)
        if not query:
            send_msg(type, jid, nick, L('Unavailable!', '%s/%s' % (jid, nick)))
            return

        stats_list = [t.getAttr('name') for t in query.getTags('stat') if t.getAttr('name')]
        if not stats_list:
            send_msg(type, jid, nick, L('Unavailable!', '%s/%s' % (jid, nick)))
            return

        # Запрашиваем значения
        iqid = get_id()
        i = xmpp.Node('iq', {'id': iqid, 'type': 'get', 'to': server}, payload=[xmpp.Node('query', {'xmlns': xmpp.NS_STATS}, [xmpp.Node('stat', {'name': t}, []) for t in stats_list])])
        iq_request[iqid] = (time.time(), stats_async, [type, jid, nick, server], xmpp.NS_STATS)
        sender(i)
    except:
        send_msg(type, jid, nick, L('Unavailable!', '%s/%s' % (jid, nick)))

def stats_async(type, jid, nick, server, is_answ):
    isa = is_answ[1]
    if len(isa) >= 2 and isa[1] == 'error':
        msg = L('Error! %s', '%s/%s' % (jid, nick)) % L(isa[0].capitalize().replace('-', ' '), '%s/%s' % (jid, nick))
        send_msg(type, jid, nick, msg)
        return

    try:
        query = isa[1].getTag('query', namespace=xmpp.NS_STATS)
        if not query:
            send_msg(type, jid, nick, L('Unavailable!', '%s/%s' % (jid, nick)))
            return

        stats = []
        for t in query.getTags('stat'):
            name = t.getAttr('name')
            value = t.getAttr('value')
            units = t.getAttr('units')
            if name and value:
                if units:
                    stats.append('%s: %s %s' % (name.capitalize(), value, units))
                else:
                    stats.append('%s: %s' % (name.capitalize(), value))

        if stats:
            msg = L('Server statistic: %s\n%s', '%s/%s' % (jid, nick)) % (server, '\n'.join(stats))
        else:
            msg = L('Unavailable!', '%s/%s' % (jid, nick))
    except:
        msg = L('Unavailable!', '%s/%s' % (jid, nick))

    send_msg(type, jid, nick, msg)

# --------------------------------------------------------------------------- #
# IQ_SRV (с последовательными запросами)                                      #
# --------------------------------------------------------------------------- #

def iq_srv(type, jid, nick, text):
    if not text:
        send_msg(type, jid, nick, L('What?', '%s/%s' % (jid, nick)))
        return
    server = _get_server_from_text(text, jid)

    data = {
        'server': server,
        'name': None,
        'version': None,
        'time': None,
        'ping': None,
        'uptime': None,
        'contact': None,
        'support': None,
        'features': [],
        'admin': None,
    }

    # Запускаем цепочку: disco → ping → time → version
    _srv_disco(type, jid, nick, server, data, 0)

def _srv_disco(type, jid, nick, server, data, step):
    global iq_request
    iqid = get_id()
    i = xmpp.Node('iq', {'id': iqid, 'type': 'get', 'to': server}, payload=[xmpp.Node('query', {'xmlns': xmpp.NS_DISCO_INFO}, [])])
    iq_request[iqid] = (time.time(), _srv_disco_cb, [type, jid, nick, server, data, step], xmpp.NS_DISCO_INFO)
    sender(i)

def _srv_disco_cb(type, jid, nick, server, data, step, is_answ):
    isa = is_answ[1]
    if len(isa) >= 2 and isa[1] == 'error':
        # Если disco не работает, всё равно продолжаем
        pass
    else:
        try:
            query = isa[1].getTag('query', namespace=xmpp.NS_DISCO_INFO)
            if query:
                # Имя сервера
                for ident in query.getTags('identity'):
                    if ident.getAttr('category') == 'server':
                        data['name'] = ident.getAttr('name')
                        break

                # Фичи
                data['features'] = [t.getAttr('var') for t in query.getTags('feature')]

                # Контакты
                x_tag = query.getTag('x', namespace=xmpp.NS_DATA)
                if x_tag:
                    for field in x_tag.getTags('field'):
                        var = field.getAttr('var')
                        if var in ['admin-addresses', 'feedback-addresses', 'abuse-addresses']:
                            vals = field.getTags('value')
                            if vals:
                                val = vals[0].getData()
                                if var == 'admin-addresses':
                                    data['admin'] = val
                                if var == 'feedback-addresses' and 'support' in val.lower():
                                    data['support'] = val
                                if not data['contact'] and 'xmpp:' in val:
                                    data['contact'] = val
                                elif not data['contact'] and 'mailto:' in val:
                                    data['contact'] = val
                    # Если контакт не нашли, используем admin
                    if not data['contact'] and data['admin']:
                        data['contact'] = data['admin']
        except:
            pass

    # Переходим к пингу
    _srv_ping(type, jid, nick, server, data, step+1)

def _srv_ping(type, jid, nick, server, data, step):
    global iq_request
    iqid = get_id()
    start = time.time()
    i = xmpp.Node('iq', {'id': iqid, 'type': 'get', 'to': server}, payload=[xmpp.Node('ping', {'xmlns': xmpp.NS_URN_PING}, [])])
    iq_request[iqid] = (start, _srv_ping_cb, [type, jid, nick, server, data, step], xmpp.NS_URN_PING)
    sender(i)

def _srv_ping_cb(type, jid, nick, server, data, step, is_answ):
    start = is_answ[1][0] if isinstance(is_answ[1], list) else time.time()
    elapsed = time.time() - start
    data['ping'] = round(elapsed, 3) if elapsed < 10 else None

    # Переходим к времени
    _srv_time(type, jid, nick, server, data, step+1)

def _srv_time(type, jid, nick, server, data, step):
    global iq_request
    iqid = get_id()
    i = xmpp.Node('iq', {'id': iqid, 'type': 'get', 'to': server}, payload=[xmpp.Node('time', {'xmlns': xmpp.NS_URN_TIME}, [])])
    iq_request[iqid] = (time.time(), _srv_time_cb, [type, jid, nick, server, data, step], xmpp.NS_URN_TIME)
    sender(i)

def _srv_time_cb(type, jid, nick, server, data, step, is_answ):
    isa = is_answ[1]
    if len(isa) >= 2 and isa[1] == 'error':
        data['time'] = None
    else:
        try:
            utc_str = isa[0]
            tzo_str = isa[1] if len(isa) > 1 else '+00:00'
            utc = datetime.datetime.fromisoformat(utc_str.replace('Z', '+00:00'))
            tzo_minutes = _parse_tzo(tzo_str)
            local_time = utc + datetime.timedelta(minutes=tzo_minutes)
            data['time'] = local_time.strftime('%H:%M:%S | %d.%m.%Y')
        except:
            data['time'] = None

    # Переходим к версии
    _srv_uptime(type, jid, nick, server, data, step+1)

def _srv_uptime(type, jid, nick, server, data, step):
    """Запрашивает uptime сервера."""
    global iq_request
    iqid = get_id()
    i = xmpp.Node('iq', {'id': iqid, 'type': 'get', 'to': server}, payload=[xmpp.Node('query', {'xmlns': xmpp.NS_LAST}, [])])
    iq_request[iqid] = (time.time(), _srv_uptime_cb, [type, jid, nick, server, data, step], xmpp.NS_LAST)
    sender(i)

def _srv_uptime_cb(type, jid, nick, server, data, step, is_answ):
    isa = is_answ[1]
    if len(isa) >= 2 and isa[1] == 'error':
        data['uptime'] = None
    else:
        try:
            seconds = int(get_tag_item(isa[0], 'query', 'seconds'))
            data['uptime'] = un_unix(seconds, '%s/%s' % (jid, nick))
        except:
            data['uptime'] = None

    # После получения uptime переходим к версии (или завершаем вывод)
    _srv_version(type, jid, nick, server, data, step+1)

def _srv_version(type, jid, nick, server, data, step):
    global iq_request
    iqid = get_id()
    i = xmpp.Node('iq', {'id': iqid, 'type': 'get', 'to': server}, payload=[xmpp.Node('query', {'xmlns': xmpp.NS_VERSION}, [])])
    iq_request[iqid] = (time.time(), _srv_version_cb, [type, jid, nick, server, data, step], xmpp.NS_VERSION)
    sender(i)

def _srv_version_cb(type, jid, nick, server, data, step, is_answ):
    isa = is_answ[1]
    if len(isa) >= 2 and isa[1] == 'error':
        data['version'] = None
    else:
        data['version'] = isa[0]

    # Все данные собраны — выводим результат
    _srv_output(type, jid, nick, server, data)

def _srv_output(type, jid, nick, server, data):
    lines = []
    lines.append('Держи:')
    #lines.append('')

    # Имя сервера
    name = data.get('name')
    if name:
        lines.append('Server name: %s' % name)

    # Версия (только ejabberd X.X.X)
    version = data.get('version')
    if version:
        # Пробуем извлечь только ejabberd версию
        import re
        match = re.search(r'(ejabberd\s+[\d\.]+)', version, re.I)
        if match:
            lines.append('Server version: %s' % match.group(1))
        else:
            lines.append('Server version: %s' % version)

    # Время
    time_val = data.get('time')
    if time_val:
        lines.append('Time: %s' % time_val)

    # Пинг
    ping_val = data.get('ping')
    if ping_val is not None:
        lines.append('Ping: %s сек.' % ping_val)

    # Uptime
    uptime_val = data.get('uptime')
    if uptime_val is not None:
        lines.append('Uptime: %s' % uptime_val)

    # Контакты
    if data.get('contact'):
        lines.append('Contact: %s' % data['contact'])
    if data.get('support'):
        lines.append('Support: %s' % data['support'])

    # Фичи
    features = data.get('features')
    if features:
        feature_map = {
            'urn:xmpp:mam:0': 'MAM',
            'urn:xmpp:mam:1': 'MAM v1',
            'urn:xmpp:mam:2': 'MAM v2',
            'urn:xmpp:carbons:2': 'Carbons',
            'jabber:iq:register': 'Registration',
        }
        supported = []
        unsupported = []
        for ns, name in feature_map.items():
            if ns in features:
                supported.append(name)
            else:
                unsupported.append(name)

        if supported:
            lines.append('')
            lines.append('Supported: %s' % ', '.join(supported))
        if unsupported:
            lines.append('Not supported: %s' % ', '.join(unsupported))
    else:
        lines.append('Features: N/A (no response)')

    msg = '\n'.join(lines)
    send_msg(type, jid, nick, msg)

# --------------------------------------------------------------------------- #
# COMMAND WRAPPERS FOR SERVER INFO                                            #
# --------------------------------------------------------------------------- #

def _send_disco_request(type, jid, nick, server, callback, extra=None):
    global iq_request
    iqid = get_id()
    i = xmpp.Node('iq', {'id': iqid, 'type': 'get', 'to': server}, payload=[xmpp.Node('query', {'xmlns': xmpp.NS_DISCO_INFO}, [])])
    iq_request[iqid] = (time.time(), callback, [type, jid, nick, server, extra], xmpp.NS_DISCO_INFO)
    sender(i)

def _check_feature(features, ns):
    return ns in features if features else False

def iq_feature_cmd(type, jid, nick, text, feature_ns, feature_name, extra_desc=''):
    if not text:
        send_msg(type, jid, nick, L('What?', '%s/%s' % (jid, nick)))
        return
    server = _get_server_from_text(text, jid)
    _send_disco_request(type, jid, nick, server, iq_feature_async, (feature_ns, feature_name, extra_desc))

def iq_feature_async(type, jid, nick, server, extra, is_answ):
    isa = is_answ[1]
    feature_ns, feature_name, extra_desc = extra

    if len(isa) >= 2 and isa[1] == 'error':
        msg = L('Error! %s', '%s/%s' % (jid, nick)) % L(isa[0].capitalize().replace('-', ' '), '%s/%s' % (jid, nick))
    else:
        try:
            query = isa[1].getTag('query', namespace=xmpp.NS_DISCO_INFO)
            if query:
                features = [t.getAttr('var') for t in query.getTags('feature')]
                if _check_feature(features, feature_ns):
                    msg = L('%s on %s: %s', '%s/%s' % (jid, nick)) % (feature_name, server, L('supported', '%s/%s' % (jid, nick)))
                    if extra_desc:
                        msg += ' (%s)' % extra_desc
                else:
                    msg = L('%s on %s: %s', '%s/%s' % (jid, nick)) % (feature_name, server, L('not supported', '%s/%s' % (jid, nick)))
            else:
                msg = L('No response from %s', '%s/%s' % (jid, nick)) % server
        except:
            msg = L('Error parsing response from %s', '%s/%s' % (jid, nick)) % server
    send_msg(type, jid, nick, msg)

def iq_ver(type, jid, nick, text):
    if not text:
        send_msg(type, jid, nick, L('What?', '%s/%s' % (jid, nick)))
        return
    server = _get_server_from_text(text, jid)
    iq_version_raw(type, jid, nick, server, False)

def iq_mam(type, jid, nick, text):
    iq_feature_cmd(type, jid, nick, text, 'urn:xmpp:mam:0', 'MAM')

def iq_reg(type, jid, nick, text):
    iq_feature_cmd(type, jid, nick, text, 'jabber:iq:register', 'In-band registration')

def iq_car(type, jid, nick, text):
    iq_feature_cmd(type, jid, nick, text, 'urn:xmpp:carbons:2', 'Carbons')

# --------------------------------------------------------------------------- #
# REGISTER COMMANDS                                                           #
# --------------------------------------------------------------------------- #

global execute, iq_hook

iq_hook = [[50,'get',iq_iq_get]]

execute = [
    (3, 'ver', iq_version, 2, 'Client version.'),
    (3, 'ver+', iq_version_caps, 2, 'Client version with caps.'),
    (3, 'caps', noiq_caps, 2, 'Show caps node and version of client.'),
    (3, 'ping_old', ping, 2, 'Ping via version request (legacy).'),
    (3, 'ping', urn_ping, 2, 'Ping - reply time.'),
    (3, 'time_old', iq_time, 2, 'Client side time (legacy).'),
    (3, 'time', iq_utime, 2, 'Client side time.'),
    (3, 'time_old_raw', iq_time_raw, 2, 'Client side time + raw time format (legacy).'),
    (3, 'time_raw', iq_utime_raw, 2, 'Client side time + raw time format.'),
    (3, 'vcard_raw', iq_vcard, 2, 'vCard query.'),
    (3, 'uptime', iq_uptime, 2, 'Server or jid uptime.'),

    # Stats (fixed)
    (3, 'stats', iq_stats, 2, 'Server statistic. Usage: stats <server>'),

    # New server info commands
    (3, 'iq_ver', iq_ver, 2, 'Server version. Usage: iq_ver <server>'),
    (3, 'iq_mam', iq_mam, 2, 'Check MAM support. Usage: iq_mam <server>'),
    (3, 'iq_reg', iq_reg, 2, 'Check in-band registration support. Usage: iq_reg <server>'),
    (3, 'iq_car', iq_car, 2, 'Check Carbons support. Usage: iq_car <server>'),
    (3, 'iq_srv', iq_srv, 2, 'Full server info: version, ping, time, features. Usage: iq_srv <server>'),
]
