# ============================================================
# HidMart - Direct Rubika Client
# Python 3.6+
# ============================================================

import os
import sys
import json
import base64
import hashlib
import secrets
import stat
import subprocess
import importlib.util
import time
import re
from datetime import datetime

ROOT = os.path.abspath(os.path.dirname(__file__))
PACKAGE = os.path.join(ROOT, "hidmart")
SESSIONS = os.path.join(ROOT, "sessions")

VERSION = "0.1.0"


# ------------------------------------------------------------
# File writer
# ------------------------------------------------------------

def write_file(path, content):
    """Write content to file with proper permissions"""
    os.makedirs(os.path.dirname(path), exist_ok=True)
    
    with open(path, "w", encoding="utf-8") as f:
        f.write(content)
    
    try:
        os.chmod(path, stat.S_IRUSR | stat.S_IWUSR)
    except Exception:
        pass


# ------------------------------------------------------------
# Build core files
# ------------------------------------------------------------

def build():
    """Create all necessary files and directories"""
    
    os.makedirs(PACKAGE, exist_ok=True)
    os.makedirs(SESSIONS, exist_ok=True)
    
    # __init__.py
    init_code = '''"""
HidMart - Rubika Client Library
"""

from .client import HidMart
from .crypto import Crypto
from .session import Session

__version__ = "0.1.0"
__all__ = ["HidMart", "Crypto", "Session"]
'''
    
    # client.py - FIXED VERSION
    client_code = '''"""
HidMart Client - Main interface for Rubika API
"""

import os
import json
import base64
import secrets
import hashlib
import time
import re
import requests

from .crypto import Crypto
from .session import Session


class HidMart:
    """Main client class for Rubika API"""
    
    API_VERSION = "6"
    
    CLIENT = {
        "app_name": "Main",
        "app_version": "2.4.6",
        "platform": "PWA",
        "package": "m.rubika.ir",
        "lang_code": "fa"
    }
    
    def __init__(self, session="main", api_url=None, timeout=30):
        """
        Initialize HidMart client
        
        Args:
            session: Session name (default: "main")
            api_url: Custom API URL (optional)
            timeout: Request timeout in seconds
        """
        self.session = Session(session)
        self.crypto = Crypto()
        self.timeout = timeout
        
        # Get API URL from session or use default
        self.api_url = api_url or self.session.get("api_url") or "https://messengerg2c17.iranlms.ir/"
        
        # Remove trailing slash if exists
        if self.api_url.endswith("/"):
            self.api_url = self.api_url[:-1]
        
        self.handlers = []
        self.http = requests.Session()
        self.http.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
            "Accept": "application/json",
            "Content-Type": "application/json"
        })
        
        # Auto-login with saved session
        self._auto_restore()
    
    # --------------------------------------------------------
    # Session management
    # --------------------------------------------------------
    
    def _auto_restore(self):
        """Restore session data from stored file"""
        self._guid = self.session.get("guid")
        self._auth = self.session.get("auth")
        self._private_key = self.session.get("private_key")
    
    @property
    def auth(self):
        return self._auth
    
    @property
    def private_key(self):
        return self._private_key
    
    @property
    def guid(self):
        return self._guid
    
    def logged_in(self):
        """Check if user is authenticated"""
        return bool(self._auth and self._guid)
    
    # --------------------------------------------------------
    # Internal helpers
    # --------------------------------------------------------
    
    def _tmp_session(self):
        """Get or create temporary session key"""
        value = self.session.get("tmp_session")
        if value:
            return value
        
        value = secrets.token_hex(16)
        self.session.set("tmp_session", value)
        return value
    
    def _request(self, envelope):
        """Send HTTP request to API"""
        try:
            response = self.http.post(
                self.api_url,
                json=envelope,
                timeout=self.timeout
            )
            response.raise_for_status()
            return response.json()
        except requests.exceptions.RequestException as e:
            raise RuntimeError("API request failed: " + str(e))
    
    def _decode_result(self, response, key):
        """Decrypt API response"""
        if not isinstance(response, dict):
            return response
        
        if "data_enc" not in response:
            return response
        
        try:
            raw = self.crypto.decrypt(response["data_enc"], key)
            return json.loads(raw)
        except Exception as e:
            # Return raw response if decryption fails
            return response
    
    # --------------------------------------------------------
    # Unauthenticated API calls
    # --------------------------------------------------------
    
    def temporary_call(self, method, input_data):
        """
        Make unauthenticated API call
        
        Args:
            method: API method name
            input_data: Input parameters
        
        Returns:
            API response
        """
        payload = {
            "method": method,
            "input": input_data,
            "client": self.CLIENT
        }
        
        key = self._tmp_session()
        
        # Encrypt payload
        encrypted = self.crypto.encrypt(
            json.dumps(payload, separators=(",", ":"), ensure_ascii=False),
            key
        )
        
        envelope = {
            "api_version": self.API_VERSION,
            "tmp_session": key,
            "data_enc": encrypted
        }
        
        result = self._request(envelope)
        return self._decode_result(result, key)
    
    # --------------------------------------------------------
    # Authenticated API calls
    # --------------------------------------------------------
    
    def call(self, method, input_data):
        """
        Make authenticated API call
        
        Args:
            method: API method name
            input_data: Input parameters
        
        Returns:
            API response
        """
        if not self.logged_in():
            raise RuntimeError("Session is not authenticated. Please login first.")
        
        payload = {
            "method": method,
            "input": input_data,
            "client": self.CLIENT
        }
        
        # Encrypt payload
        encrypted = self.crypto.encrypt(
            json.dumps(payload, separators=(",", ":"), ensure_ascii=False),
            self._auth
        )
        
        # Sign encrypted data
        signature = self.crypto.sign(encrypted, self._private_key)
        
        envelope = {
            "api_version": self.API_VERSION,
            "auth": self.crypto.transform_auth(self._auth),
            "data_enc": encrypted,
            "sign": signature
        }
        
        result = self._request(envelope)
        return self._decode_result(result, self._auth)
    
    # --------------------------------------------------------
    # Authentication
    # --------------------------------------------------------
    
    def send_code(self, phone_number, send_type="SMS"):
        """
        Send verification code to phone number
        
        Args:
            phone_number: Phone number (can be with or without country code)
            send_type: "SMS" or "CALL"
        
        Returns:
            API response with phone_code_hash
        """
        # Clean phone number
        phone = re.sub(r'[+\s\-]', '', str(phone_number))
        if phone.startswith("0"):
            phone = "98" + phone[1:]
        elif not phone.startswith("98") and len(phone) == 10:
            phone = "98" + phone
        
        result = self.temporary_call("sendCode", {
            "phone_number": phone,
            "send_type": send_type
        })
        
        # Store phone_code_hash for later use
        if isinstance(result, dict):
            data = result.get("data", result)
            if isinstance(data, dict):
                code_hash = data.get("phone_code_hash")
                if code_hash:
                    self.session.set("phone_code_hash", code_hash)
                    self.session.set("phone_number", phone)
        
        return result
    
    def sign_in(self, phone_number, phone_code):
        """
        Complete login with verification code
        
        Args:
            phone_number: Phone number (same as used in send_code)
            phone_code: Verification code received via SMS/CALL
        
        Returns:
            API response with authentication data
        """
        # Clean phone number
        phone = re.sub(r'[+\s\-]', '', str(phone_number))
        if phone.startswith("0"):
            phone = "98" + phone[1:]
        elif not phone.startswith("98") and len(phone) == 10:
            phone = "98" + phone
        
        # Get stored code hash
        phone_hash = self.session.get("phone_code_hash")
        if not phone_hash:
            raise RuntimeError("Phone code hash not found. Please run send_code first.")
        
        # Generate RSA key pair
        private_key, public_key = self.crypto.generate_rsa()
        
        result = self.temporary_call("signIn", {
            "phone_code": str(phone_code).strip(),
            "phone_number": phone,
            "phone_code_hash": phone_hash,
            "public_key": public_key
        })
        
        # Extract authentication data
        data = result.get("data", result) if isinstance(result, dict) else {}
        
        if isinstance(data, dict):
            encrypted_auth = data.get("auth") or data.get("auth_key") or data.get("auth_encrypted")
            
            if encrypted_auth:
                try:
                    # Decrypt authentication key
                    auth = self.crypto.decrypt_rsa(encrypted_auth, private_key)
                    if isinstance(auth, bytes):
                        auth = auth.decode()
                    
                    # Save session
                    self._auth = auth
                    self._private_key = private_key
                    
                    self.session.set("auth", auth)
                    self.session.set("private_key", private_key)
                    
                    # Get user info
                    try:
                        info = self.get_self_info()
                        if isinstance(info, dict):
                            data = info.get("data", info)
                            if isinstance(data, dict):
                                self._guid = data.get("guid")
                                self.session.set("guid", self._guid)
                    except:
                        pass
                    
                    # Clean up temporary data
                    self.session.delete("phone_code_hash")
                    self.session.delete("tmp_session")
                    
                    print("[HidMart] Successfully logged in as " + phone)
                    
                except Exception as e:
                    raise RuntimeError("Failed to decrypt authentication response: " + str(e))
        
        return result
    
    def get_self_info(self):
        """Get current user information"""
        return self.call("getSelf", {})
    
    # --------------------------------------------------------
    # Messaging
    # --------------------------------------------------------
    
    def send_message(self, chat_id, text, reply_to=None):
        """
        Send a message to a chat
        
        Args:
            chat_id: Chat GUID
            text: Message text
            reply_to: Message ID to reply to (optional)
        
        Returns:
            API response
        """
        data = {
            "object_guid": chat_id,
            "rnd": secrets.randbelow(800000) + 100000,
            "text": str(text)
        }
        if reply_to:
            data["reply_to_message_id"] = int(reply_to)
        
        return self.call("sendMessage", data)
    
    def get_messages(self, chat_id, limit=50, sort="FromMax"):
        """
        Get messages from a chat
        
        Args:
            chat_id: Chat GUID
            limit: Number of messages (max 100)
            sort: Sort order ("FromMax" or "FromMin")
        
        Returns:
            API response with messages
        """
        return self.call("getMessages", {
            "object_guid": chat_id,
            "sort": sort,
            "limit": min(int(limit), 100)
        })
    
    def get_chat_info(self, chat_id):
        """Get chat information"""
        return self.call("getChatInfo", {"object_guid": chat_id})
    
    # --------------------------------------------------------
    # Profile management
    # --------------------------------------------------------
    
    def get_user_info(self, user_guid):
        """Get user information by GUID"""
        return self.call("getUserInfo", {"user_guid": user_guid})
    
    def set_name(self, first_name, last_name=""):
        """Update user profile name"""
        return self.call("editProfile", {
            "first_name": str(first_name),
            "last_name": str(last_name)
        })
    
    def set_bio(self, bio):
        """Update user bio"""
        return self.call("editProfile", {"bio": str(bio)})
    
    # --------------------------------------------------------
    # Message handlers
    # --------------------------------------------------------
    
    def on_message(self, callback):
        """
        Decorator to register message handler
        
        Usage:
            @client.on_message
            def handle(msg):
                print(msg)
        """
        if not callable(callback):
            raise TypeError("Callback must be callable")
        
        self.handlers.append(callback)
        return callback
    
    def _emit(self, message):
        """Call all registered handlers with message"""
        for callback in self.handlers:
            try:
                callback(message)
            except Exception as e:
                print("[HidMart] Handler error: " + str(e))
    
    # --------------------------------------------------------
    # Polling
    # --------------------------------------------------------
    
    def run(self, interval=3):
        """
        Start polling for new messages
        
        Args:
            interval: Polling interval in seconds
        """
        if not self.logged_in():
            raise RuntimeError("Not logged in. Please login first.")
        
        print("[HidMart] Started polling...")
        print("[HidMart] Press Ctrl+C to stop")
        
        last_message_id = 0
        
        try:
            while True:
                try:
                    # Get recent messages
                    result = self.call("getMessages", {
                        "object_guid": self._guid,
                        "sort": "FromMax",
                        "limit": 10
                    })
                    
                    # Process messages
                    if isinstance(result, dict):
                        data = result.get("data", result)
                        if isinstance(data, dict):
                            messages = data.get("messages", [])
                            if messages:
                                # Process new messages
                                self._emit({"messages": messages})
                                
                                # Update last message ID
                                for msg in messages:
                                    if msg.get("id", 0) > last_message_id:
                                        last_message_id = msg.get("id", 0)
                    
                    time.sleep(interval)
                    
                except KeyboardInterrupt:
                    raise
                except Exception as e:
                    print("[HidMart] Polling error: " + str(e))
                    time.sleep(interval * 2)
                    
        except KeyboardInterrupt:
            print("\\n[HidMart] Stopped polling")
'''
    
    # crypto.py
    crypto_code = '''"""
Cryptography module for Rubika API
"""

import base64
import hashlib
import os

# Try to import Crypto, fallback to Cryptodome
try:
    from Crypto.Cipher import AES
    from Crypto.PublicKey import RSA
    from Crypto.Signature import pkcs1_15
    from Crypto.Hash import SHA256
    from Crypto.Util.Padding import pad, unpad
    from Crypto.Cipher import PKCS1_OAEP
    CRYPTO_LIB = "pycryptodome"
except ImportError:
    try:
        from Cryptodome.Cipher import AES
        from Cryptodome.PublicKey import RSA
        from Cryptodome.Signature import pkcs1_15
        from Cryptodome.Hash import SHA256
        from Cryptodome.Util.Padding import pad, unpad
        from Cryptodome.Cipher import PKCS1_OAEP
        CRYPTO_LIB = "pycryptodomex"
    except ImportError:
        raise ImportError("Please install pycryptodome or pycryptodomex")


class Crypto:
    """Cryptography operations for Rubika"""
    
    @staticmethod
    def _key(value):
        """Generate SHA256 key from value"""
        if isinstance(value, str):
            value = value.encode()
        return hashlib.sha256(value).digest()
    
    def encrypt(self, text, key):
        """
        Encrypt text using AES-CBC
        
        Args:
            text: Text to encrypt
            key: Encryption key
        
        Returns:
            Base64 encoded encrypted data
        """
        key = self._key(key)
        iv = key[:16]
        
        cipher = AES.new(key, AES.MODE_CBC, iv)
        encrypted = cipher.encrypt(pad(text.encode("utf-8"), AES.block_size))
        
        return base64.b64encode(encrypted).decode()
    
    def decrypt(self, value, key):
        """
        Decrypt AES-CBC encrypted data
        
        Args:
            value: Base64 encoded encrypted data
            key: Decryption key
        
        Returns:
            Decrypted text
        """
        key = self._key(key)
        iv = key[:16]
        
        raw = base64.b64decode(value)
        cipher = AES.new(key, AES.MODE_CBC, iv)
        
        return unpad(cipher.decrypt(raw), AES.block_size).decode("utf-8")
    
    @staticmethod
    def generate_rsa():
        """
        Generate RSA key pair
        
        Returns:
            Tuple of (private_key_pem, public_key_b64)
        """
        key = RSA.generate(2048)
        
        private = key.export_key(format="PEM").decode()
        public = base64.b64encode(key.publickey().export_key(format="DER")).decode()
        
        return private, public
    
    @staticmethod
    def decrypt_rsa(encrypted, private_key):
        """
        Decrypt RSA-encrypted data
        
        Args:
            encrypted: Base64 encoded encrypted data
            private_key: Private key in PEM format
        
        Returns:
            Decrypted data
        """
        if isinstance(encrypted, str):
            encrypted = base64.b64decode(encrypted)
        
        key = RSA.import_key(private_key)
        cipher = PKCS1_OAEP.new(key)
        
        return cipher.decrypt(encrypted)
    
    @staticmethod
    def sign(data, private_key):
        """
        Sign data using RSA with SHA256
        
        Args:
            data: Data to sign
            private_key: Private key in PEM format
        
        Returns:
            Base64 encoded signature
        """
        key = RSA.import_key(private_key)
        
        if isinstance(data, str):
            data = data.encode()
        
        digest = SHA256.new(data)
        signature = pkcs1_15.new(key).sign(digest)
        
        return base64.b64encode(signature).decode()
    
    @staticmethod
    def transform_auth(auth):
        """Transform auth key for API"""
        if isinstance(auth, str):
            auth = auth.encode()
        return base64.b64encode(auth).decode()
'''
    
    # session.py
    session_code = '''"""
Session management for HidMart
"""

import os
import json
import stat
import time


class Session:
    """Session storage handler"""
    
    def __init__(self, name="main"):
        """
        Initialize session
        
        Args:
            name: Session name
        """
        self.root = os.path.abspath(
            os.path.join(os.path.dirname(__file__), "..", "sessions")
        )
        os.makedirs(self.root, exist_ok=True)
        
        self.path = os.path.join(self.root, name + ".json")
        self.data = {}
        self.load()
    
    def load(self):
        """Load session data from file"""
        if not os.path.exists(self.path):
            return
        
        try:
            with open(self.path, "r", encoding="utf-8") as f:
                self.data = json.load(f)
        except Exception:
            self.data = {}
    
    def save(self):
        """Save session data to file"""
        tmp = self.path + ".tmp"
        
        try:
            with open(tmp, "w", encoding="utf-8") as f:
                json.dump(self.data, f, ensure_ascii=False, indent=2)
            
            # Set secure permissions
            try:
                os.chmod(tmp, stat.S_IRUSR | stat.S_IWUSR)
            except Exception:
                pass
            
            os.replace(tmp, self.path)
            
        except Exception as e:
            # Try to save without replacement
            try:
                with open(self.path, "w", encoding="utf-8") as f:
                    json.dump(self.data, f, ensure_ascii=False, indent=2)
            except:
                raise RuntimeError("Failed to save session: " + str(e))
    
    def get(self, key, default=None):
        """Get a value from session"""
        return self.data.get(key, default)
    
    def set(self, key, value):
        """Set a value in session"""
        self.data[key] = value
        self.save()
    
    def delete(self, key):
        """Delete a key from session"""
        if key in self.data:
            del self.data[key]
            self.save()
    
    def clear(self):
        """Clear all session data"""
        self.data = {}
        self.save()
    
    def __contains__(self, key):
        """Check if key exists in session"""
        return key in self.data
'''
    
    requirements = """requests>=2.25.0
pycryptodome>=3.10.0
"""
    
    # Write all files
    write_file(os.path.join(PACKAGE, "__init__.py"), init_code)
    write_file(os.path.join(PACKAGE, "client.py"), client_code)
    write_file(os.path.join(PACKAGE, "crypto.py"), crypto_code)
    write_file(os.path.join(PACKAGE, "session.py"), session_code)
    write_file(os.path.join(ROOT, "requirements.txt"), requirements)
    write_file(os.path.join(SESSIONS, ".gitignore"), "*\n!.gitignore\n")
    
    # Login script
    login_script = '''#!/usr/bin/env python3
"""
Quick login script for HidMart
"""

import sys
import os

sys.path.insert(0, os.path.dirname(__file__))

from hidmart import HidMart

def main():
    print("=" * 50)
    print("HidMart Login")
    print("=" * 50)
    
    client = HidMart()
    
    if client.logged_in():
        print("Already logged in as: " + str(client.guid))
        return
    
    phone = input("Enter phone number: ").strip()
    
    print("Sending verification code...")
    result = client.send_code(phone)
    
    if "error" in str(result).lower():
        print("Error: " + str(result))
        return
    
    code = input("Enter verification code: ").strip()
    
    print("Logging in...")
    result = client.sign_in(phone, code)
    
    if client.logged_in():
        print("Login successful!")
        print("GUID: " + str(client.guid))
    else:
        print("Login failed")
        print(result)

if __name__ == "__main__":
    main()
'''
    
    write_file(os.path.join(ROOT, "login.py"), login_script)
    try:
        os.chmod(os.path.join(ROOT, "login.py"), 0o755)
    except:
        pass
    
    print("[HidMart] All files created successfully.")
    print("[HidMart] Package: " + PACKAGE)
    print("[HidMart] Sessions: " + SESSIONS)


# ------------------------------------------------------------
# Dependency installer
# ------------------------------------------------------------

def install():
    """Install required dependencies"""
    packages = []
    
    # Check requests
    try:
        import requests
    except ImportError:
        packages.append("requests")
    
    # Check Crypto
    try:
        import Crypto
    except ImportError:
        try:
            import Cryptodome
        except ImportError:
            packages.append("pycryptodome")
    
    if not packages:
        print("[HidMart] All dependencies are already installed.")
        return
    
    print("[HidMart] Installing dependencies: " + ", ".join(packages))
    
    # Try to install with --user first
    try:
        subprocess.check_call([
            sys.executable,
            "-m",
            "pip",
            "install",
            "--user",
            "--no-cache-dir",
            *packages
        ])
        print("[HidMart] Dependencies installed successfully.")
        return
    except Exception as e:
        print("[HidMart] Install with --user failed: " + str(e))
    
    # Try without --user
    try:
        subprocess.check_call([
            sys.executable,
            "-m",
            "pip",
            "install",
            "--no-cache-dir",
            *packages
        ])
        print("[HidMart] Dependencies installed successfully.")
        return
    except Exception as e:
        print("[HidMart] Install without --user failed: " + str(e))
    
    # Try alternative package
    if "pycryptodome" in packages:
        try:
            print("[HidMart] Trying pycryptodomex...")
            subprocess.check_call([
                sys.executable,
                "-m",
                "pip",
                "install",
                "--user",
                "--no-cache-dir",
                "pycryptodomex"
            ])
            packages.remove("pycryptodome")
            packages.append("pycryptodomex")
            print("[HidMart] Dependencies installed with pycryptodomex.")
            return
        except Exception as e:
            print("[HidMart] Failed to install pycryptodomex: " + str(e))
    
    print("[HidMart] Please install dependencies manually:")
    print("pip install --user " + " ".join(packages))


# ------------------------------------------------------------
# Main
# ------------------------------------------------------------

def main():
    """Main entry point"""
    print()
    print("=" * 50)
    print("         HidMart - Rubika Client")
    print("              Version " + VERSION)
    print("=" * 50)
    print()
    
    # Build files
    build()
    
    # Install dependencies
    try:
        install()
    except Exception as e:
        print("[HidMart] Dependency installation failed: " + str(e))
        print("[HidMart] You can install manually with:")
        print("pip install --user requests pycryptodome")
        return
    
    # Check if we can import the library
    try:
        sys.path.insert(0, ROOT)
        from hidmart import HidMart
        
        client = HidMart()
        
        if client.logged_in():
            print("")
            print("[HidMart] Session found!")
            print("[HidMart] GUID: " + str(client.guid))
            print("")
            print("[HidMart] Ready to use!")
            print("")
            print("Quick examples:")
            print("  from hidmart import HidMart")
            print("  client = HidMart()")
            print("  client.send_message('GUID', 'Hello!')")
            print("  client.run()  # Start polling")
        else:
            print("")
            print("[HidMart] No active session found.")
            print("")
            print("To login, run:")
            print("  python3 login.py")
            print("")
            print("Or use in your code:")
            print("  from hidmart import HidMart")
            print("  client = HidMart()")
            print("  client.send_code('09123456789')")
            print("  client.sign_in('09123456789', '12345')")
            
    except Exception as e:
        print("[HidMart] Error: " + str(e))


if __name__ == "__main__":
    main()