# ===================
# Lufix Uploader [https://t.me/Moonlightcrow]
# Coded By Who Knows
# Thanks to ststack overflow and github {cause i'm not Good at GUI }
# ===================

import sys
import json
import random
import threading
import requests
from concurrent.futures import ThreadPoolExecutor
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
from PyQt6.QtCore import QObject, QThread, pyqtSignal, Qt
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
    QComboBox, QTextEdit, QPushButton, QProgressBar, QLabel, QPlainTextEdit,
    QMessageBox, QLineEdit, QGroupBox, QFileDialog, QFormLayout, QSpinBox,
    QDoubleSpinBox
)



# WORKER CLASS

class UploaderWorker(QObject):
    finished = pyqtSignal()
    progress = pyqtSignal(int)
    log_message = pyqtSignal(str)
    update_stats = pyqtSignal(int, int)

    def __init__(self, items_to_upload, upload_type, settings, thread_count, min_price, max_price):
        super().__init__()
        self.items = items_to_upload
        self.upload_type = upload_type
        self.settings = settings
        self.thread_count = thread_count
        self.min_price = min_price
        self.max_price = max_price
        self.is_running = True
        self.file_lock = threading.Lock()

    def run(self):
        """The main job of the worker."""
        total_items = len(self.items)
        success_count = 0
        failure_count = 0

        with ThreadPoolExecutor(max_workers=self.thread_count) as executor:
            for i, result in enumerate(executor.map(self._process_one_item, self.items)):
                if not self.is_running:
                    self.log_message.emit("INFO: Upload process was cancelled by the user.")
                    break  # Exit

                if result and result.startswith("[SUCCESS]"):
                    success_count += 1
                else:
                    failure_count += 1

                self.log_message.emit(result or "[ERROR] An unexpected error .")

                # Calculate and send progress updates
                progress_percentage = int(((i + 1) / total_items) * 100)
                self.progress.emit(progress_percentage)
                self.update_stats.emit(success_count, failure_count)

        self.finished.emit()

    def _save_to_file(self, filename, text_to_append):
        """A thread-safe write"""
        with self.file_lock:
            with open(filename, 'a', encoding='utf-8') as f:
                f.write(text_to_append + '\n')

    def _process_one_item(self, item_data):
        cfg = self.settings['CONFIG'][self.upload_type]
        full_url = self.settings['BASE_URL'] + cfg['endpoint']
        item_name = self.upload_type.capitalize()
        
        # Generate a random price between min and max
        price = round(random.uniform(self.min_price, self.max_price), 2)
        
        data = cfg['data_template'].copy()
        data['price'] = str(price)
        data['list'] = item_data
        
        headers = self.settings['COMMON_HEADERS'].copy()
        headers['Referer'] = cfg['referer']
        headers['X-Csrf-Token'] = self.settings['csrf_token']

        try:
            response = requests.post(
                full_url,
                headers=headers,
                cookies=self.settings['cookies'],
                data=data,
                timeout=20,
                verify=False
            )
            response.raise_for_status()
            response_data = response.json()
            if response_data.get("state") == "true" and "results" in response_data and response_data["results"]:
                message = response_data["results"][0].get("message", "Added Successfully")
                if "Added Successfully" in message:
                    self._save_to_file(f"{self.upload_type}_added.txt", item_data)
                    return f"[SUCCESS] [{item_name}] {item_data} -> {message} (Price: ${price:.2f})"
                elif "already" in message.lower():
                    self._save_to_file(f"{self.upload_type}_already_added.txt", item_data)
                    return f"[SKIPPED] [{item_name}] {item_data} -> {message} (Price: ${price:.2f})"
                else:
                    self._save_to_file(f"{self.upload_type}_others.txt", item_data)
                    return f"[FAILED]  [{item_name}] {item_data} -> Unexpected Response: {message}"
            else:
                error_message = response_data.get('message', 'Unknown server error')
                self._save_to_file(f"{self.upload_type}_others.txt", item_data)
                return f"[FAILED]  [{item_name}] {item_data} -> {error_message}"

        except requests.exceptions.RequestException as e:
            self._save_to_file(f"{self.upload_type}_others.txt", item_data)
            return f"[ERROR]   [{item_name}] {item_data} -> Network error: {e}"
        except json.JSONDecodeError:
            error_text = response.text if response else "No response"
            self._save_to_file(f"{self.upload_type}_others.txt", item_data)
            return f"[ERROR]   [{item_name}] {item_data} -> Couldn't understand server response: {error_text[:200]}"

    def stop(self):
        self.is_running = False


class UploaderLogic(QObject):
    log_message = pyqtSignal(str)
    progress_updated = pyqtSignal(int)
    stats_updated = pyqtSignal(int, int)
    upload_finished = pyqtSignal()

    def __init__(self):
        super().__init__()
        self.thread = None
        self.worker = None
        self.settings = self._load_settings()

    def _load_settings(self):
        try:
            with open('settings.json', 'r') as f:
                settings_data = json.load(f)
        except (FileNotFoundError, json.JSONDecodeError):
            QMessageBox.information(
                None, "Settings File Created", ""
            )
            settings_data = {
                "csrf_token": "PASTE_YOUR_CSRF_TOKEN_HERE",
                "cookies": { "auth_c": "PASTE_YOUR_auth_c_TOKEN_HERE", "XSRF-TOKEN": "PASTE_YOUR_XSRF_TOKEN_HERE", "lufix_session": "PASTE_YOUR_lufix_session_TOKEN_HERE" }
            }
            with open('settings.json', 'w') as f:
                json.dump(settings_data, f, indent=4)

        # Add some configuration that's always the same
        config = {
            "shell": {"endpoint": "/seller/add/mass-shell", "referer": "https://lufix.gs/seller/shells", "data_template": {"source": "hacked"}},
            "mailer": {"endpoint": "/seller/add/mass-mailer", "referer": "https://lufix.gs/seller/mailers", "data_template": {}},
            "smtp": {"endpoint": "/seller/add/mass-smtp", "referer": "https://lufix.gs/seller/smtps", "data_template": {"webmail": "yes"}}
        }
        settings_data.update({
            'CONFIG': config,
            'BASE_URL': "https://lufix.gs",
            'COMMON_HEADERS': {"User-Agent": "Mozilla/5.0", "Accept": "application/json"}
        })
        return settings_data

    def get_credentials(self):
        cookies = self.settings.get('cookies', {})
        return {
            'csrf': self.settings.get('csrf_token', ''),
            'auth_c': cookies.get('auth_c', ''),
            'xsrf': cookies.get('XSRF-TOKEN', ''),
            'session': cookies.get('lufix_session', '')
        }
        
    def save_settings(self, csrf, auth_c, xsrf, session):
        """Saves new credentials from the GUI"""
        self.settings['csrf_token'] = csrf
        self.settings['cookies']['auth_c'] = auth_c
        self.settings['cookies']['XSRF-TOKEN'] = xsrf
        self.settings['cookies']['lufix_session'] = session
        
        try:
            settings_to_save = {
                "csrf_token": self.settings['csrf_token'],
                "cookies": self.settings['cookies']
            }
            with open('settings.json', 'w') as f:
                json.dump(settings_to_save, f, indent=4)
            QMessageBox.information(None, "Success", "Your settings have been saved.")
        except IOError as e:
            QMessageBox.critical(None, "Error", f"Could not save settings.json: {e}")

    def start_upload_process(self, upload_params):
        self.worker = UploaderWorker(**upload_params)
        self.thread = QThread()
        self.worker.moveToThread(self.thread)
        self.worker.log_message.connect(self.log_message)
        self.worker.progress.connect(self.progress_updated)
        self.worker.update_stats.connect(self.stats_updated)
        self.worker.finished.connect(self.on_worker_finished)
        self.thread.started.connect(self.worker.run)
        self.thread.start()

    def on_worker_finished(self):
        self.thread.quit()
        self.thread.wait()
        self.thread = None
        self.worker = None
        self.upload_finished.emit()

    def stop_process(self):
        if self.worker:
            self.worker.stop()



class AppWindow(QMainWindow):
    """
    The main UI
    """
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Lufix Uploader v0.1 @WhoKnows (https://t.me/Moonlightcrow)")
        self.setGeometry(100, 100, 800, 850)

        self.logic = UploaderLogic()
        self.total_items_count = 0

        self._setup_ui()
        self._connect_signals()
        self._populate_fields_from_settings()

    def _setup_ui(self):
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QVBoxLayout(central_widget)

        # --- Top Section: Upload Type ---
        top_controls_layout = QHBoxLayout()
        self.type_combo = QComboBox()
        self.type_combo.addItems(self.logic.settings['CONFIG'].keys())
        top_controls_layout.addWidget(QLabel("Upload Type:"))
        top_controls_layout.addWidget(self.type_combo)
        top_controls_layout.addStretch()
        main_layout.addLayout(top_controls_layout)

        # --- Configuration Section: Threads & Price ---
        config_group = QGroupBox("Configuration")
        config_layout = QHBoxLayout()
        self.thread_count_input = QSpinBox()
        self.thread_count_input.setRange(1, 100); self.thread_count_input.setValue(10)
        self.min_price_input = QDoubleSpinBox()
        self.min_price_input.setRange(0.0, 1000.0); self.min_price_input.setValue(5.00); self.min_price_input.setDecimals(2)
        self.max_price_input = QDoubleSpinBox()
        self.max_price_input.setRange(0.0, 1000.0); self.max_price_input.setValue(10.99); self.max_price_input.setDecimals(2)
        config_layout.addWidget(QLabel("Threads:")); config_layout.addWidget(self.thread_count_input)
        config_layout.addWidget(QLabel("Min Price:")); config_layout.addWidget(self.min_price_input)
        config_layout.addWidget(QLabel("Max Price:")); config_layout.addWidget(self.max_price_input)
        config_layout.addStretch()
        config_group.setLayout(config_layout)
        main_layout.addWidget(config_group)

        # --- Credentials Section ---
        self.credentials_group = QGroupBox("Credentials (from settings.json)")
        form_layout = QFormLayout()
        self.csrf_token_input = QLineEdit()
        self.auth_c_input = QLineEdit()
        self.xsrf_token_input = QLineEdit()
        self.lufix_session_input = QLineEdit()
        form_layout.addRow("CSRF Token:", self.csrf_token_input)
        form_layout.addRow("auth_c Cookie:", self.auth_c_input)
        form_layout.addRow("XSRF-TOKEN Cookie:", self.xsrf_token_input)
        form_layout.addRow("lufix_session Cookie:", self.lufix_session_input)
        self.save_settings_button = QPushButton("Save Credentials to settings.json")
        vbox_creds = QVBoxLayout()
        vbox_creds.addLayout(form_layout)
        vbox_creds.addWidget(self.save_settings_button)
        self.credentials_group.setLayout(vbox_creds)
        main_layout.addWidget(self.credentials_group)

        # --- Item List Section ---
        self.items_group = QGroupBox("Items to Upload")
        items_layout = QVBoxLayout()
        file_actions_layout = QHBoxLayout()
        self.current_file_label = QLabel("Current file: (None selected)")
        self.load_custom_file_button = QPushButton("Load Items from File...")
        file_actions_layout.addWidget(self.current_file_label); file_actions_layout.addStretch(); file_actions_layout.addWidget(self.load_custom_file_button)
        items_layout.addLayout(file_actions_layout)
        self.item_editor = QTextEdit()
        items_layout.addWidget(self.item_editor)
        self.items_group.setLayout(items_layout)
        main_layout.addWidget(self.items_group)

        # --- Action Button ---
        self.upload_button = QPushButton("Start Upload")
        self.upload_button.setStyleSheet("background-color: #4CAF50; color: white; font-weight: bold; padding: 5px;")
        main_layout.addWidget(self.upload_button)
        
        # --- Log & Progress Section ---
        main_layout.addWidget(QLabel("Log:"))
        self.log_console = QPlainTextEdit()
        self.log_console.setReadOnly(True)
        self.log_console.setStyleSheet("background-color: #2b2b2b; color: #f0f0f0; font-family: Consolas, monaco, monospace;")
        main_layout.addWidget(self.log_console)
        self.progress_bar = QProgressBar()
        main_layout.addWidget(self.progress_bar)
        
        stats_layout = QHBoxLayout()
        self.total_label = QLabel("Total: 0"); self.processed_label = QLabel("Processed: 0")
        self.remaining_label = QLabel("Remaining: 0"); self.success_label = QLabel("Success: 0")
        self.failure_label = QLabel("Failed: 0")
        stats_layout.addWidget(self.total_label); stats_layout.addWidget(self.processed_label)
        stats_layout.addWidget(self.remaining_label); stats_layout.addWidget(self.success_label)
        stats_layout.addWidget(self.failure_label)
        main_layout.addLayout(stats_layout)

    def _connect_signals(self):
        self.upload_button.clicked.connect(self._handle_start_upload)
        self.save_settings_button.clicked.connect(self._handle_save_settings)
        self.load_custom_file_button.clicked.connect(self._handle_load_file)
        self.type_combo.currentTextChanged.connect(self._handle_type_change)
        self.logic.log_message.connect(self.log_console.appendPlainText)
        self.logic.progress_updated.connect(self.progress_bar.setValue)
        self.logic.stats_updated.connect(self._update_stats_labels)
        self.logic.upload_finished.connect(self._handle_upload_finished)

    def _populate_fields_from_settings(self):
        creds = self.logic.get_credentials()
        self.csrf_token_input.setText(creds['csrf'])
        self.auth_c_input.setText(creds['auth_c'])
        self.xsrf_token_input.setText(creds['xsrf'])
        self.lufix_session_input.setText(creds['session'])

    def _handle_start_upload(self):
        self.log_console.clear()
        self.progress_bar.setValue(0)
        
        if self.min_price_input.value() > self.max_price_input.value():
            QMessageBox.warning(self, "Input Error", "Minimum price cannot be greater than maximum price.")
            return

        items = [line.strip() for line in self.item_editor.toPlainText().splitlines() if line.strip()]
        if not items:
            QMessageBox.warning(self, "No Items", "There's nothing to upload. Please load a file first.")
            return

        self.total_items_count = len(items)
        self._reset_stats_labels()
        self.logic.settings['csrf_token'] = self.csrf_token_input.text()
        self.logic.settings['cookies']['auth_c'] = self.auth_c_input.text()
        self.logic.settings['cookies']['XSRF-TOKEN'] = self.xsrf_token_input.text()
        self.logic.settings['cookies']['lufix_session'] = self.lufix_session_input.text()
        upload_parameters = {
            "items_to_upload": items,
            "upload_type": self.type_combo.currentText(),
            "settings": self.logic.settings,
            "thread_count": self.thread_count_input.value(),
            "min_price": self.min_price_input.value(),
            "max_price": self.max_price_input.value(),
        }

        self._set_controls_enabled(False)
        self.logic.start_upload_process(upload_parameters)

    def _handle_save_settings(self):
        self.logic.save_settings(
            self.csrf_token_input.text(),
            self.auth_c_input.text(),
            self.xsrf_token_input.text(),
            self.lufix_session_input.text()
        )
    
    def _handle_load_file(self):
        file_path, _ = QFileDialog.getOpenFileName(self, "Select Item List", "", "Text Files (*.txt);;All Files (*)")
        if file_path:
            self.current_file_label.setText(f"Current file: {file_path}")
            try:
                with open(file_path, 'r', encoding='utf-8') as f:
                    self.item_editor.setPlainText(f.read())
                self.log_console.appendPlainText(f"INFO: Loaded '{file_path}'.")
            except Exception as e:
                QMessageBox.critical(self, "Error", f"Could not read file: {e}")

    def _handle_type_change(self):
        self.log_console.appendPlainText(f"INFO: Switched to '{self.type_combo.currentText()}' type.")

    def _handle_upload_finished(self):
        self.log_console.appendPlainText("\n--- UPLOAD FINISHED ---")
        self._set_controls_enabled(True)
    
    def _reset_stats_labels(self):
        self.total_label.setText(f"Total: {self.total_items_count}")
        self.processed_label.setText("Processed: 0")
        self.remaining_label.setText(f"Remaining: {self.total_items_count}")
        self.success_label.setText("Success: 0")
        self.failure_label.setText("Failed: 0")
        
    def _update_stats_labels(self, successes, failures):
        processed = successes + failures
        remaining = self.total_items_count - processed
        self.processed_label.setText(f"Processed: {processed}")
        self.remaining_label.setText(f"Remaining: {remaining}")
        self.success_label.setText(f"Success: {successes}")
        self.failure_label.setText(f"Failed: {failures}")

    def _set_controls_enabled(self, enabled):
        self.upload_button.setEnabled(enabled)
        self.credentials_group.setEnabled(enabled)
        self.items_group.setEnabled(enabled)
        
        if enabled:
            self.upload_button.setText("Start Upload")
            self.upload_button.setStyleSheet("background-color: #4CAF50; color: white; font-weight: bold; padding: 5px;")
        else:
            self.upload_button.setText("Uploading...")
            self.upload_button.setStyleSheet("background-color: #f44336; color: white; font-weight: bold; padding: 5px;")

    def closeEvent(self, event):
        self.logic.stop_process()
        event.accept()
        
if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = AppWindow()
    window.show()
    sys.exit(app.exec())