import requests
import base64
import json
import time
from datetime import datetime, timezone, timedelta
from urllib.parse import urlparse, parse_qs
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from . import my_pb2
from . import output_pb2
from functools import lru_cache

# ==================== CONSTANTS ====================
KEY = bytes([89, 103, 38, 116, 99, 37, 68, 69, 117, 104, 54, 37, 90, 99, 94, 56])
IV = bytes([54, 111, 121, 90, 68, 114, 50, 50, 69, 51, 121, 99, 104, 106, 77, 37])

PLATFORM_MAP = {
    3: "Facebook",
    4: "Guest",
    5: "VK",
    8: "Google",
    10: "AppleId",
    11: "X (Twitter)"
}

# 🔁 GitHub se versions fetch karne ka URL (apne username se replace karein)
VERSIONS_URL = "https://raw.githubusercontent.com/dangerapix/danger-ffjwt/main/versions.json"

SESSION = requests.Session()

# ==================== VERSION FETCH ====================
@lru_cache(maxsize=1)
def fetch_latest_versions():
    """
    GitHub se latest OB version aur client version fetch karta hai.
    Agar fetch fail ho to default values return karega.
    """
    try:
        resp = SESSION.get(VERSIONS_URL, timeout=5)
        if resp.status_code == 200:
            data = resp.json()
            ob_version = data.get("ob_version", "OB52")
            client_version = data.get("client_version", "1.120.1")
            return ob_version, client_version
    except Exception as e:
        print(f"[WARN] Version fetch failed: {e}, using defaults.")
    return "OB52", "1.120.1"   # fallback defaults

# ==================== UTILITY FUNCTIONS ====================
def convert_timestamp(timestamp_seconds):
    """Unix timestamp ko human readable UTC/IST aur time remaining mein convert kare."""
    try:
        utc_time = datetime.fromtimestamp(timestamp_seconds, tz=timezone.utc)
        ist_time = utc_time + timedelta(hours=5, minutes=30)
        current = int(time.time())
        remaining = timestamp_seconds - current
        days = remaining // (24*3600)
        hours = (remaining % (24*3600)) // 3600
        minutes = (remaining % 3600) // 60
        seconds = remaining % 60
        return {
            "timestamp": timestamp_seconds,
            "utc": utc_time.strftime("%Y-%m-%d %H:%M:%S UTC"),
            "ist": ist_time.strftime("%Y-%m-%d %H:%M:%S IST"),
            "remaining_seconds": remaining,
            "remaining_human": f"{days}d {hours}h {minutes}m {seconds}s" if remaining>0 else "Expired",
            "is_expired": remaining <= 0
        }
    except Exception as e:
        return {"error": str(e)}

def get_platform_name(platform_type):
    """Platform type (int) ka readable naam return kare."""
    return PLATFORM_MAP.get(platform_type, "Unknown")

def inspect_access_token(access_token):
    """Garena inspect endpoint se token info fetch kare."""
    try:
        resp = SESSION.get(
            f"https://100067.connect.garena.com/oauth/token/inspect?token={access_token}",
            timeout=15,
            verify=False
        )
        data = resp.json()
        if 'open_id' in data and 'platform' in data and 'uid' in data:
            if 'expiry_time' in data:
                data['expiry_info'] = convert_timestamp(data['expiry_time'])
            return data
    except Exception as e:
        print(f"[ERROR] inspect_access_token: {e}")
    return None

def decode_jwt(jwt_token):
    """JWT token ka payload decode kare."""
    try:
        parts = jwt_token.split('.')
        if len(parts) != 3:
            return None
        payload = parts[1]
        padding = 4 - (len(payload) % 4)
        if padding != 4:
            payload += '=' * padding
        decoded = base64.urlsafe_b64decode(payload).decode('utf-8')
        data = json.loads(decoded)
        if 'exp' in data:
            data['expiry_info'] = convert_timestamp(data['exp'])
        if 'iat' in data:
            data['issued_at_info'] = convert_timestamp(data['iat'])
        return data
    except Exception as e:
        print(f"[ERROR] decode_jwt: {e}")
        return None

def guest_to_access(uid, password):
    """Guest UID/password se access_token aur open_id prapt kare."""
    headers = {
        "Host": "100067.connect.garena.com",
        "User-Agent": "GarenaMSDK/4.0.19P4(G011A ;Android 9;en;US;)",
        "Content-Type": "application/x-www-form-urlencoded",
        "Accept-Encoding": "gzip, deflate, br",
        "Connection": "close"
    }
    data = {
        "uid": str(uid),
        "password": str(password),
        "response_type": "token",
        "client_type": "2",
        "client_secret": "2ee44819e9b4598845141067b281621874d0d5d7af9d8f7e00c1e54715b7d1e3",
        "client_id": "100067"
    }
    try:
        response = SESSION.post("https://100067.connect.garena.com/oauth/guest/token/grant",
                                headers=headers, data=data, verify=False)
        data_response = response.json()
        if data_response.get("success") is True:
            resp = data_response.get("response", {})
            if resp.get("error") == "auth_error":
                return {"success": False, "error": "auth_error", "message": "Invalid guest credentials"}
        access_token = data_response.get("access_token")
        open_id = data_response.get("open_id")
        if access_token and open_id:
            return {
                "success": True,
                "access_token": access_token,
                "open_id": open_id,
                "uid": str(uid)
            }
        return {"success": False, "error": "unknown", "message": "Failed to get access_token"}
    except Exception as e:
        return {"success": False, "error": "exception", "message": str(e)}

# ==================== INTERNAL LOGIN (JWT GENERATION) ====================
def _login(uid, access_token, open_id, platform_type, ob_version, client_version):
    """
    Internal function to actually call MajorLogin endpoint and get JWT.
    Returns JWT string on success, error dict on failure.
    """
    url = "https://loginbp.ggpolarbear.com/MajorLogin"
    game_data = my_pb2.GameData()
    game_data.timestamp = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
    game_data.game_name = "Free Fire"
    game_data.game_version = 1
    game_data.version_code = client_version
    game_data.os_info = "iOS 18.4"
    game_data.device_type = "Handheld"
    game_data.network_provider = "Verizon Wireless"
    game_data.connection_type = "WIFI"
    game_data.screen_width = 1170
    game_data.screen_height = 2532
    game_data.dpi = "460"
    game_data.cpu_info = "Apple A15 Bionic"
    game_data.total_ram = 6144
    game_data.gpu_name = "Apple GPU (5-core)"
    game_data.gpu_version = "Metal 3"
    game_data.user_id = str(uid)
    game_data.ip_address = "172.190.111.97"
    game_data.language = "en"
    game_data.open_id = str(open_id)
    game_data.access_token = str(access_token)
    game_data.platform_type = int(platform_type)
    game_data.field_99 = str(platform_type)
    game_data.field_100 = str(platform_type)

    serialized = game_data.SerializeToString()
    padded = pad(serialized, AES.block_size)
    cipher = AES.new(KEY, AES.MODE_CBC, IV)
    encrypted = cipher.encrypt(padded)

    headers = {
        "User-Agent": "Dalvik/2.1.0 (Linux; U; Android 9; ASUS_Z01QD Build/PI)",
        "Connection": "Keep-Alive",
        "Accept-Encoding": "gzip",
        "Content-Type": "application/octet-stream",
        "Expect": "100-continue",
        "X-GA": "v1 1",
        "X-Unity-Version": "2018.4.11f1",
        "ReleaseVersion": ob_version,
        "Content-Length": str(len(encrypted))
    }

    try:
        resp = SESSION.post(url, data=encrypted, headers=headers, timeout=30, verify=False)
        if resp.status_code == 200:
            jwt_msg = output_pb2.Garena_420()
            jwt_msg.ParseFromString(resp.content)
            if jwt_msg.token:
                return jwt_msg.token
        else:
            error_text = resp.content.decode().strip()
            if error_text == "BR_PLATFORM_INVALID_PLATFORM":
                return {"error": "INVALID_PLATFORM", "message": "Account registered on another platform"}
            elif error_text == "BR_GOP_TOKEN_AUTH_FAILED":
                return {"error": "INVALID_TOKEN", "message": "AccessToken invalid"}
            elif error_text == "BR_PLATFORM_INVALID_OPENID":
                return {"error": "INVALID_OPENID", "message": "OpenID invalid"}
            else:
                return {"error": "UNKNOWN", "message": error_text}
    except Exception as e:
        return {"error": "EXCEPTION", "message": str(e)}
    return None   # should not reach here

# ==================== PUBLIC FUNCTIONS ====================
def access_to_jwt(access_token, open_id=None, platform_type=None, ob_version=None, client_version=None):
    """
    Access token se JWT generate kare.
    Agar open_id/platform_type nahi diye to inspect se fetch karega.
    """
    # Agar versions nahi diye to fetch karo
    if ob_version is None or client_version is None:
        ob_version, client_version = fetch_latest_versions()

    # Token inspect karo agar open_id/platform_type nahi diye
    if not open_id or not platform_type:
        info = inspect_access_token(access_token)
        if not info:
            return {"success": False, "error": "INVALID_TOKEN", "message": "Access token invalid or expired"}
        open_id = info.get('open_id')
        platform_type = info.get('platform', 4)
        uid = info.get('uid')
    else:
        # Still need uid (inspect se le lenge)
        info = inspect_access_token(access_token)
        if not info:
            return {"success": False, "error": "INVALID_TOKEN", "message": "Access token invalid or expired"}
        uid = info.get('uid')

    if not open_id:
        return {"success": False, "error": "MISSING_OPENID", "message": "Could not determine open_id"}

    jwt_result = _login(uid, access_token, open_id, platform_type, ob_version, client_version)
    if isinstance(jwt_result, dict) and 'error' in jwt_result:
        return {"success": False, "error": jwt_result['error'], "message": jwt_result['message']}
    if not jwt_result:
        return {"success": False, "error": "JWT_FAILED", "message": "JWT generation failed"}

    decoded = decode_jwt(jwt_result)
    return {
        "success": True,
        "jwt_token": jwt_result,
        "decoded": decoded,
        "access_token": access_token,
        "open_id": open_id,
        "platform_type": platform_type,
        "platform_name": get_platform_name(platform_type),
        "uid": str(uid),
        "ob_version_used": ob_version,
        "client_version_used": client_version
    }

def eat_to_access(eat_token):
    """
    EAT token se access_token aur user details prapt kare.
    """
    try:
        callback_url = f"https://api-otrss.garena.com/support/callback/?access_token={eat_token}"
        response = SESSION.get(callback_url, allow_redirects=True, timeout=30, verify=False)

        if 'help.garena.com' in response.url:
            parsed = urlparse(response.url)
            params = parse_qs(parsed.query)
            access_token = params.get('access_token', [None])[0]
            if not access_token:
                return {"success": False, "error": "NO_ACCESS_TOKEN", "message": "No access_token in redirect URL"}

            token_data = inspect_access_token(access_token)
            if not token_data:
                return {"success": False, "error": "INSPECT_FAILED", "message": "Access token invalid after conversion"}

            platform_type = token_data.get('platform', 4)
            platform_name = get_platform_name(platform_type)

            result = {
                "success": True,
                "eat_token": eat_token,
                "access_token": access_token,
                "region": params.get('region', [None])[0],
                "game_uid": params.get('account_id', [None])[0],
                "nickname": params.get('nickname', [None])[0],
                "game": params.get('game', ['FF'])[0],
                "language": params.get('lang', ['en'])[0],
                "platform_type": platform_type,
                "platform_name": platform_name,
                "open_id": token_data.get('open_id'),
                "uid": token_data.get('uid'),
            }
            if 'expiry_info' in token_data:
                result['access_token_expiry'] = token_data['expiry_info']
            return result
        else:
            return {"success": False, "error": "INVALID_EAT", "message": "EAT token conversion failed"}
    except Exception as e:
        return {"success": False, "error": "EXCEPTION", "message": str(e)}

def guest_to_jwt(uid, password, ob_version=None, client_version=None):
    """
    Guest UID/password se direct JWT generate kare.
    """
    if ob_version is None or client_version is None:
        ob_version, client_version = fetch_latest_versions()

    guest = guest_to_access(uid, password)
    if not guest.get('success'):
        return guest

    # Guest platform always 4
    return access_to_jwt(guest['access_token'], open_id=guest['open_id'], platform_type=4,
                         ob_version=ob_version, client_version=client_version)

def eat_to_jwt(eat_token, ob_version=None, client_version=None):
    """
    EAT token se direct JWT generate kare.
    """
    if ob_version is None or client_version is None:
        ob_version, client_version = fetch_latest_versions()

    eat = eat_to_access(eat_token)
    if not eat.get('success'):
        return eat

    # Use data from eat conversion
    jwt_res = access_to_jwt(eat['access_token'],
                            open_id=eat.get('open_id'),
                            platform_type=eat.get('platform_type'),
                            ob_version=ob_version,
                            client_version=client_version)

    if jwt_res.get('success'):
        # Add eat_info for reference (excluding success and access_token)
        jwt_res['eat_info'] = {k: v for k, v in eat.items() if k not in ['success', 'access_token']}
    return jwt_res