import requests
import time
from google.protobuf import message as _message
from . import protos
from .utils import encrypt_message, bytes_to_hex
from .exceptions import (
    InvalidCredentialsError,
    InvalidPlatformError,
    TokenRetrievalError,
)

# Constants
GRANT_TOKEN_URL = "https://ffmconnect.live.gop.garenanow.com/oauth/guest/token/grant"
LOGIN_URL = "https://loginbp.ggblueshark.com/MajorLogin"
CLIENT_ID = "100067"
CLIENT_SECRET = "2ee44819e9b4598845141067b281621874d0d5d7af9d8f7e00c1e54715b7d1e3"


def _get_access_token(uid: str, password: str) -> dict:
    """Obtain an access token from Garena using guest credentials."""
    headers = {
        "Host": "100067.connect.garena.com",
        "User-Agent": "GarenaMSDK/4.0.19P4 (Vivo Y15c; Android 12; en;IN;)",
        "Content-Type": "application/x-www-form-urlencoded",
    }
    data = {
        "uid": uid,
        "password": password,
        "response_type": "token",
        "client_type": "2",
        "client_secret": CLIENT_SECRET,
        "client_id": CLIENT_ID,
    }
    try:
        resp = requests.post(GRANT_TOKEN_URL, headers=headers, data=data, timeout=10)
        resp.raise_for_status()
        return resp.json()
    except requests.RequestException as e:
        raise TokenRetrievalError(f"Failed to retrieve access token: {e}")


def _inspect_token(access_token: str) -> dict:
    """Validate an access token and obtain open_id and platform."""
    url = f"https://100067.connect.garena.com/oauth/token/inspect?token={access_token}"
    headers = {"User-Agent": "GarenaMSDK/4.0.19P4 (Vivo Y15c; Android 12; en;IN;)"}
    try:
        resp = requests.get(url, headers=headers, timeout=10)
        resp.raise_for_status()
        data = resp.json()
        if "open_id" not in data or "platform" not in data or "uid" not in data:
            raise InvalidCredentialsError("Invalid access token structure")
        return data
    except requests.RequestException as e:
        raise TokenRetrievalError(f"Token inspection failed: {e}")


def _build_game_data(open_id: str, access_token: str, uid: str = None,
                     platform_type: int = 4) -> protos.my_pb2.GameData:
    """Create a GameData protobuf with all required fields."""
    game_data = protos.my_pb2.GameData()
    # Hardcoded values taken from the original script
    game_data.timestamp = "2025-05-29 13:11:47"
    game_data.game_name = "free fire"
    game_data.game_version = 1
    game_data.version_code = "1.120.1"
    game_data.os_info = "Android OS 11 / API-30 (RKQ1.201112.002/eng.realme.20221110.193122)"
    game_data.device_type = "Handheld"
    game_data.network_provider = "JIO"
    game_data.connection_type = "MOBILE"
    game_data.screen_width = 720
    game_data.screen_height = 1600
    game_data.dpi = "280"
    game_data.cpu_info = "ARM Cortex-A73 | 2200 | 4"
    game_data.total_ram = 4096
    game_data.gpu_name = "Adreno (TM) 610"
    game_data.gpu_version = "OpenGL ES 3.2"
    game_data.user_id = uid if uid else "Google|c71ff1e2-457f-4e2d-83a1-c519fa3f2a44"
    game_data.ip_address = "182.75.115.22"
    game_data.language = "en"
    game_data.open_id = open_id
    game_data.access_token = access_token
    game_data.platform_type = platform_type
    game_data.device_form_factor = "Handheld"
    game_data.device_model = "realme RMX1825"
    game_data.field_60 = 30000
    game_data.field_61 = 27500
    game_data.field_62 = 1940
    game_data.field_63 = 720
    game_data.field_64 = 28000
    game_data.field_65 = 30000
    game_data.field_66 = 28000
    game_data.field_67 = 30000
    game_data.field_70 = 4
    game_data.field_73 = 2
    game_data.library_path = "/data/app/com.dts.freefireth-XaT5M7jRwEL-nPaKOQvqdg==/lib/arm"
    game_data.field_76 = 1
    game_data.apk_info = "2f4a7f349f3a3ea581fc4d803bc5a977|/data/app/com.dts.freefireth-XaT5M7jRwEL-nPaKOQvqdg==/base.apk"
    game_data.field_78 = 6
    game_data.field_79 = 1
    game_data.os_architecture = "64"
    game_data.build_number = "2022041388"
    game_data.field_85 = 1
    game_data.graphics_backend = "OpenGLES3"
    game_data.max_texture_units = 16383
    game_data.rendering_api = 4
    game_data.encoded_field_89 = "\x10U\x15\x03\x02\t\rPYN\tEX\x03AZO9X\x07\rU\niZPVj\x05\rm\t\x04c"
    game_data.field_92 = 8999
    game_data.marketplace = "3rd_party"
    game_data.encryption_key = "Jp2DT7F3Is55K/92LSJ4PWkJxZnMzSNn+HEBK2AFBDBdrLpWTA3bZjtbU3JbXigkIFFJ5ZJKi0fpnlJCPDD2A7h2aPQ="
    game_data.total_storage = 64000
    game_data.field_97 = 1
    game_data.field_98 = 1
    game_data.field_99 = str(platform_type)
    game_data.field_100 = str(platform_type).encode()
    return game_data


def _send_login_request(encrypted_data: bytes) -> protos.output_pb2.Lokesh:
    """Send the encrypted protobuf to the login endpoint and parse the response."""
    headers = {
        "User-Agent": "Dalvik/2.1.0 (Linux; U; Android 12; ASUS_Z01QD Build/PI)",
        "Connection": "Keep-Alive",
        "Accept-Encoding": "gzip",
        "Content-Type": "application/octet-stream",
        "Expect": "100-continue",
        "X-Unity-Version": "2018.4.11f1",
        "X-GA": "v1 1",
        "ReleaseVersion": "OB52"
    }
    try:
        resp = requests.post(LOGIN_URL, data=encrypted_data, headers=headers, timeout=10)
    except requests.RequestException as e:
        raise TokenRetrievalError(f"Login request failed: {e}")

    if resp.status_code != 200:
        error_text = resp.text.strip()
        if error_text == "BR_PLATFORM_INVALID_PLATFORM":
            raise InvalidPlatformError("Account is registered on another platform")
        elif error_text == "BR_GOP_TOKEN_AUTH_FAILED":
            raise InvalidCredentialsError("Access token invalid")
        elif error_text == "BR_PLATFORM_INVALID_OPENID":
            raise InvalidCredentialsError("OpenID invalid")
        else:
            raise TokenRetrievalError(f"Login failed: HTTP {resp.status_code} - {error_text}")

    # Parse the protobuf response
    response_proto = protos.output_pb2.Lokesh()
    try:
        response_proto.ParseFromString(resp.content)
    except Exception as e:
        raise TokenRetrievalError(f"Failed to parse response protobuf: {e}")

    return response_proto


def get_jwt_from_uid_password(uid: str, password: str) -> str:
    """
    Obtain a JWT using guest UID and password.

    Args:
        uid: The guest UID (string).
        password: The guest password (string).

    Returns:
        The JWT token as a string.

    Raises:
        InvalidCredentialsError: If the credentials are invalid.
        InvalidPlatformError: If the account is bound to another platform.
        TokenRetrievalError: For other errors.
    """
    # Step 1: get access token from Garena
    token_data = _get_access_token(uid, password)
    access_token = token_data.get("access_token")
    open_id = token_data.get("open_id")
    if not access_token or not open_id:
        raise InvalidCredentialsError("Access token or open_id missing in response")

    # Step 2: build GameData protobuf
    game_data = _build_game_data(open_id, access_token, uid=uid)

    # Step 3: encrypt and send
    serialized = game_data.SerializeToString()
    encrypted = encrypt_message(serialized)
    response_proto = _send_login_request(encrypted)

    # Step 4: extract token
    if not response_proto.token:
        raise TokenRetrievalError("No token in login response")
    return response_proto.token


def get_jwt_from_access_token(access_token: str) -> str:
    """
    Obtain a JWT using an existing Garena access token.

    Args:
        access_token: A valid Garena access token.

    Returns:
        The JWT token as a string.

    Raises:
        InvalidCredentialsError: If the access token is invalid.
        InvalidPlatformError: If the account is bound to another platform.
        TokenRetrievalError: For other errors.
    """
    # Step 1: inspect token to get open_id and platform
    inspect_data = _inspect_token(access_token)
    open_id = inspect_data["open_id"]
    platform_type = inspect_data.get("platform", 4)
    uid = str(inspect_data["uid"])

    # Step 2: build GameData
    game_data = _build_game_data(open_id, access_token, uid=uid, platform_type=platform_type)

    # Step 3: encrypt and send
    serialized = game_data.SerializeToString()
    encrypted = encrypt_message(serialized)
    response_proto = _send_login_request(encrypted)

    # Step 4: extract token
    if not response_proto.token:
        raise TokenRetrievalError("No token in login response")
    return response_proto.token


# Convenience wrapper
def get_jwt(uid: str = None, password: str = None, access_token: str = None) -> str:
    """
    Unified method to obtain a JWT.

    Provide either (uid and password) or access_token.
    """
    if access_token:
        return get_jwt_from_access_token(access_token)
    elif uid and password:
        return get_jwt_from_uid_password(uid, password)
    else:
        raise ValueError("Either (uid and password) or access_token must be provided")