#!/usr/bin/env python3
"""
luckyget v2.0 - The all-in-one dev CLI tool for Termux
"""

import sys, os, json, time, socket, re, hashlib, base64, threading
import urllib.request, urllib.error, urllib.parse
import argparse
from datetime import datetime

R  = "\033[0m";  B  = "\033[1m";  DIM= "\033[2m"
RED= "\033[91m"; GRN= "\033[92m"; YLW= "\033[93m"
BLU= "\033[94m"; MAG= "\033[95m"; CYN= "\033[96m"; WHT= "\033[97m"

def c(color, text): return f"{color}{text}{R}"

def banner():
    print(f"""{CYN}{B}
  ██╗     ██╗   ██╗ ██████╗██╗  ██╗██╗   ██╗ ██████╗ ███████╗████████╗
  ██║     ██║   ██║██╔════╝██║ ██╔╝╚██╗ ██╔╝██╔════╝ ██╔════╝╚══██╔══╝
  ██║     ██║   ██║██║     █████╔╝  ╚████╔╝ ██║  ███╗█████╗     ██║
  ██║     ██║   ██║██║     ██╔═██╗   ╚██╔╝  ██║   ██║██╔══╝     ██║
  ███████╗╚██████╔╝╚██████╗██║  ██╗   ██║   ╚██████╔╝███████╗   ██║
  ╚══════╝ ╚═════╝  ╚═════╝╚═╝  ╚═╝   ╚═╝    ╚═════╝ ╚══════╝   ╚═╝
{R}{DIM}  The all-in-one dev CLI tool for Termux  {R}{YLW}{B}v2.0{R}
""")

def fmt_json(data):
    pretty = json.dumps(data, indent=2, ensure_ascii=False)
    pretty = re.sub(r'"[^"]*"(?=\s*:)', lambda m: f"{BLU}{B}{m.group(0)}{R}", pretty)
    pretty = re.sub(r'(?<=:\s)"[^"]*"', lambda m: f"{GRN}{m.group(0)}{R}", pretty)
    pretty = re.sub(r'\b(true|false|null)\b', lambda m: f"{YLW}{m.group(0)}{R}", pretty)
    pretty = re.sub(r'(?<!["\w])-?\d+\.?\d*(?!["\w])', lambda m: f"{CYN}{m.group(0)}{R}", pretty)
    return pretty

def status_color(code):
    if code < 300: return GRN
    if code < 400: return YLW
    if code < 500: return RED
    return MAG

def format_size(n):
    for unit in ['B','KB','MB','GB']:
        if n < 1024: return f"{n:.1f} {unit}"
        n /= 1024
    return f"{n:.1f} TB"

def format_ms(ms):
    if ms < 1000: return f"{ms:.0f}ms"
    return f"{ms/1000:.2f}s"

def section(title):
    print(f"\n{c(B+CYN, '  ' + title)}")
    print(f"  {c(DIM, chr(8212)*50)}")

def cmd_get(args):
    url = args.url
    if not url.startswith('http'): url = 'https://' + url
    method = (args.method or 'GET').upper()
    headers = {'User-Agent': 'luckyget/2.0'}
    body = None
    if args.json:
        headers['Content-Type'] = 'application/json'
        headers['Accept'] = 'application/json'
    if args.header:
        for h in args.header:
            if ':' in h:
                k, v = h.split(':', 1)
                headers[k.strip()] = v.strip()
    if args.data:
        body = args.data.encode()
        if not args.method: method = 'POST'
    if args.bearer:
        headers['Authorization'] = f'Bearer {args.bearer}'
    print(f"\n{DIM}-> {method} {url}{R}")
    print(f"{DIM}{'-'*50}{R}")
    req = urllib.request.Request(url, data=body, headers=headers, method=method)
    t0 = time.time()
    try:
        with urllib.request.urlopen(req, timeout=args.timeout or 10) as resp:
            elapsed = (time.time()-t0)*1000
            raw = resp.read()
            code = resp.status
            resp_headers = dict(resp.headers)
            sc = status_color(code)
            print(f"  {c(B,'Status:')}  {c(sc, str(code)+' '+resp.reason)}")
            print(f"  {c(B,'Time:  ')}  {c(CYN, format_ms(elapsed))}")
            print(f"  {c(B,'Size:  ')}  {c(YLW, format_size(len(raw)))}")
            if args.verbose:
                print(f"\n{c(B,'Headers:')}")
                for k,v in resp_headers.items():
                    print(f"  {c(DIM,k+':')} {v}")
            print(f"\n{c(B,'Body:')}\n{DIM}{'-'*50}{R}")
            try:
                decoded = raw.decode('utf-8')
                ct = resp_headers.get('Content-Type','')
                if 'json' in ct or args.json:
                    print(fmt_json(json.loads(decoded)))
                else:
                    if len(decoded) > 2000 and not args.full:
                        print(decoded[:2000])
                        print(f"\n{c(DIM,'... (--full to see all)')}")
                    else:
                        print(decoded)
            except:
                print(f"{YLW}[binary, {format_size(len(raw))}]{R}")
    except urllib.error.HTTPError as e:
        sc = status_color(e.code)
        print(f"  {c(B,'Status:')}  {c(sc, str(e.code)+' '+e.reason)}")
        try:
            bd = e.read().decode()
            print(f"\n{c(RED,'Error:')}")
            try: print(fmt_json(json.loads(bd)))
            except: print(bd[:800])
        except: pass
    except Exception as e:
        print(f"\n{c(RED,'X')} {e}")

def cmd_status(args):
    urls = args.urls
    section('Site Status Check')
    print(f"  {'URL':<36} {'STATUS':<12} {'TIME':<10} SIZE")
    print(f"  {c(DIM,'-'*60)}")
    lock = threading.Lock()
    def check(url):
        u = url if url.startswith('http') else 'https://'+url
        t0 = time.time()
        try:
            req = urllib.request.Request(u, headers={'User-Agent':'luckyget/2.0'})
            with urllib.request.urlopen(req, timeout=args.timeout or 8) as r:
                ms = (time.time()-t0)*1000
                raw = r.read()
                sc = status_color(r.status)
                short = u.replace('https://','').replace('http://','')[:34]
                with lock:
                    print(f"  {short:<36} {c(sc,str(r.status)+' OK'):<20} {c(CYN,format_ms(ms)):<18} {c(YLW,format_size(len(raw)))}")
        except urllib.error.HTTPError as e:
            ms = (time.time()-t0)*1000
            sc = status_color(e.code)
            short = u.replace('https://','').replace('http://','')[:34]
            with lock:
                print(f"  {short:<36} {c(sc,str(e.code)+' ERR'):<20} {c(CYN,format_ms(ms)):<18} {c(DIM,'-')}")
        except:
            short = u.replace('https://','').replace('http://','')[:34]
            with lock:
                print(f"  {short:<36} {c(RED,'TIMEOUT'):<20} {c(DIM,'-'):<18} {c(DIM,'-')}")
    threads = [threading.Thread(target=check, args=(u,)) for u in urls]
    for t in threads: t.start()
    for t in threads: t.join()
    print()

def cmd_ports(args):
    host = args.host
    common_ports = {
        21:'FTP',22:'SSH',23:'Telnet',25:'SMTP',53:'DNS',
        80:'HTTP',110:'POP3',143:'IMAP',443:'HTTPS',445:'SMB',
        3000:'Node/Dev',3306:'MySQL',5432:'PostgreSQL',5900:'VNC',
        6379:'Redis',8000:'Django',8080:'HTTP-alt',8443:'HTTPS-alt',
        8888:'Jupyter',9200:'Elasticsearch',27017:'MongoDB',
        4200:'Angular',3001:'React',5000:'Flask',5173:'Vite',4000:'Phoenix'
    }
    ports = args.ports if args.ports else list(common_ports.keys())
    try:
        ip = socket.gethostbyname(host)
    except:
        ip = host
    section(f'Port Scanner -> {host} ({ip})')
    print(f"  Scanning {c(YLW, str(len(ports)))} ports...\n")
    open_ports = []
    lock = threading.Lock()
    def scan(port):
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.settimeout(args.timeout or 0.8)
            result = s.connect_ex((ip, port))
            s.close()
            if result == 0:
                svc = common_ports.get(port, 'unknown')
                with lock:
                    open_ports.append((port, svc))
                    print(f"  {c(GRN,'*')} {c(B, str(port)):<8} {c(GRN,'OPEN'):<14} {c(DIM,svc)}")
        except: pass
    threads = [threading.Thread(target=scan, args=(p,)) for p in ports]
    for t in threads: t.start()
    for t in threads: t.join()
    if not open_ports:
        print(f"  {c(DIM,'No open ports found')}")
    print(f"\n  {c(B,'Open:')} {c(GRN if open_ports else RED, str(len(open_ports)))} / {len(ports)} scanned\n")

def cmd_dns(args):
    host = args.host
    section(f'DNS Lookup -> {host}')
    print()
    try:
        ips = socket.getaddrinfo(host, None)
        seen = set()
        for r in ips:
            ip = r[4][0]
            fam = 'IPv6' if r[0].name == 'AF_INET6' else 'IPv4'
            if ip not in seen:
                seen.add(ip)
                print(f"  {c(BLU,fam):<20} {c(GRN, ip)}")
        try:
            rev = socket.gethostbyaddr(list(seen)[0])[0]
            print(f"\n  {c(B,'Reverse:')}  {c(YLW, rev)}")
        except: pass
    except Exception as e:
        print(f"  {c(RED,'X')} {e}")
    print()

def cmd_encode(args):
    text = args.text
    section('Encode / Decode / Hash')
    print()
    b64enc = base64.b64encode(text.encode()).decode()
    print(f"  {c(B+BLU,'Base64 encode:')}  {c(GRN, b64enc)}")
    try:
        b64dec = base64.b64decode(text).decode('utf-8')
        print(f"  {c(B+BLU,'Base64 decode:')}  {c(YLW, b64dec)}")
    except:
        print(f"  {c(B+BLU,'Base64 decode:')}  {c(DIM,'(not valid base64)')}")
    urlenc = urllib.parse.quote(text)
    urldec = urllib.parse.unquote(text)
    print(f"\n  {c(B+MAG,'URL encode:')}     {c(GRN, urlenc)}")
    print(f"  {c(B+MAG,'URL decode:')}     {c(YLW, urldec)}")
    print(f"\n  {c(B+CYN,'MD5:   ')}  {c(GRN, hashlib.md5(text.encode()).hexdigest())}")
    print(f"  {c(B+CYN,'SHA1:  ')}  {c(GRN, hashlib.sha1(text.encode()).hexdigest())}")
    print(f"  {c(B+CYN,'SHA256:')}  {c(GRN, hashlib.sha256(text.encode()).hexdigest())}")
    print(f"  {c(B+CYN,'SHA512:')}  {c(GRN, hashlib.sha512(text.encode()).hexdigest()[:64]+'...')}")
    print()

def cmd_ip(args):
    section('IP Info')
    print()
    target = args.host or ''
    url = f"https://ipinfo.io/{target}/json"
    try:
        req = urllib.request.Request(url, headers={'User-Agent':'luckyget/2.0','Accept':'application/json'})
        with urllib.request.urlopen(req, timeout=8) as r:
            data = json.loads(r.read())
        fields = ['ip','city','region','country','org','timezone','loc']
        labels = {'ip':'IP','city':'City','region':'Region','country':'Country','org':'Org','timezone':'Timezone','loc':'Coords'}
        for f in fields:
            if f in data:
                print(f"  {c(B, labels.get(f,f)+':', ):<18} {c(GRN if f=='ip' else WHT, data[f])}")
    except Exception as e:
        print(f"  {c(RED,'X')} {e}")
    print()

def cmd_tg(args):
    token = args.token or os.environ.get('TELEGRAM_BOT_TOKEN')
    if not token:
        print(f"\n{c(RED,'X No bot token!')}\n  export TELEGRAM_BOT_TOKEN='123:ABC...'\n  or: luckyget tg --token 123:ABC...\n")
        return
    base = f"https://api.telegram.org/bot{token}"
    def tg_call(endpoint, data=None):
        url = f"{base}/{endpoint}"
        payload = json.dumps(data).encode() if data else None
        req = urllib.request.Request(url, data=payload,
            headers={'Content-Type':'application/json'} if payload else {})
        with urllib.request.urlopen(req, timeout=10) as r:
            return json.loads(r.read())
    section('Telegram Bot Tester')
    print()
    try:
        me = tg_call('getMe')['result']
        print(f"  {c(GRN,'OK')} Bot: {c(B,me.get('first_name','?'))} {c(DIM,'@'+me.get('username','?'))}")
        print(f"  {c(DIM,'ID:')} {me.get('id')}")
    except Exception as e:
        print(f"  {c(RED,'X getMe failed:')} {e}"); return
    if args.updates:
        try:
            upd = tg_call('getUpdates?limit=5')['result']
            print(f"\n  {c(B,'Last messages:')}")
            for u in upd:
                msg = u.get('message',{})
                fr = msg.get('from',{})
                txt = msg.get('text','[no text]')
                name = fr.get('first_name','?')
                print(f"  -> {c(YLW,name)}: {txt[:60]}")
        except Exception as e:
            print(f"  {c(RED,'X getUpdates:')} {e}")
    if args.chat and args.msg:
        try:
            tg_call('sendMessage', {'chat_id': args.chat, 'text': args.msg})
            print(f"\n  {c(GRN,'OK Message sent!')} to {args.chat}")
        except Exception as e:
            print(f"\n  {c(RED,'X sendMessage:')} {e}")
    print()

def cmd_mock(args):
    import http.server
    port = args.port or 8888
    resp_body = args.body or '{"status":"ok","mock":true}'
    resp_code = args.code or 200
    resp_ct   = args.ct   or 'application/json'
    class MockHandler(http.server.BaseHTTPRequestHandler):
        def do_GET(self): self._respond()
        def do_POST(self): self._respond()
        def do_PUT(self): self._respond()
        def do_DELETE(self): self._respond()
        def do_PATCH(self): self._respond()
        def do_OPTIONS(self): self._respond()
        def _respond(self):
            body_bytes = resp_body.encode()
            t = datetime.now().strftime('%H:%M:%S')
            cl = self.headers.get('Content-Length','0')
            req_body = ''
            if cl and int(cl) > 0:
                req_body = self.rfile.read(int(cl)).decode()
            print(f"  {c(DIM,t)} {c(YLW,self.command):<8} {c(CYN,self.path)}")
            if req_body: print(f"    {c(DIM,'body:')} {req_body[:100]}")
            self.send_response(resp_code)
            self.send_header('Content-Type', resp_ct)
            self.send_header('Content-Length', str(len(body_bytes)))
            self.send_header('Access-Control-Allow-Origin', '*')
            self.send_header('Access-Control-Allow-Methods', '*')
            self.send_header('Access-Control-Allow-Headers', '*')
            self.end_headers()
            self.wfile.write(body_bytes)
        def log_message(self, *a): pass
    section(f'Mock HTTP Server  ->  port {port}')
    print(f"\n  {c(B,'Status:')} {c(GRN if resp_code < 300 else YLW, str(resp_code))}")
    print(f"  {c(B,'Body:')}   {resp_body[:60]}")
    print(f"  {c(B,'URL:')}    {c(CYN, f'http://localhost:{port}')}")
    print(f"\n  {c(DIM,'Ctrl+C to stop')}\n")
    server = http.server.HTTPServer(('', port), MockHandler)
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        print(f"\n  {c(YLW,'Server stopped.')}\n")

def cmd_json(args):
    if args.input:
        raw = args.input
    else:
        print(f"{DIM}Paste JSON (Ctrl+D when done):{R}")
        raw = sys.stdin.read()
    try:
        parsed = json.loads(raw)
        kind = 'object' if isinstance(parsed,dict) else 'array'
        print(f"\n{c(GRN,'OK Valid JSON')} {c(DIM,f'({kind}, {len(parsed)} items, {format_size(len(raw))})')}\n")
        print(fmt_json(parsed))
        print()
    except json.JSONDecodeError as e:
        print(f"\n{c(RED,'X Invalid JSON:')} {e}\n")

def cmd_ping(args):
    url = args.url
    if not url.startswith('http'): url = 'https://'+url
    count = args.count or 4
    section(f'HTTP Ping -> {url}')
    print(f"  {c(DIM,f'({count} requests)')}\n")
    times = []
    for i in range(count):
        t0 = time.time()
        try:
            req = urllib.request.Request(url, headers={'User-Agent':'luckyget/2.0'}, method='HEAD')
            with urllib.request.urlopen(req, timeout=10) as r:
                ms = (time.time()-t0)*1000
                times.append(ms)
                bar = c(CYN,'|') * min(int(ms/15), 40)
                sc = status_color(r.status)
                print(f"  [{i+1}] {c(sc,str(r.status))}  {c(CYN,f'{ms:>7.1f}ms')}  {bar}")
        except Exception as e:
            print(f"  [{i+1}] {c(RED,'FAIL')}  {c(DIM,str(e)[:40])}")
        time.sleep(0.5)
    if times:
        print(f"\n  {c(B,'Min:')} {c(GRN,format_ms(min(times)))}  "
              f"{c(B,'Avg:')} {c(YLW,format_ms(sum(times)/len(times)))}  "
              f"{c(B,'Max:')} {c(RED,format_ms(max(times)))}\n")

def cmd_headers(args):
    url = args.url
    if not url.startswith('http'): url = 'https://'+url
    section(f'Headers -> {url}')
    print()
    try:
        req = urllib.request.Request(url, headers={'User-Agent':'luckyget/2.0'}, method='HEAD')
        with urllib.request.urlopen(req, timeout=10) as r:
            sc = status_color(r.status)
            print(f"  {c(B,'HTTP')} {c(sc, str(r.status)+' '+r.reason)}\n")
            highlight = {'content-type','server','x-powered-by','cache-control',
                         'strict-transport-security','x-frame-options','content-security-policy'}
            for k,v in r.headers.items():
                col = BLU if k.lower() in highlight else DIM
                print(f"  {c(col+B, k+':'):<35} {v}")
    except Exception as e:
        print(f"  {c(RED,'X')} {e}")
    print()

PROVIDERS = {
    'claude':     {'env':'ANTHROPIC_API_KEY',  'free':False},
    'openai':     {'env':'OPENAI_API_KEY',     'free':False},
    'gemini':     {'env':'GEMINI_API_KEY',     'free':True},
    'groq':       {'env':'GROQ_API_KEY',       'free':True},
    'mistral':    {'env':'MISTRAL_API_KEY',    'free':False},
    'openrouter': {'env':'OPENROUTER_API_KEY', 'free':True},
}

def ai_call(provider, api_key, sys_prompt, user_prompt):
    if provider == 'claude':
        payload = json.dumps({"model":"claude-sonnet-4-20250514","max_tokens":2000,
            "system":sys_prompt,"messages":[{"role":"user","content":user_prompt}]}).encode()
        req = urllib.request.Request("https://api.anthropic.com/v1/messages",data=payload,
            headers={"Content-Type":"application/json","x-api-key":api_key,"anthropic-version":"2023-06-01"},method="POST")
        with urllib.request.urlopen(req,timeout=60) as r:
            d=json.loads(r.read()); u=d.get('usage',{})
            return d['content'][0]['text'],u.get('input_tokens',0),u.get('output_tokens',0)
    elif provider in ('openai','groq','mistral','openrouter'):
        urls = {'openai':'https://api.openai.com/v1/chat/completions',
                'groq':'https://api.groq.com/openai/v1/chat/completions',
                'mistral':'https://api.mistral.ai/v1/chat/completions',
                'openrouter':'https://openrouter.ai/api/v1/chat/completions'}
        models = {'openai':'gpt-4o-mini','groq':'llama-3.3-70b-versatile',
                  'mistral':'mistral-small-latest','openrouter':'meta-llama/llama-3.3-70b-instruct:free'}
        payload = json.dumps({"model":models[provider],"max_tokens":2000,
            "messages":[{"role":"system","content":sys_prompt},{"role":"user","content":user_prompt}]}).encode()
        hdrs = {"Content-Type":"application/json","Authorization":f"Bearer {api_key}","User-Agent":"Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36"}
        if provider == 'openrouter': hdrs["HTTP-Referer"]="https://github.com/luckyget"
        req = urllib.request.Request(urls[provider],data=payload,headers=hdrs,method="POST")
        with urllib.request.urlopen(req,timeout=60) as r:
            d=json.loads(r.read()); u=d.get('usage',{})
            return d['choices'][0]['message']['content'],u.get('prompt_tokens',0),u.get('completion_tokens',0)
    elif provider == 'gemini':
        payload = json.dumps({"contents":[{"parts":[{"text":sys_prompt+"\n\n"+user_prompt}]}],
            "generationConfig":{"maxOutputTokens":2000}}).encode()
        url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key={api_key}"
        req = urllib.request.Request(url,data=payload,headers={"Content-Type":"application/json"},method="POST")
        with urllib.request.urlopen(req,timeout=60) as r:
            d=json.loads(r.read()); u=d.get('usageMetadata',{})
            return d['candidates'][0]['content']['parts'][0]['text'],u.get('promptTokenCount',0),u.get('candidatesTokenCount',0)
    raise ValueError(f"Unknown provider: {provider}")

def cmd_ai(args):
    mode = args.mode or 'chat'
    lang = args.lang or 'auto'
    provider = getattr(args,'provider',None) or ''

    if not provider:
        for p,info in PROVIDERS.items():
            if os.environ.get(info['env']):
                provider = p; break

    if not provider:
        section('Available AI Providers')
        print()
        for p,info in PROVIDERS.items():
            has = c(GRN,'KEY SET') if os.environ.get(info['env']) else c(DIM,'no key')
            free = c(GRN,'FREE') if info['free'] else c(YLW,'paid')
            print(f"  {c(B+CYN,p):<25} {free:<15} {has}  {c(DIM,info['env'])}")
        print(f"""
{c(B,'Get free keys:')}
  Groq (fastest):    {c(GRN,'console.groq.com')}
  Gemini:            {c(GRN,'aistudio.google.com')}
  OpenRouter:        {c(GRN,'openrouter.ai')}
""")
        return

    api_key = getattr(args,'key',None) or os.environ.get(PROVIDERS.get(provider,{}).get('env',''),'')
    if not api_key:
        print(f"\n{c(RED,f'X No key for {provider}!')}  export {PROVIDERS[provider]['env']}='...'\n"); return

    prompt = getattr(args,'prompt','') or ''
    if getattr(args,'file',None):
        try:
            with open(os.path.expanduser(args.file),'r',encoding='utf-8') as f:
                fc=f.read()
            fname=os.path.basename(args.file)
            print(f"  {c(DIM,'File:')} {fname} ({c(YLW,str(len(fc)))} chars, {c(YLW,str(fc.count(chr(10))))} lines)\n")
            prompt=(prompt+"\n\n" if prompt else "")+f"File: {fname}\n```\n{fc}\n```"
        except Exception as e:
            print(f"{c(RED,'X Cannot read file:')} {e}"); return

    if not prompt:
        print(f"{c(RED,'X')} Provide a prompt or --file"); return

    sys_prompt = {
        'code':    f"Expert programmer. Write clean working {lang} code with brief inline comments.",
        'explain': "Senior dev. Explain code clearly with bullet points. Be concise.",
        'fix':     "Debugger. Fix the code. Show fixed version + explain what was wrong.",
        'review':  "Code reviewer. Give structured feedback: good things, bugs, improvements, security.",
        'chat':    "Helpful programming assistant. Answer concisely.",
    }.get(mode,'Helpful programming assistant.')

    print(f"\n{c(DIM,'Asking')} {c(CYN+B,provider)}{c(DIM,'...')}\n")
    try:
        text,tok_in,tok_out = ai_call(provider,api_key,sys_prompt,prompt)
        print(f"{c(CYN+B,'=== '+provider.upper()+' ================================================')}")
        print(text)
        print(f"{c(CYN+B,'==================================================')} ")
        if tok_in or tok_out:
            print(f"\n{DIM}Tokens: {tok_in} in / {tok_out} out{R}\n")
    except urllib.error.HTTPError as e:
        err=e.read().decode()
        try: msg=json.loads(err).get('error',{}).get('message',err[:300])
        except: msg=err[:300]
        print(f"{c(RED,f'X {provider} {e.code}:')} {msg}\n")
    except Exception as e:
        import traceback; traceback.print_exc()
        print(f"{c(RED,'X AI failed:')} {type(e).__name__}: {e}\n")


def cmd_ping(args):
    url = args.url
    if not url.startswith('http'): url = 'https://'+url
    count = args.count or 4
    section(f'HTTP Ping -> {url}')
    print(f"  {c(DIM,f'({count} requests)')}\n")
    times = []
    for i in range(count):
        t0 = time.time()
        try:
            req = urllib.request.Request(url, headers={'User-Agent':'luckyget/2.0'}, method='HEAD')
            with urllib.request.urlopen(req, timeout=10) as r:
                ms = (time.time()-t0)*1000
                times.append(ms)
                bar = c(CYN,'|') * min(int(ms/15), 40)
                sc = status_color(r.status)
                print(f"  [{i+1}] {c(sc,str(r.status))}  {c(CYN,f'{ms:>7.1f}ms')}  {bar}")
        except Exception as e:
            print(f"  [{i+1}] {c(RED,'FAIL')}  {c(DIM,str(e)[:40])}")
        time.sleep(0.5)
    if times:
        print(f"\n  {c(B,'Min:')} {c(GRN,format_ms(min(times)))}  "
              f"{c(B,'Avg:')} {c(YLW,format_ms(sum(times)/len(times)))}  "
              f"{c(B,'Max:')} {c(RED,format_ms(max(times)))}\n")

def cmd_headers(args):
    url = args.url
    if not url.startswith('http'): url = 'https://'+url
    section(f'Headers -> {url}')
    print()
    try:
        req = urllib.request.Request(url, headers={'User-Agent':'luckyget/2.0'}, method='HEAD')
        with urllib.request.urlopen(req, timeout=10) as r:
            sc = status_color(r.status)
            print(f"  {c(B,'HTTP')} {c(sc, str(r.status)+' '+r.reason)}\n")
            highlight = {'content-type','server','x-powered-by','cache-control',
                         'strict-transport-security','x-frame-options','content-security-policy'}
            for k,v in r.headers.items():
                col = BLU if k.lower() in highlight else DIM
                print(f"  {c(col+B, k+':'):<35} {v}")
    except Exception as e:
        print(f"  {c(RED,'X')} {e}")
    print()

def print_help():
    banner()
    print(f"""{c(B+WHT,'COMMANDS:')}

  {c(CYN+B,'get/post/put/delete')} {c(DIM,'<url>')}    HTTP request (curl but beautiful)
  {c(CYN+B,'status')} {c(DIM,'<url> [url...]')}        Check multiple sites in parallel
  {c(CYN+B,'ping')} {c(DIM,'<url> [-n count]')}        HTTP latency bar chart
  {c(CYN+B,'headers')} {c(DIM,'<url>')}                Inspect response headers
  {c(CYN+B,'dns')} {c(DIM,'<host>')}                   DNS / IP + reverse lookup
  {c(CYN+B,'ports')} {c(DIM,'<host> [-p ports]')}      Scan open ports (parallel)
  {c(CYN+B,'ip')} {c(DIM,'[host]')}                    IP geolocation (yours or any)
  {c(CYN+B,'encode')} {c(DIM,'<text>')}                Base64 / URL / MD5 / SHA256 / SHA512
  {c(CYN+B,'json')} {c(DIM,'[-i <json>]')}             Format & validate JSON
  {c(CYN+B,'mock')} {c(DIM,'[-p port] [-b body]')}     Local mock HTTP server
  {c(CYN+B,'tg')} {c(DIM,'[--token T] [--updates]')}   Test Telegram bot
  {c(CYN+B,'ai')} {c(DIM,'<prompt>')}                  AI code gen / explain / fix / review

{c(B+WHT,'HTTP OPTIONS:')}
  -H "Key: Val"   header       -d "data"     body
  -j/--json       JSON mode    -b/--bearer   auth token
  -X METHOD       method       -v/--verbose  show headers
  --full          full body    -t/--timeout  seconds

{c(B+WHT,'AI OPTIONS:')}
  --mode  code|explain|fix|review|chat
  --lang  python|bash|js|...
  --key   sk-ant-...  (or: export ANTHROPIC_API_KEY=...)

{c(B+WHT,'EXAMPLES:')}
  {c(GRN,'luckyget get https://api.github.com/users/torvalds')}
  {c(GRN,'luckyget post https://httpbin.org/post -d (quote){"x":1}(quote) -j')}
  {c(GRN,'luckyget status google.com github.com cloudflare.com')}
  {c(GRN,'luckyget ports localhost -p 3000,5000,8080,8888')}
  {c(GRN,'luckyget ports scanme.nmap.org')}
  {c(GRN,'luckyget ip  /  luckyget ip 8.8.8.8')}
  {c(GRN,'luckyget encode "hello world"')}
  {c(GRN,'luckyget mock -p 3000 -b (quote){"user":"test"}(quote)')}
  {c(GRN,'luckyget tg --updates')}
  {c(GRN,'luckyget tg --chat 123456 --msg "Bot online!"')}
  {c(GRN,'luckyget ai "scrivi bash per backup" --lang bash')}
  {c(GRN,'luckyget ai "$(cat script.py)" --mode review')}

{c(DIM,'v2.0 | zero dependencies | pure Python stdlib')}
""")

def main():
    if len(sys.argv) < 2 or sys.argv[1] in ('-h','--help','help'):
        print_help(); return
    cmd = sys.argv[1].lower()
    rest = sys.argv[2:]

    if cmd in ('get','post','put','delete','patch','head'):
        p = argparse.ArgumentParser(add_help=False)
        p.add_argument('url')
        p.add_argument('-H','--header', action='append', dest='header')
        p.add_argument('-d','--data')
        p.add_argument('-j','--json', action='store_true')
        p.add_argument('-b','--bearer')
        p.add_argument('-X','--method')
        p.add_argument('-v','--verbose', action='store_true')
        p.add_argument('--full', action='store_true')
        p.add_argument('-t','--timeout', type=int)
        args = p.parse_args(rest)
        if not args.method: args.method = cmd.upper()
        cmd_get(args)
    elif cmd == 'status':
        p = argparse.ArgumentParser(add_help=False)
        p.add_argument('urls', nargs='+')
        p.add_argument('-t','--timeout', type=int)
        cmd_status(p.parse_args(rest))
    elif cmd == 'dns':
        p = argparse.ArgumentParser(add_help=False)
        p.add_argument('host')
        cmd_dns(p.parse_args(rest))
    elif cmd == 'ports':
        p = argparse.ArgumentParser(add_help=False)
        p.add_argument('host')
        p.add_argument('-p','--ports')
        p.add_argument('-t','--timeout', type=float)
        args = p.parse_args(rest)
        if args.ports: args.ports = [int(x) for x in args.ports.split(',')]
        cmd_ports(args)
    elif cmd == 'ip':
        p = argparse.ArgumentParser(add_help=False)
        p.add_argument('host', nargs='?', default='')
        cmd_ip(p.parse_args(rest))
    elif cmd == 'encode':
        p = argparse.ArgumentParser(add_help=False)
        p.add_argument('text')
        cmd_encode(p.parse_args(rest))
    elif cmd == 'json':
        p = argparse.ArgumentParser(add_help=False)
        p.add_argument('-i','--input', dest='input')
        cmd_json(p.parse_args(rest))
    elif cmd == 'mock':
        p = argparse.ArgumentParser(add_help=False)
        p.add_argument('-p','--port', type=int)
        p.add_argument('-b','--body')
        p.add_argument('-c','--code', type=int)
        p.add_argument('--ct')
        cmd_mock(p.parse_args(rest))
    elif cmd == 'tg':
        p = argparse.ArgumentParser(add_help=False)
        p.add_argument('--token')
        p.add_argument('--updates', action='store_true')
        p.add_argument('--chat')
        p.add_argument('--msg')
        cmd_tg(p.parse_args(rest))
    elif cmd == 'ai':
        p = argparse.ArgumentParser(add_help=False)
        p.add_argument('prompt', nargs='?', default='')
        p.add_argument('--file', '-f')
        p.add_argument('--provider', choices=['claude','openai','gemini','groq','mistral','cohere','openrouter'])
        p.add_argument('--lang')
        p.add_argument('--mode', choices=['code','explain','fix','review','chat'])
        p.add_argument('--key')
        cmd_ai(p.parse_args(rest))
    elif cmd == 'headers':
        p = argparse.ArgumentParser(add_help=False)
        p.add_argument('url')
        cmd_headers(p.parse_args(rest))
    elif cmd == 'ping':
        p = argparse.ArgumentParser(add_help=False)
        p.add_argument('url')
        p.add_argument('-n','--count', type=int)
        p.add_argument('-t','--timeout', type=int)
        cmd_ping(p.parse_args(rest))
    else:
        print(f"\n{c(RED,'X Unknown command:')} {cmd}")
        print(f"  Run {c(CYN,'luckyget help')} to see all commands.\n")

if __name__ == '__main__':
    main()
