import os
import re
import time
import threading
import random
import requests
import shutil
import subprocess
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime

from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton,
    QLabel, QTextEdit, QLineEdit, QCheckBox, QProgressBar, QTableWidget,
    QTableWidgetItem, QHeaderView, QFileDialog, QMessageBox, QDialog,
    QListWidget, QListWidgetItem, QSplitter, QMenu, QAction, QStyleFactory,
    QDesktopWidget, QFrame, QTextBrowser, QGroupBox, QComboBox
)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer, QUrl, QSettings
from PyQt5.QtGui import QDesktopServices, QFont, QCursor, QIcon

import uiautomator2 as u2  # Для автоматизации UI на Android

# --- Constants for File Paths ---
TOKENS_FILE_PATH = "токены для загруки аватарок.txt"
FAILED_TOKENS_FILE_PATH = "Токены неуспешных аватарок.txt"
AVATARS_DIR = "аватарки"
UPLOADED_AVATARS_DIR = os.path.join(AVATARS_DIR, "Загруженные")
AUTO_AVATAR_STATE_FILE_PATH = "auto_avatar_state.txt"
LAST_AVATAR_DATE_FILE_PATH = "last_avatar_date.txt"
CHANGE_IP_START_STATE_FILE_PATH = "change_ip_start_state.txt"
CHANGE_IP_END_STATE_FILE_PATH = "change_ip_end_state.txt"
LAST_MODEL_FILE_PATH = "last_model.txt"  # Новый файл для хранения последнего выбранного устройства
DELETE_POSTS_STATE_FILE_PATH = "delete_posts_state.txt"  # Новый файл для состояния галочки удаления постов

# --- Enhanced Sharp, Rectangular, Compact Dark Theme Stylesheet ---
DARK_STYLESHEET = """
    QMainWindow, QDialog {
        background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                   stop: 0 #1e1e2e, stop: 1 #181825);
        color: #cdd6f4;
    }
    QWidget {
        background-color: transparent;
        color: #cdd6f4;
        font-family: 'Segoe UI', Arial, sans-serif;
        font-size: 10pt;
    }
    QLabel {
        color: #cdd6f4;
        font-weight: 500;
    }
    QLineEdit, QTextEdit, QListWidget, QTableWidget, QTextBrowser {
        background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                   stop: 0 #313244, stop: 1 #1e1e2e);
        color: #cdd6f4;
        border: 1px solid #45475a;
        border-radius: 4px;
        padding: 4px;
        selection-background-color: #89b4fa;
        selection-color: #1e1e2e;
    }
    QLineEdit:focus, QTextEdit:focus, QListWidget:focus, QTableWidget:focus {
        border-color: #89b4fa;
        background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                   stop: 0 #383a59, stop: 1 #2a2d4a);
    }
    QPushButton {
        background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                   stop: 0 #585b70, stop: 1 #45475a);
        color: #cdd6f4;
        border: 1px solid #6c7086;
        border-radius: 4px;
        padding: 6px 10px;
        min-height: 18px;
        font-weight: 600;
        font-size: 9pt;
    }
    QPushButton:hover {
        background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                   stop: 0 #6c7086, stop: 1 #585b70);
        border-color: #89b4fa;
        color: #ffffff;
    }
    QPushButton:pressed {
        background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                   stop: 0 #45475a, stop: 1 #383a59);
        border-color: #74c7ec;
    }
    QPushButton:disabled {
        background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                   stop: 0 #383a59, stop: 1 #2a2d4a);
        color: #6c7086;
        border-color: #45475a;
    }
    QCheckBox {
        color: #cdd6f4;
        font-weight: 500;
        spacing: 6px;
    }
    QCheckBox::indicator {
        width: 16px;
        height: 16px;
        background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                   stop: 0 #313244, stop: 1 #1e1e2e);
        border: 1px solid #45475a;
        border-radius: 2px;
    }
    QCheckBox::indicator:hover {
        border-color: #89b4fa;
    }
    QCheckBox::indicator:checked {
        background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                   stop: 0 #89b4fa, stop: 1 #74c7ec);
        border-color: #89b4fa;
    }
    QProgressBar {
        border: 1px solid #45475a;
        border-radius: 4px;
        text-align: center;
        color: #1e1e2e;
        font-weight: bold;
        background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                   stop: 0 #313244, stop: 1 #1e1e2e);
    }
    QProgressBar::chunk {
        background: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0,
                                   stop: 0 #89b4fa, stop: 0.5 #74c7ec, stop: 1 #94e2d5);
        border-radius: 3px;
        margin: 1px;
    }
    QHeaderView::section {
        background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                   stop: 0 #585b70, stop: 1 #45475a);
        color: #cdd6f4;
        padding: 4px;
        border: 1px solid #6c7086;
        font-weight: bold;
        border-radius: 2px;
    }
    QTableWidget {
        gridline-color: #6c7086;
        alternate-background-color: #262738;
    }
    QSplitter::handle {
        background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                   stop: 0 #6c7086, stop: 1 #585b70);
        border-radius: 2px;
    }
    QSplitter::handle:horizontal {
        width: 6px;
        margin: 1px 0px;
    }
    QSplitter::handle:vertical {
        height: 6px;
        margin: 0px 1px;
    }
    QMenu {
        background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                   stop: 0 #313244, stop: 1 #1e1e2e);
        color: #cdd6f4;
        border: 1px solid #6c7086;
        border-radius: 4px;
        padding: 2px;
    }
    QMenu::item {
        padding: 4px 12px;
        border-radius: 2px;
    }
    QMenu::item:selected {
        background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                   stop: 0 #89b4fa, stop: 1 #74c7ec);
        color: #1e1e2e;
        font-weight: bold;
    }
    QTextEdit#successfulText, QTextBrowser#successfulText {
        background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                   stop: 0 #2d4a2b, stop: 1 #1e3a1c);
        border-color: #a6e3a1;
    }
    QTextEdit#failedText {
        background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                   stop: 0 #4a2d3a, stop: 1 #3a1e2a);
        border-color: #f38ba8;
    }
    QLabel#successfulAvatarsLabel {
        color: #a6e3a1;
        font-weight: bold;
        font-size: 11pt;
    }
    QLabel#failedAvatarsLabel, QLabel#tokenErrorsSpecificLabel, 
    QLabel#bannedTokensSpecificLabel, QLabel#otherErrorsSpecificLabel {
        color: #f38ba8;
        font-weight: bold;
        font-size: 11pt;
    }
    QLabel#lastAvatarDateLabel {
        color: #cba6f7;
        font-weight: bold;
        font-size: 11pt;
    }
    QFrame[frameShape="4"] {
        color: #6c7086;
        background: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0,
                                   stop: 0 transparent, stop: 0.4 #6c7086, 
                                   stop: 0.6 #6c7086, stop: 1 transparent);
        border: none;
        height: 1px;
    }
    QFrame[frameShape="5"] {
        color: #6c7086;
        background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                   stop: 0 transparent, stop: 0.4 #6c7086, 
                                   stop: 0.6 #6c7086, stop: 1 transparent);
        border: none;
        width: 1px;
    }
    QGroupBox {
        color: #cdd6f4;
        border: 1px solid #6c7086;
        border-radius: 4px;
        margin-top: 1ex;
        font-weight: bold;
        font-size: 10pt;
    }
    QGroupBox::title {
        subcontrol-origin: margin;
        left: 8px;
        padding: 0 6px 0 6px;
        color: #89b4fa;
        font-weight: bold;
    }
    QComboBox {
        background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                   stop: 0 #313244, stop: 1 #1e1e2e);
        color: #cdd6f4;
        border: 1px solid #45475a;
        border-radius: 4px;
        padding: 4px;
        selection-background-color: #89b4fa;
        selection-color: #1e1e2e;
    }
    QComboBox:focus {
        border-color: #89b4fa;
    }
"""

FONT_STYLE = QFont("Segoe UI", 10)
HEADER_FONT = QFont("Segoe UI", 11, QFont.Bold)

user_info_dict = {}
stop_flag = threading.Event()

# Глобальные переменные для смены IP
devices_by_model = {}  # Словарь для хранения устройств: модель -> serial

# --- Helper Functions for UI separators ---
def create_horizontal_separator():
    separator = QFrame()
    separator.setFrameShape(QFrame.HLine)
    separator.setFrameShadow(QFrame.Sunken)
    separator.setFixedHeight(4)
    return separator

def create_vertical_separator():
    separator = QFrame()
    separator.setFrameShape(QFrame.VLine)
    separator.setFrameShadow(QFrame.Sunken)
    separator.setFixedWidth(4)
    return separator

# --- Worker Signals ---
class WorkerSignals(QWidget):
    log_message = pyqtSignal(str)
    update_progress = pyqtSignal(int, int, int)
    successful_avatar = pyqtSignal(str, str)
    failed_avatar = pyqtSignal(str, str)
    enable_controls = pyqtSignal(bool)
    upload_finished = pyqtSignal()
    update_avatar_table = pyqtSignal(list)
    set_progress_max = pyqtSignal(int)
    increment_progress_bar = pyqtSignal()
    device_scan_completed = pyqtSignal(list)  # Сигнал для обновления списка устройств

# --- Helper Functions ---
def natural_key(filename):
    basename = os.path.basename(filename)
    return [text for text in re.split(r'(\d+)', basename)]

def get_current_wifi_ssid():
    try:
        process = subprocess.Popen(['netsh', 'wlan', 'show', 'interfaces'],
                                     stdout=subprocess.PIPE, text=True,
                                     encoding='utf-8', errors='ignore',
                                     creationflags=subprocess.CREATE_NO_WINDOW)
        stdout, stderr = process.communicate(timeout=5)
        if process.returncode == 0:
            for line in stdout.splitlines():
                if "SSID" in line and "BSSID" not in line:
                    match = re.search(r":\s*(.+)", line)
                    if match:
                        ssid = match.group(1).strip()
                        if ssid: return ssid
            return "Нет подключения"
        return "Ошибка опроса"
    except Exception: return "Ошибка Wi-Fi"

def get_external_ip():
    try:
        response = requests.get('https://api.ipify.org', timeout=5)
        response.raise_for_status()
        return response.text.strip()
    except requests.RequestException: return "Недоступно"

def get_location_by_ip(ip):
    try:
        response = requests.get(f'https://ipinfo.io/{ip}/json', timeout=5)
        response.raise_for_status()
        data = response.json()
        city, region, country = data.get('city', 'Н/Д'), data.get('region', 'Н/Д'), data.get('country', 'Н/Д')
        return f"{city}, {region}, {country}"
    except requests.RequestException: return "Недоступно"

def get_user_info(token):
    url = 'https://api.vk.com/method/users.get'
    params = {'access_token': token, 'v': '5.131'}
    response = requests.get(url, params=params, timeout=10)
    data = response.json()
    if 'response' in data:
        user_info = data['response'][0]
        user_info_dict[token] = f"{user_info['first_name']} {user_info['last_name']}"
        return user_info['id'], user_info_dict[token]
    elif 'error' in data and data['error'].get('error_code') == 5:
        raise Exception(f"Ошибка авторизации: {data['error']['error_msg']}")
    else:
        raise Exception("Ошибка получения информации о пользователе: " + str(data.get('error', {})))

def upload_avatar_photo(token, photo_path):
    """Загружает фото как аватарку пользователя"""
    # Получаем URL для загрузки аватарки
    url = 'https://api.vk.com/method/photos.getOwnerPhotoUploadServer'
    params = {'access_token': token, 'v': '5.131'}
    response = requests.post(url, params=params, timeout=10)
    response.raise_for_status()
    upload_info = response.json()
    
    if 'response' not in upload_info:
        raise Exception("Ошибка получения сервера для загрузки аватарки: " + str(upload_info.get('error', {})))
    
    upload_url = upload_info['response']['upload_url']
    
    # Загружаем фото на сервер ВК
    with open(photo_path, 'rb') as photo_file:
        files = {'photo': photo_file}
        upload_response = requests.post(upload_url, files=files, timeout=10)
        upload_response.raise_for_status()
        upload_data = upload_response.json()
    
    if 'photo' not in upload_data:
        raise Exception("Ошибка загрузки аватарки: " + str(upload_data))
    
    return upload_data

def save_avatar_photo(token, upload_data):
    """Сохраняет загруженное фото как аватарку"""
    url = 'https://api.vk.com/method/photos.saveOwnerPhoto'
    params = {
        'access_token': token,
        'server': upload_data['server'],
        'photo': upload_data['photo'],
        'hash': upload_data['hash'],
        'v': '5.131'
    }
    response = requests.post(url, params=params, timeout=10)
    response.raise_for_status()
    data = response.json()
    
    if 'response' not in data:
        raise Exception("Ошибка сохранения аватарки: " + str(data.get('error', {})))
    
    return data

def delete_wall_post(token, user_id, post_id):
    """Удаляет пост со стены (автоматически созданный при установке аватарки)"""
    url = 'https://api.vk.com/method/wall.delete'
    params = {
        'access_token': token,
        'owner_id': user_id,
        'post_id': post_id,
        'v': '5.131'
    }
    response = requests.post(url, params=params, timeout=10)
    return response.json()

# --- Avatar Upload Thread ---
class AvatarWorker(QThread):
    def __init__(self, tokens_list, avatar_schedule_list, auto_avatar_flag, signals_emitter, delete_posts=True, ip_change_limit=0, main_window=None):
        super().__init__()
        self.tokens_list = tokens_list
        self.avatar_schedule_list = avatar_schedule_list
        self.auto_avatar_flag = auto_avatar_flag
        self.signals = signals_emitter
        self.delete_posts = delete_posts
        self.current_successful_avatars_count = 0
        self.current_failed_avatars_count = 0
        self.final_failed_tokens_for_ui_copy = set()
        self.final_successful_tokens_for_ui_copy = set()
        self.used_avatars = []  # Список использованных аватарок для перемещения
        self.ip_change_limit = ip_change_limit
        self.main_window = main_window  # Ссылка на главное окно для вызова смены IP
        self.successful_since_last_ip_change = 0

    def set_avatar_for_account(self, token, user_id_param, photo_path):
        """Устанавливает аватарку для одного аккаунта"""
        if stop_flag.is_set():
            return ("Установка аватарки остановлена пользователем.", False, None)
        
        max_attempts = 3
        attempt = 0
        user_name = ""
        actual_user_id = user_id_param
        while attempt < max_attempts:
            if stop_flag.is_set():
                return ("Установка аватарки остановлена пользователем.", False, None)
            
            attempt += 1
            try:
                if actual_user_id is None or attempt == 1:
                    actual_user_id, user_name = get_user_info(token)
                elif not user_name:
                    _, user_name = get_user_info(token)
                self.signals.log_message.emit(f"Установка аватарки для: {user_name if user_name else 'ID ' + str(actual_user_id)}... Попытка {attempt}\n")
                
                # Загружаем фото как аватарку
                self.signals.log_message.emit(f"Загрузка аватарки: {os.path.basename(photo_path)} для {user_name}...\n")
                upload_data = upload_avatar_photo(token, photo_path)
                
                # Сохраняем аватарку
                saved_avatar = save_avatar_photo(token, upload_data)
                
                if 'response' not in saved_avatar:
                    raise Exception(f"Не удалось сохранить аватарку {os.path.basename(photo_path)}. Ответ: {saved_avatar}") 
                
                # Если нужно удалить автоматически созданный пост
                if self.delete_posts:
                    try:
                        # Получаем последний пост со стены (обычно это пост с новой аватаркой)
                        wall_url = 'https://api.vk.com/method/wall.get'
                        wall_params = {'access_token': token, 'owner_id': actual_user_id, 'count': 1, 'v': '5.131'}
                        wall_response = requests.get(wall_url, params=wall_params, timeout=10)
                        wall_data = wall_response.json()
                        
                        if 'response' in wall_data and wall_data['response']['items']:
                            last_post = wall_data['response']['items'][0]
                            # Проверяем, что это пост с фотографией (аватаркой)
                            if 'attachments' in last_post and any(att.get('type') == 'photo' for att in last_post['attachments']):
                                delete_result = delete_wall_post(token, actual_user_id, last_post['id'])
                                if 'response' in delete_result:
                                    self.signals.log_message.emit(f"Автоматический пост с аватаркой удален для {user_name}\n")
                    except Exception as e:
                        self.signals.log_message.emit(f"Не удалось удалить пост с аватаркой для {user_name}: {e}\n")

                profile_link = f"https://vk.com/id{actual_user_id}"
                return (f"Аватарка {os.path.basename(photo_path)} успешно установлена для: {user_name}", profile_link, actual_user_id, photo_path)

            except (requests.ConnectionError, requests.Timeout) as net_err:
                self.signals.log_message.emit(f"Сетевая ошибка для {user_name or token}: {net_err}. Повтор через 5-10 сек...\n")
                time.sleep(random.uniform(5, 10))
            except Exception as e:
                error_text = str(e)
                user_name_display = user_name if user_name else user_info_dict.get(token, token)
                if attempt >= max_attempts:
                    error_message = f"Ошибка установки аватарки для {user_name_display}: {error_text} (после {max_attempts} попыток)\n"
                    return error_message, False, token, None
                else:
                    self.signals.log_message.emit(f"Попытка {attempt} не удалась для {user_name_display}. Ошибка: {error_text}\n")
                    if "too many requests per second" in error_text.lower() or \
                       (hasattr(e, 'response') and getattr(e.response, 'status_code', 0) == 429) or \
                       ('error_code' in error_text and '6' in error_text):
                        self.signals.log_message.emit("Обнаружена ошибка 'too many requests'. Увеличиваю задержку...\n")
                        time.sleep(random.uniform(5, 10))
                    else:
                        time.sleep(random.uniform(1, 3))
        
        return (f"Не удалось установить аватарку для {user_name_display} после {max_attempts} попыток.", False, token, None)

    def move_uploaded_files(self, files):
        """Перемещает загруженные файлы в папку 'Загруженные'"""
        os.makedirs(UPLOADED_AVATARS_DIR, exist_ok=True)
        for file_path in files:
            try:
                if os.path.exists(file_path):
                    destination = os.path.join(UPLOADED_AVATARS_DIR, os.path.basename(file_path))
                    shutil.move(file_path, destination)
                    self.signals.log_message.emit(f"Файл перемещён в папку 'Загруженные': {os.path.basename(file_path)}\n")
                else:
                    self.signals.log_message.emit(f"Файл для перемещения не найден: {os.path.basename(file_path)}\n")
            except Exception as e:
                self.signals.log_message.emit(f"Ошибка при перемещении файла {os.path.basename(file_path)}: {e}\n")

    def wait_for_network_connection(self):
        """Ждёт подключения к сети после смены IP"""
        self.signals.log_message.emit("Ожидание подключения к сети...\n")
        max_wait_time = 60  # Максимальное время ожидания в секундах
        check_interval = 5  # Интервал проверки в секундах
        elapsed = 0
        while elapsed < max_wait_time:
            if get_external_ip() != "Недоступно":
                self.signals.log_message.emit("Подключение к сети восстановлено.\n")
                return True
            time.sleep(check_interval)
            elapsed += check_interval
        self.signals.log_message.emit("Ошибка: Не удалось подключиться к сети в отведённое время.\n")
        return False

    def process_batch(self, batch_pairs, total_avatars_to_set):
        """Обрабатывает батч пар токен-аватарка в отдельном executor"""
        failed_tokens_this_batch = set()
        successful_tokens_this_batch = set()
        batch_used_avatars = []

        max_workers = min(10, len(batch_pairs))  # Для отладки можно установить =1
        # max_workers = 1  # Раскомментировать для тестирования без многопоточности

        with ThreadPoolExecutor(max_workers=max_workers) as executor:
            future_to_data = {
                executor.submit(self.set_avatar_for_account, token, None, avatar_path): (token, avatar_path)
                for token, avatar_path in batch_pairs
            }
            for future in as_completed(future_to_data):
                if stop_flag.is_set():
                    for f in future_to_data:
                        if not f.done():
                            f.cancel()
                    return failed_tokens_this_batch, successful_tokens_this_batch, batch_used_avatars
                
                token_from_future, avatar_path_from_future = future_to_data[future]
                try:
                    result = future.result()
                    if len(result) == 4:
                        status_message, outcome_is_link_or_false, problematic_item, used_avatar_path = result
                        
                        if outcome_is_link_or_false == False:
                            self.current_failed_avatars_count += 1
                            self.signals.failed_avatar.emit(status_message, problematic_item)
                            if problematic_item:
                                failed_tokens_this_batch.add(problematic_item)
                        else:
                            self.current_successful_avatars_count += 1
                            self.successful_since_last_ip_change += 1
                            successful_tokens_this_batch.add(token_from_future)
                            self.signals.successful_avatar.emit(status_message, outcome_is_link_or_false)
                            
                            # Добавляем использованную аватарку в список для перемещения
                            if used_avatar_path and used_avatar_path not in batch_used_avatars:
                                batch_used_avatars.append(used_avatar_path)
                    else:
                        self.current_failed_avatars_count += 1
                        display_name = user_info_dict.get(token_from_future, token_from_future)
                        self.signals.failed_avatar.emit(f"Неизвестная ошибка обработки для токена: {display_name}.\n", token_from_future)
                        failed_tokens_this_batch.add(token_from_future)
                except Exception as exc:
                    self.current_failed_avatars_count += 1
                    display_name = user_info_dict.get(token_from_future, token_from_future)
                    error_msg = f"Критическая ошибка для {display_name} при установке аватарки: {exc}\n"
                    self.signals.failed_avatar.emit(error_msg, token_from_future)
                    failed_tokens_this_batch.add(token_from_future)
                
                self.signals.increment_progress_bar.emit()
                self.signals.update_progress.emit(self.current_successful_avatars_count, self.current_failed_avatars_count, total_avatars_to_set)

        return failed_tokens_this_batch, successful_tokens_this_batch, batch_used_avatars

    def run(self):
        stop_flag.clear()
        self.signals.enable_controls.emit(False)
        self.current_successful_avatars_count = 0
        self.current_failed_avatars_count = 0
        self.used_avatars = []
        self.successful_since_last_ip_change = 0
        
        failed_tokens_this_run = set()
        successful_tokens_this_run = set()
        
        # Формируем список аватарок для установки
        available_avatars = []
        if self.auto_avatar_flag:
            if not os.path.exists(AVATARS_DIR):
                self.signals.log_message.emit(f"Папка '{AVATARS_DIR}' не найдена для автоустановки аватарок.\n")
                self.signals.upload_finished.emit()
                return
            
            # Получаем все файлы изображений из папки "аватарки"
            all_photos = []
            for f in os.listdir(AVATARS_DIR):
                if f.lower().endswith(('.jpg', '.jpeg', '.png')):
                    full_path = os.path.join(AVATARS_DIR, f)
                    # Проверяем, что это файл, а не папка
                    if os.path.isfile(full_path):
                        all_photos.append(full_path)
            
            # Сортируем по естественному порядку
            all_photos = sorted(all_photos, key=natural_key)
            
            if not all_photos:
                self.signals.log_message.emit(f"В папке '{AVATARS_DIR}' нет фотографий для автоустановки.\n")
                self.signals.upload_finished.emit()
                return
            
            # Используем количество токенов как лимит аватарок
            max_avatars = len(self.tokens_list)
            available_avatars = all_photos[:max_avatars]
            
            self.signals.log_message.emit(f"Автоустановка: выбрано {len(available_avatars)} аватарок из папки '{AVATARS_DIR}' для {len(self.tokens_list)} токенов.\n")
        else:
            # Используем ручной режим - каждая аватарка для всех токенов (старая логика не подходит)
            # В ручном режиме тоже будем использовать логику "каждому токену - своя аватарка"
            available_avatars = [entry['photo'] for entry in self.avatar_schedule_list]

        if not self.tokens_list:
            self.signals.log_message.emit("Нет доступных токенов для установки аватарок.\n")
            self.signals.upload_finished.emit()
            return

        if not available_avatars:
            self.signals.log_message.emit("Нет аватарок для установки.\n")
            self.signals.upload_finished.emit()
            return

        # Проверяем, хватает ли аватарок на все токены
        if len(available_avatars) < len(self.tokens_list):
            self.signals.log_message.emit(f"Внимание: аватарок ({len(available_avatars)}) меньше чем токенов ({len(self.tokens_list)}). Некоторые токены будут пропущены.\n")
        
        # Создаем пары токен-аватарка
        token_avatar_pairs = []
        for i, token in enumerate(self.tokens_list):
            if i < len(available_avatars):
                token_avatar_pairs.append((token, available_avatars[i]))
        
        # Обновляем таблицу для отображения
        avatar_schedule_for_display = [{'photo': avatar} for _, avatar in token_avatar_pairs]
        if self.signals.update_avatar_table:
            self.signals.update_avatar_table.emit(avatar_schedule_for_display)

        total_avatars_to_set = len(token_avatar_pairs)
        self.signals.set_progress_max.emit(total_avatars_to_set)
        self.signals.update_progress.emit(0, 0, total_avatars_to_set)

        self.signals.log_message.emit(f"Начинается установка {total_avatars_to_set} уникальных аватарок на разные аккаунты...\n")

        # Группировка в батчи по ip_change_limit
        batch_size = self.ip_change_limit if self.ip_change_limit > 0 else total_avatars_to_set
        for i in range(0, total_avatars_to_set, batch_size):
            if stop_flag.is_set():
                break
            batch = token_avatar_pairs[i:i + batch_size]
            self.signals.log_message.emit(f"Обработка батча {len(batch)} аватарок...\n")
            
            batch_failed, batch_successful, batch_used = self.process_batch(batch, total_avatars_to_set)
            failed_tokens_this_run.update(batch_failed)
            successful_tokens_this_run.update(batch_successful)
            self.used_avatars.extend(batch_used)
            
            self.final_failed_tokens_for_ui_copy.update(batch_failed)
            self.final_successful_tokens_for_ui_copy.update(batch_successful)
            
            # Проверка на смену IP после батча
            if self.ip_change_limit > 0 and self.successful_since_last_ip_change >= self.ip_change_limit and i + batch_size < total_avatars_to_set:
                self.signals.log_message.emit(f"Достигнут лимит {self.ip_change_limit} успешных установок. Смена IP...\n")
                self.main_window.run_flight_mode_scenario()
                if self.wait_for_network_connection():
                    self.successful_since_last_ip_change = 0
                else:
                    self.signals.log_message.emit("Продолжение без смены IP из-за ошибки подключения.\n")
        
        # Перемещаем все использованные аватарки
        if not stop_flag.is_set() and self.used_avatars:
            self.signals.log_message.emit(f"Перемещение {len(self.used_avatars)} использованных аватарок в папку 'Загруженные'...\n")
            self.move_uploaded_files(self.used_avatars)
        elif stop_flag.is_set() and self.used_avatars:
            self.signals.log_message.emit(f"Процесс был прерван. {len(self.used_avatars)} аватарок не будут перемещены.\n")
        
        if failed_tokens_this_run:
            self.write_failed_tokens(failed_tokens_this_run)

        final_message = "Установка уникальных аватарок завершена.\n" if not stop_flag.is_set() else "Процесс был остановлен пользователем.\n"
        self.signals.log_message.emit(final_message)
        self.signals.update_progress.emit(self.current_successful_avatars_count, self.current_failed_avatars_count, total_avatars_to_set)
        self.signals.upload_finished.emit()

    def write_failed_tokens(self, failed_tokens_set_param):
        separator = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + " ------------------------\n"
        try:
            with open(FAILED_TOKENS_FILE_PATH, 'a', encoding='utf-8') as file:
                file.write(separator)
                for token in failed_tokens_set_param:
                    file.write(token + '\n')
            self.signals.log_message.emit(f"Токены неуспешных установок аватарок записаны в {FAILED_TOKENS_FILE_PATH}\n")
        except Exception as e:
            self.signals.log_message.emit(f"Ошибка записи токенов неуспешных установок: {e}\n")
            
    def get_final_failed_tokens(self):
        return self.final_failed_tokens_for_ui_copy

    def get_final_successful_tokens(self):
        return self.final_successful_tokens_for_ui_copy

# --- Add Avatar Dialog ---
class AddAvatarDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Добавить аватарки")
        self.setGeometry(300, 300, 600, 400)
        self.setModal(True)
        self.avatar_files = []
        self.initUI()
        self.center()

    def center(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    def initUI(self):
        layout = QVBoxLayout(self)
        layout.setSpacing(10)
        layout.setContentsMargins(15, 15, 15, 15)
        
        title_label = QLabel("✨ Добавить аватарки")
        title_label.setFont(QFont("Segoe UI", 14, QFont.Bold))
        title_label.setAlignment(Qt.AlignCenter)
        layout.addWidget(title_label)

        layout.addWidget(create_horizontal_separator())

        # Группа для файлов
        files_group = QGroupBox("📁 Файлы аватарок")
        files_group_layout = QVBoxLayout(files_group)
        
        files_label_text = QLabel("Перетащите фотографии сюда или используйте кнопки добавления")
        files_label_text.setAlignment(Qt.AlignCenter)
        files_group_layout.addWidget(files_label_text)
        
        files_layout = QHBoxLayout()
        self.files_listbox = QListWidget()
        self.files_listbox.setSelectionMode(QListWidget.ExtendedSelection)
        self.files_listbox.setAcceptDrops(True)
        self.files_listbox.dragEnterEvent = self.dragEnterEventFiles
        self.files_listbox.dragMoveEvent = self.dragMoveEventFiles
        self.files_listbox.dropEvent = self.dropEventFiles
        files_layout.addWidget(self.files_listbox, 1)
        
        files_layout.addWidget(create_vertical_separator())
        
        files_buttons_layout = QVBoxLayout()
        add_files_button = QPushButton("➕")
        add_files_button.setFixedSize(40, 40)
        add_files_button.setToolTip("Добавить файлы")
        add_files_button.clicked.connect(self.add_files_dialog)
        files_buttons_layout.addWidget(add_files_button)
        
        remove_files_button = QPushButton("➖")
        remove_files_button.setFixedSize(40, 40)
        remove_files_button.setToolTip("Удалить выбранные")
        remove_files_button.clicked.connect(self.remove_selected_files_dialog)
        files_buttons_layout.addWidget(remove_files_button)
        files_buttons_layout.addStretch()
        files_layout.addLayout(files_buttons_layout)
        
        files_group_layout.addLayout(files_layout)
        layout.addWidget(files_group)
        
        layout.addWidget(create_horizontal_separator())

        # Кнопки управления
        buttons_frame_layout = QHBoxLayout()
        buttons_frame_layout.addStretch()
        
        save_and_new_button = QPushButton("📥 Добавить ещё")
        save_and_new_button.clicked.connect(lambda: self.save_avatars_dialog(close_after=False))
        buttons_frame_layout.addWidget(save_and_new_button)
        
        save_button = QPushButton("✅ Добавить и закрыть")
        save_button.clicked.connect(lambda: self.save_avatars_dialog(close_after=True))
        buttons_frame_layout.addWidget(save_button)
        
        cancel_button = QPushButton("❌ Отмена")
        cancel_button.clicked.connect(self.reject)
        buttons_frame_layout.addWidget(cancel_button)
        buttons_frame_layout.addStretch()
        layout.addLayout(buttons_frame_layout)

    def dragEnterEventFiles(self, event):
        if event.mimeData().hasUrls(): 
            event.acceptProposedAction()
        else: 
            event.ignore()

    def dragMoveEventFiles(self, event):
        if event.mimeData().hasUrls(): 
            event.acceptProposedAction()
        else: 
            event.ignore()

    def dropEventFiles(self, event):
        files = [url.toLocalFile() for url in event.mimeData().urls()]
        valid_files_to_add = [
            f for f in files
            if os.path.exists(f) and f.lower().endswith(('.jpg', '.jpeg', '.png'))
        ]
        newly_added_basenames = []
        for file_path in valid_files_to_add:
            if file_path not in self.avatar_files:
                self.avatar_files.append(file_path)
                self.files_listbox.addItem(QListWidgetItem(os.path.basename(file_path)))
                newly_added_basenames.append(os.path.basename(file_path))
        if newly_added_basenames and self.parent():
            self.parent().log_message_slot(f"Добавлено аватарок через DnD: {', '.join(newly_added_basenames)}\n")

    def add_files_dialog(self):
        selected_files, _ = QFileDialog.getOpenFileNames(
            self, "Выберите фотографии для аватарок", "",
            "Изображения (*.jpg *.jpeg *.png);;Все файлы (*.*)"
        )
        newly_added_basenames = []
        for file_path in selected_files:
            if file_path not in self.avatar_files:
                self.avatar_files.append(file_path)
                self.files_listbox.addItem(QListWidgetItem(os.path.basename(file_path)))
                newly_added_basenames.append(os.path.basename(file_path))
        if newly_added_basenames and self.parent():
            self.parent().log_message_slot(f"Добавлено аватарок через диалог: {', '.join(newly_added_basenames)}\n")

    def remove_selected_files_dialog(self):
        selected_items = self.files_listbox.selectedItems()
        if not selected_items: 
            return
        removed_basenames = []
        for item in reversed(selected_items):
            row = self.files_listbox.row(item)
            base_name = os.path.basename(self.avatar_files[row])
            removed_basenames.append(base_name)
            del self.avatar_files[row]
            self.files_listbox.takeItem(row)
        if removed_basenames and self.parent():
            self.parent().log_message_slot(f"Удалены выбранные аватарки: {', '.join(reversed(removed_basenames))}\n")

    def save_avatars_dialog(self, close_after=False):
        if not self.avatar_files:
            QMessageBox.warning(self, "Предупреждение", "Добавьте хотя бы одну фотографию.")
            return False
        
        if self.parent():
            for photo_path in self.avatar_files:
                self.parent().add_avatar_data({'photo': photo_path})
            self.parent().log_message_slot(f"Добавлено {len(self.avatar_files)} аватарок.\n")
        
        self.files_listbox.clear()
        self.avatar_files.clear()
        if close_after: 
            self.accept()
        return True

# --- Main Application Window ---
class VKAvatarUploaderApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("🚀 VK Avatar Uploader - Автоустановка уникальных аватарок")
        self.setGeometry(100, 100, 1200, 800)
        self.tokens = []
        self.avatar_schedule = []
        self.auto_scroll_log = True
        self.worker_signals = WorkerSignals()
        self.upload_thread = None
        self.final_failed_tokens_for_ui_copy = set()
        self.final_successful_tokens_for_ui_copy = set()

        self.token_error_specific_count = 0
        self.banned_token_specific_count = 0
        self.other_error_specific_count = 0

        self.auth_error_tokens = set()
        self.banned_error_tokens = set()
        self.other_error_tokens = set()

        self.elapsed_time_label = QLabel("⏱️ Время: 00:00:00")
        self.runtime_qtimer = QTimer(self)
        self.runtime_qtimer.timeout.connect(self.update_runtime_display)
        self.upload_start_time = None
        self.elapsed_seconds = 0

        # Инициализация QSettings
        self.settings = QSettings('MyCompany', 'VKAvatarUploader')

        self.initUI()
        self.center()
        self.load_settings()
        self.update_add_avatar_button_state()
        self.update_buttons_on_token_load()
        self.update_retry_button_state()

        self.info_update_timer = QTimer(self)
        self.info_update_timer.timeout.connect(self.update_info_display_slot)
        self.info_update_timer.start(15000)
        self.update_info_display_slot()

        self.load_last_avatar_date()

        self.worker_signals.log_message.connect(self.log_message_slot)
        self.worker_signals.update_progress.connect(self.update_progress_labels_slot)
        self.worker_signals.successful_avatar.connect(self.successful_avatar_slot)
        self.worker_signals.failed_avatar.connect(self.failed_avatar_slot)
        self.worker_signals.enable_controls.connect(self.toggle_upload_buttons_slot)
        self.worker_signals.upload_finished.connect(self.on_upload_finished)
        self.worker_signals.update_avatar_table.connect(self.receive_avatar_update_from_worker)
        self.worker_signals.set_progress_max.connect(self.progress_bar.setMaximum)
        self.worker_signals.increment_progress_bar.connect(self.increment_progress_bar_slot)
        self.worker_signals.device_scan_completed.connect(self.update_device_list)  # Подключение сигнала для устройств

        self.scan_devices_async()  # Автоматическое сканирование устройств при запуске
        self.update_remaining_avatars()  # Инициализация метки оставшихся аватарок

    def center(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    def load_last_avatar_date(self):
        if os.path.exists(LAST_AVATAR_DATE_FILE_PATH):
            try:
                with open(LAST_AVATAR_DATE_FILE_PATH, 'r', encoding='utf-8') as f:
                    last_date = f.read().strip()
                    if last_date:
                        self.last_avatar_date_label.setText(f"🕐 Последняя установка: {last_date}")
                    else:
                        self.last_avatar_date_label.setText("🕐 Последняя установка: Нет данных")
            except Exception as e:
                self.log_message_slot(f"Ошибка загрузки даты последней установки: {e}\n")
                self.last_avatar_date_label.setText("🕐 Последняя установка: Ошибка загрузки")
        else:
            self.last_avatar_date_label.setText("🕐 Последняя установка: Нет данных")

    def save_last_avatar_date(self):
        current_datetime = datetime.now().strftime("%d.%m.%Y %H:%M:%S")
        try:
            with open(LAST_AVATAR_DATE_FILE_PATH, 'w', encoding='utf-8') as f:
                f.write(current_datetime)
            self.last_avatar_date_label.setText(f"🕐 Последняя установка: {current_datetime}")
        except Exception as e:
            self.log_message_slot(f"Ошибка сохранения даты последней установки: {e}\n")

    def initUI(self):
        main_widget = QWidget()
        self.setCentralWidget(main_widget)
        main_layout = QVBoxLayout(main_widget)
        main_layout.setSpacing(8)
        main_layout.setContentsMargins(10, 10, 10, 10)

        # Заголовок приложения
        header_label = QLabel("🎯 VK Avatar Uploader Pro")
        header_label.setFont(QFont("Segoe UI", 16, QFont.Bold))
        header_label.setAlignment(Qt.AlignCenter)
        main_layout.addWidget(header_label)

        # Секция прогресс-бара
        progress_group = QGroupBox("📊 Прогресс выполнения")
        progress_layout = QVBoxLayout(progress_group)
        
        self.progress_bar = QProgressBar()
        self.progress_bar.setValue(0)
        self.progress_bar.setTextVisible(True)
        self.progress_bar.setFixedHeight(20)
        progress_layout.addWidget(self.progress_bar)
        
        labels_info_layout = QHBoxLayout()
        self.progress_label = QLabel("🎯 Готов к работе")
        labels_info_layout.addWidget(self.progress_label)
        labels_info_layout.addStretch()
        self.elapsed_time_label.setAlignment(Qt.AlignRight)
        labels_info_layout.addWidget(self.elapsed_time_label)
        progress_layout.addLayout(labels_info_layout)
        
        main_layout.addWidget(progress_group)

        # Секция информационных меток
        stats_group = QGroupBox("📈 Статистика")
        stats_layout = QHBoxLayout(stats_group)
        
        self.successful_avatars_label = QLabel("✅ Успешные установки: 0")
        self.successful_avatars_label.setObjectName("successfulAvatarsLabel")
        self.successful_avatars_label.setToolTip("Нажмите, чтобы скопировать успешные токены")
        self.successful_avatars_label.mousePressEvent = lambda e: self.copy_tokens_to_clipboard_handler(True)
        self.successful_avatars_label.setCursor(QCursor(Qt.PointingHandCursor))
        stats_layout.addWidget(self.successful_avatars_label)
        
        stats_layout.addWidget(create_vertical_separator())
        
        self.failed_avatars_label = QLabel("❌ Неуспешные установки: 0")
        self.failed_avatars_label.setObjectName("failedAvatarsLabel")
        self.failed_avatars_label.setToolTip("Нажмите, чтобы скопировать все неуспешные токены")
        self.failed_avatars_label.mousePressEvent = lambda e: self.copy_tokens_to_clipboard_handler(False)
        self.failed_avatars_label.setCursor(QCursor(Qt.PointingHandCursor))
        stats_layout.addWidget(self.failed_avatars_label)
        
        stats_layout.addWidget(create_vertical_separator())
        
        self.last_avatar_date_label = QLabel("🕐 Последняя установка: Нет данных")
        self.last_avatar_date_label.setObjectName("lastAvatarDateLabel")
        self.last_avatar_date_label.setToolTip("Дата и время последней успешной установки аватарки")
        stats_layout.addWidget(self.last_avatar_date_label)

        stats_layout.addWidget(create_vertical_separator())

        self.remaining_avatars_label = QLabel("🖼️ Оставшиеся аватарки: 0")
        self.remaining_avatars_label.setToolTip("Количество оставшихся аватарок в папке 'аватарки'")
        stats_layout.addWidget(self.remaining_avatars_label)
        
        main_layout.addWidget(stats_group)

        # Секция сетевой информации
        network_group = QGroupBox("🌐 Сетевая информация")
        network_layout = QHBoxLayout(network_group)
        
        self.wifi_status_label = QLabel("📶 Wi-Fi: Сканирование...")
        network_layout.addWidget(self.wifi_status_label)
        
        network_layout.addWidget(create_vertical_separator())
        
        self.ip_label = QLabel("🌍 IP: Сканирование...")
        network_layout.addWidget(self.ip_label)
        
        network_layout.addWidget(create_vertical_separator())
        
        self.location_label = QLabel("📍 Локация: Сканирование...")
        network_layout.addWidget(self.location_label)
        
        main_layout.addWidget(network_group)

        # Новая секция для смены IP
        ip_change_group = QGroupBox("🔄 Смена IP")
        ip_change_layout = QVBoxLayout(ip_change_group)

        # Комбо-бокс для выбора модели устройства
        self.device_combobox = QComboBox()
        self.device_combobox.addItem("Нет подключённых устройств")
        self.device_combobox.currentTextChanged.connect(self.on_model_selected)
        ip_change_layout.addWidget(QLabel("Выберите устройство:"))
        ip_change_layout.addWidget(self.device_combobox)

        # Кнопка для сканирования устройств
        self.scan_devices_button = QPushButton("Сканировать устройства")
        self.scan_devices_button.clicked.connect(self.scan_devices_async)
        ip_change_layout.addWidget(self.scan_devices_button)

        # Новые галочки для смены IP в начале и в конце (по горизонтали)
        ip_change_checks_layout = QHBoxLayout()
        self.change_ip_start_check = QCheckBox("Сменить IP в начале процесса")
        self.change_ip_start_check.setToolTip("Автоматически сменить IP перед началом установки аватарок")
        self.change_ip_start_check.stateChanged.connect(self.save_change_ip_start_state)
        ip_change_checks_layout.addWidget(self.change_ip_start_check)

        self.change_ip_end_check = QCheckBox("Сменить IP в конце процесса")
        self.change_ip_end_check.setToolTip("Автоматически сменить IP после завершения установки аватарок")
        self.change_ip_end_check.stateChanged.connect(self.save_change_ip_end_state)
        ip_change_checks_layout.addWidget(self.change_ip_end_check)
        ip_change_checks_layout.addStretch()
        ip_change_layout.addLayout(ip_change_checks_layout)

        # Новое поле для лимита смены IP
        ip_limit_layout = QHBoxLayout()
        self.ip_limit_label = QLabel("Лимит аватарок перед сменой IP:")
        ip_limit_layout.addWidget(self.ip_limit_label)
        self.ip_limit_edit = QLineEdit()
        self.ip_limit_edit.setToolTip("Введите число аватарок, после которого будет смена IP (0 - отключено)")
        # Загружаем сохраненное значение
        saved_limit = self.settings.value('ip_change_limit', "0")
        self.ip_limit_edit.setText(saved_limit)
        # Подключаем сигнал для сохранения в реальном времени
        self.ip_limit_edit.textChanged.connect(self.save_ip_limit)
        ip_limit_layout.addWidget(self.ip_limit_edit)
        ip_change_layout.addLayout(ip_limit_layout)

        main_layout.addWidget(ip_change_group)

        # Секция настроек
        settings_group = QGroupBox("⚙️ Настройки")
        settings_layout = QHBoxLayout(settings_group)
        
        self.auto_avatar_check = QCheckBox("🤖 Автоустановка уникальных аватарок")
        self.auto_avatar_check.stateChanged.connect(self.save_auto_avatar_state_setting)
        self.auto_avatar_check.stateChanged.connect(self.update_add_avatar_button_state)
        settings_layout.addWidget(self.auto_avatar_check)
        
        settings_layout.addWidget(create_vertical_separator())
        
        self.delete_posts_check = QCheckBox("🗑️ Удалять автопосты")
        self.delete_posts_check.setChecked(True)
        self.delete_posts_check.setToolTip("Удалять автоматически созданные посты при установке аватарки")
        self.delete_posts_check.stateChanged.connect(self.save_delete_posts_state)
        settings_layout.addWidget(self.delete_posts_check)
        
        settings_layout.addStretch()
        main_layout.addWidget(settings_group)

        # Секция управления аккаунтами
        accounts_group = QGroupBox("👥 Управление аккаунтами")
        accounts_layout = QHBoxLayout(accounts_group)
        
        self.account_button = QPushButton("📂 Указать аккаунты")
        self.account_button.clicked.connect(self.load_tokens_ui_update)
        accounts_layout.addWidget(self.account_button)
        
        accounts_layout.addWidget(create_vertical_separator())
        
        self.tokens_label = QLabel("👤 Токенов: 0")
        accounts_layout.addWidget(self.tokens_label)
        accounts_layout.addStretch()
        main_layout.addWidget(accounts_group)

        # Секция кнопок управления
        controls_group = QGroupBox("🎮 Управление")
        controls_layout = QHBoxLayout(controls_group)
        controls_layout.addStretch()
        
        self.upload_button = QPushButton("🚀 Установить уникальные аватарки")
        self.upload_button.clicked.connect(self.submit_upload_handler)
        controls_layout.addWidget(self.upload_button)
        
        controls_layout.addWidget(create_vertical_separator())
        
        self.stop_button = QPushButton("⏸️ Остановить")
        self.stop_button.setEnabled(False)
        self.stop_button.clicked.connect(self.stop_upload_handler)
        controls_layout.addWidget(self.stop_button)
        
        controls_layout.addWidget(create_vertical_separator())
        
        self.retry_other_errors_button = QPushButton("🔄 Повторить для 'Прочих ошибок'")
        self.retry_other_errors_button.clicked.connect(self.retry_other_errors_handler)
        self.retry_other_errors_button.setEnabled(False)
        controls_layout.addWidget(self.retry_other_errors_button)

        controls_layout.addWidget(create_vertical_separator())

        self.add_avatar_button = QPushButton("➕ Добавить аватарки")
        self.add_avatar_button.clicked.connect(self.open_add_avatar_dialog)
        controls_layout.addWidget(self.add_avatar_button)
        
        controls_layout.addWidget(create_vertical_separator())
        
        self.remove_avatar_button = QPushButton("🗑️ Удалить выбранные")
        self.remove_avatar_button.clicked.connect(self.remove_selected_avatars)
        controls_layout.addWidget(self.remove_avatar_button)
        controls_layout.addStretch()
        controls_group.setLayout(controls_layout)
        main_layout.addWidget(controls_group)

        # Основные панели (таблица и лог)
        splitter_schedule_log = QSplitter(Qt.Horizontal)
        
        # Таблица аватарок
        avatar_table_container = QGroupBox("📋 План установки уникальных аватарок")
        avatar_table_layout = QVBoxLayout(avatar_table_container)
        self.avatar_schedule_table = QTableWidget()
        self.avatar_schedule_table.setColumnCount(2)
        self.avatar_schedule_table.setHorizontalHeaderLabels(["№", "📸 Аватарка"])
        self.avatar_schedule_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
        self.avatar_schedule_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents)
        self.avatar_schedule_table.setSelectionBehavior(QTableWidget.SelectRows)
        self.avatar_schedule_table.setContextMenuPolicy(Qt.CustomContextMenu)
        self.avatar_schedule_table.customContextMenuRequested.connect(self.show_table_context_menu)
        self.avatar_schedule_table.setAlternatingRowColors(True)
        avatar_table_layout.addWidget(self.avatar_schedule_table)
        splitter_schedule_log.addWidget(avatar_table_container)
        
        # Лог процесса
        log_output_container = QGroupBox("📝 Лог процесса")
        log_output_layout = QVBoxLayout(log_output_container)
        self.output_text = QTextEdit()
        self.output_text.setReadOnly(True)
        self.output_text.setContextMenuPolicy(Qt.CustomContextMenu)
        self.output_text.customContextMenuRequested.connect(lambda pos: self.show_text_edit_context_menu(pos, self.output_text))
        log_output_layout.addWidget(self.output_text)
        splitter_schedule_log.addWidget(log_output_container)
        splitter_schedule_log.setSizes([350, 550])
        main_layout.addWidget(splitter_schedule_log, 1)

        # Панели результатов
        splitter_results = QSplitter(Qt.Horizontal)
        
        # Успешные установки
        successful_container = QGroupBox("✅ Успешные установки")
        successful_layout = QVBoxLayout(successful_container)
        self.successful_text = QTextBrowser()
        self.successful_text.setOpenExternalLinks(True)
        self.successful_text.setObjectName("successfulText")
        self.successful_text.setContextMenuPolicy(Qt.CustomContextMenu)
        self.successful_text.customContextMenuRequested.connect(lambda pos: self.show_text_edit_context_menu(pos, self.successful_text))
        successful_layout.addWidget(self.successful_text)
        splitter_results.addWidget(successful_container)
        
        # Неуспешные установки
        failed_container = QGroupBox("❌ Неуспешные установки")
        failed_layout = QVBoxLayout(failed_container)

        # Специфичные счетчики ошибок
        specific_failed_counts_layout = QHBoxLayout()
        self.token_errors_specific_label = QLabel("🔑 Ошибка авторизации: 0")
        self.token_errors_specific_label.setObjectName("tokenErrorsSpecificLabel")
        self.token_errors_specific_label.setToolTip("Нажмите, чтобы скопировать токены с ошибкой авторизации")
        self.token_errors_specific_label.mousePressEvent = lambda event, cat="auth": self.copy_categorized_tokens_handler(cat)
        self.token_errors_specific_label.setCursor(QCursor(Qt.PointingHandCursor))
        specific_failed_counts_layout.addWidget(self.token_errors_specific_label)

        specific_failed_counts_layout.addWidget(create_vertical_separator())

        self.banned_tokens_specific_label = QLabel("🚫 Аккаунт заблокирован: 0")
        self.banned_tokens_specific_label.setObjectName("bannedTokensSpecificLabel")
        self.banned_tokens_specific_label.setToolTip("Нажмите, чтобы скопировать заблокированные токены")
        self.banned_tokens_specific_label.mousePressEvent = lambda event, cat="banned": self.copy_categorized_tokens_handler(cat)
        self.banned_tokens_specific_label.setCursor(QCursor(Qt.PointingHandCursor))
        specific_failed_counts_layout.addWidget(self.banned_tokens_specific_label)
        
        specific_failed_counts_layout.addWidget(create_vertical_separator())
        
        self.other_errors_specific_label = QLabel("⚠️ Прочие ошибки: 0")
        self.other_errors_specific_label.setObjectName("otherErrorsSpecificLabel")
        self.other_errors_specific_label.setToolTip("Нажмите, чтобы скопировать токены с прочими ошибками")
        self.other_errors_specific_label.mousePressEvent = lambda event, cat="other": self.copy_categorized_tokens_handler(cat)
        self.other_errors_specific_label.setCursor(QCursor(Qt.PointingHandCursor))
        specific_failed_counts_layout.addWidget(self.other_errors_specific_label)
        
        specific_failed_counts_layout.addStretch()
        failed_layout.addLayout(specific_failed_counts_layout)

        failed_layout.addWidget(create_horizontal_separator())

        self.failed_text = QTextEdit()
        self.failed_text.setReadOnly(True)
        self.failed_text.setObjectName("failedText")
        self.failed_text.setContextMenuPolicy(Qt.CustomContextMenu)
        self.failed_text.customContextMenuRequested.connect(lambda pos: self.show_text_edit_context_menu(pos, self.failed_text))
        failed_layout.addWidget(self.failed_text)
        splitter_results.addWidget(failed_container)
        splitter_results.setSizes([450, 450])
        main_layout.addWidget(splitter_results, 1)
        self.setFont(FONT_STYLE)

    def update_remaining_avatars(self):
        if os.path.exists(AVATARS_DIR):
            remaining = len([f for f in os.listdir(AVATARS_DIR) 
                             if f.lower().endswith(('.jpg', '.jpeg', '.png')) 
                             and os.path.isfile(os.path.join(AVATARS_DIR, f))])
            self.remaining_avatars_label.setText(f"🖼️ Оставшиеся аватарки: {remaining}")
        else:
            self.remaining_avatars_label.setText("🖼️ Оставшиеся аватарки: 0 (папка не найдена)")

    def update_runtime_display(self):
        self.elapsed_seconds += 1
        hours = self.elapsed_seconds // 3600
        minutes = (self.elapsed_seconds % 3600) // 60
        seconds = self.elapsed_seconds % 60
        self.elapsed_time_label.setText(f"⏱️ Время: {hours:02}:{minutes:02}:{seconds:02}")

    def copy_tokens_to_clipboard_handler(self, successful=True):
        tokens_set = self.final_successful_tokens_for_ui_copy if successful else self.final_failed_tokens_for_ui_copy
        type_name = "успешных" if successful else "всех неуспешных"
        if not tokens_set:
            self.log_message_slot(f"Нет {type_name} токенов для копирования в буфер.\n")
            return
        try:
            tokens_str = "\n".join(sorted(list(tokens_set)))
            QApplication.clipboard().setText(tokens_str)
            self.log_message_slot(f"{type_name.capitalize()} токены ({len(tokens_set)} шт.) скопированы в буфер обмена.\n")
        except Exception as e:
            self.log_message_slot(f"Ошибка копирования токенов ({type_name}) в буфер: {e}\n")
            
    def copy_categorized_tokens_handler(self, category_key):
        tokens_to_copy = set()
        category_name_log = ""

        if category_key == "auth":
            tokens_to_copy = self.auth_error_tokens
            category_name_log = "с ошибкой авторизации (токен)"
        elif category_key == "banned":
            tokens_to_copy = self.banned_error_tokens
            category_name_log = "заблокированных аккаунтов"
        elif category_key == "other":
            tokens_to_copy = self.other_error_tokens
            category_name_log = "с прочими ошибками"
        else:
            self.log_message_slot(f"Неизвестная категория токенов для копирования: {category_key}\n")
            return

        if not tokens_to_copy:
            self.log_message_slot(f"Нет токенов ({category_name_log}) для копирования.\n")
            return
        try:
            tokens_str = "\n".join(sorted(list(tokens_to_copy)))
            QApplication.clipboard().setText(tokens_str)
            self.log_message_slot(f"Токены ({len(tokens_to_copy)} шт.) {category_name_log} скопированы в буфер обмена.\n")
        except Exception as e:
            self.log_message_slot(f"Ошибка копирования токенов ({category_name_log}) в буфер: {e}\n")

    def receive_avatar_update_from_worker(self, new_schedule):
        self.avatar_schedule = new_schedule
        self.update_avatar_schedule_table_display()

    def increment_progress_bar_slot(self):
        self.progress_bar.setValue(self.progress_bar.value() + 1)

    def show_text_edit_context_menu(self, position, text_edit_widget):
        menu = QMenu()
        copy_action = menu.addAction("📋 Копировать")
        select_all_action = menu.addAction("🔍 Выбрать все")
        menu.addSeparator()
        if text_edit_widget == self.output_text:
            auto_scroll_action = QAction("📜 Включить автопрокрутку", self, checkable=True)
            auto_scroll_action.setChecked(self.auto_scroll_log)
            auto_scroll_action.triggered.connect(self.toggle_auto_scroll_log)
            menu.addAction(auto_scroll_action)
        action = menu.exec_(text_edit_widget.mapToGlobal(position))
        if action == copy_action: 
            text_edit_widget.copy()
        elif action == select_all_action: 
            text_edit_widget.selectAll()

    def toggle_auto_scroll_log(self):
        self.auto_scroll_log = not self.auto_scroll_log
        self.log_message_slot(f"Автопрокрутка лога {'включена' if self.auto_scroll_log else 'отключена'}.\n")

    def show_table_context_menu(self, position):
        menu = QMenu()
        select_all_action = menu.addAction("🔍 Выбрать все")
        remove_action = menu.addAction("🗑️ Удалить выбранные")
        if not self.avatar_schedule_table.selectedItems(): 
            remove_action.setEnabled(False)
        action = menu.exec_(self.avatar_schedule_table.viewport().mapToGlobal(position))
        if action == select_all_action: 
            self.avatar_schedule_table.selectAll()
        elif action == remove_action: 
            self.remove_selected_avatars()

    def update_info_display_slot(self):
        threading.Thread(target=self._fetch_network_info, daemon=True).start()

    def _fetch_network_info(self):
        ssid = get_current_wifi_ssid()
        ip = get_external_ip()
        location = "Недоступно"
        if ip != "Недоступно": 
            location = get_location_by_ip(ip)
        self.wifi_status_label.setText(f"📶 Wi-Fi: {ssid}")
        self.ip_label.setText(f"🌍 IP: {ip}")
        self.location_label.setText(f"📍 Локация: {location}")

    def load_settings(self):
        auto_avatar_enabled = False
        if os.path.exists(AUTO_AVATAR_STATE_FILE_PATH):
            try:
                with open(AUTO_AVATAR_STATE_FILE_PATH, 'r', encoding='utf-8') as f: 
                    auto_avatar_enabled = f.read().strip().lower() == 'true'
            except Exception as e: 
                self.log_message_slot(f"Ошибка загрузки состояния автоустановки: {e}\n")
        self.auto_avatar_check.setChecked(auto_avatar_enabled)
        
        # Загрузка состояний галочек смены IP
        change_ip_start_enabled = False
        if os.path.exists(CHANGE_IP_START_STATE_FILE_PATH):
            try:
                with open(CHANGE_IP_START_STATE_FILE_PATH, 'r', encoding='utf-8') as f: 
                    change_ip_start_enabled = f.read().strip().lower() == 'true'
            except Exception as e: 
                self.log_message_slot(f"Ошибка загрузки состояния смены IP в начале: {e}\n")
        self.change_ip_start_check.setChecked(change_ip_start_enabled)
        
        change_ip_end_enabled = False
        if os.path.exists(CHANGE_IP_END_STATE_FILE_PATH):
            try:
                with open(CHANGE_IP_END_STATE_FILE_PATH, 'r', encoding='utf-8') as f: 
                    change_ip_end_enabled = f.read().strip().lower() == 'true'
            except Exception as e: 
                self.log_message_slot(f"Ошибка загрузки состояния смены IP в конце: {e}\n")
        self.change_ip_end_check.setChecked(change_ip_end_enabled)
        
        # Загрузка состояния галочки удаления постов
        delete_posts_enabled = True
        if os.path.exists(DELETE_POSTS_STATE_FILE_PATH):
            try:
                with open(DELETE_POSTS_STATE_FILE_PATH, 'r', encoding='utf-8') as f: 
                    delete_posts_enabled = f.read().strip().lower() == 'true'
            except Exception as e: 
                self.log_message_slot(f"Ошибка загрузки состояния удаления постов: {e}\n")
        self.delete_posts_check.setChecked(delete_posts_enabled)
        
        self.load_tokens_ui_update()

    def save_setting_to_file(self, file_path, value_to_save, error_msg_prefix):
        try:
            with open(file_path, 'w', encoding='utf-8') as f: 
                f.write(str(value_to_save))
        except Exception as e: 
            self.log_message_slot(f"{error_msg_prefix}: {e}\n")

    def save_auto_avatar_state_setting(self): 
        self.save_setting_to_file(AUTO_AVATAR_STATE_FILE_PATH, self.auto_avatar_check.isChecked(), "Ошибка сохранения состояния автоустановки")

    def save_change_ip_start_state(self):
        self.save_setting_to_file(CHANGE_IP_START_STATE_FILE_PATH, self.change_ip_start_check.isChecked(), "Ошибка сохранения состояния смены IP в начале")

    def save_change_ip_end_state(self):
        self.save_setting_to_file(CHANGE_IP_END_STATE_FILE_PATH, self.change_ip_end_check.isChecked(), "Ошибка сохранения состояния смены IP в конце")

    def save_delete_posts_state(self):
        self.save_setting_to_file(DELETE_POSTS_STATE_FILE_PATH, self.delete_posts_check.isChecked(), "Ошибка сохранения состояния удаления постов")

    def save_ip_limit(self, text):
        self.settings.setValue('ip_change_limit', text)

    def load_tokens_ui_update(self):
        if os.path.exists(TOKENS_FILE_PATH):
            try:
                with open(TOKENS_FILE_PATH, 'r', encoding='utf-8') as file: 
                    self.tokens = [token.strip() for token in file.readlines() if token.strip()]
                self.tokens_label.setText(f"👤 Токенов загружено: {len(self.tokens)}")
                if self.tokens: 
                    self.log_message_slot(f"Загружено {len(self.tokens)} токенов.\n")
                else: 
                    self.log_message_slot("Файл токенов пуст.\n")
            except Exception as e:
                self.log_message_slot(f"Ошибка чтения файла токенов: {e}\n")
                QMessageBox.critical(self, "Ошибка токенов", f"Не удалось прочитать файл токенов: {e}")
                self.tokens = []
                self.tokens_label.setText("👤 Токенов: 0 (ошибка)")
        else:
            self.log_message_slot(f"Файл с токенами не найден: {TOKENS_FILE_PATH}\n")
            self.tokens_label.setText("👤 Токенов: 0 (файл не найден)")
        self.update_buttons_on_token_load()

    def update_buttons_on_token_load(self):
        has_tokens = bool(self.tokens)
        self.upload_button.setEnabled(has_tokens)
        self.remove_avatar_button.setEnabled(has_tokens and bool(self.avatar_schedule))
        self.auto_avatar_check.setEnabled(has_tokens)
        if not has_tokens: 
            self.auto_avatar_check.setChecked(False)
        self.update_add_avatar_button_state()
        self.update_retry_button_state()

    def update_add_avatar_button_state(self):
        is_auto_avatar = self.auto_avatar_check.isChecked()
        self.add_avatar_button.setEnabled(not is_auto_avatar and bool(self.tokens))
        if is_auto_avatar and self.avatar_schedule:
            self.log_message_slot("Режим автоустановки включен. Ручной план аватарок очищен.\n")
            self.avatar_schedule.clear()
            self.update_avatar_schedule_table_display()
        self.update_retry_button_state()

    def update_retry_button_state(self):
        can_retry = bool(self.other_error_tokens) and not (self.upload_thread and self.upload_thread.isRunning())
        self.retry_other_errors_button.setEnabled(can_retry)

    def open_add_avatar_dialog(self):
        dialog = AddAvatarDialog(self)
        dialog.exec_()

    def add_avatar_data(self, data):
        self.avatar_schedule.append(data)
        self.update_avatar_schedule_table_display()
        self.update_buttons_on_token_load()

    def remove_selected_avatars(self):
        selected_rows = sorted(list(set(item.row() for item in self.avatar_schedule_table.selectedItems())), reverse=True)
        if not selected_rows:
            QMessageBox.warning(self, "Предупреждение", "Выберите запись для удаления.")
            return
        removed_count = 0
        for row_index in selected_rows:
            if 0 <= row_index < len(self.avatar_schedule):
                del self.avatar_schedule[row_index]
                self.log_message_slot(f"Удалена аватарка (бывшая №{row_index + 1} в списке до удаления).\n")
                removed_count += 1
        if removed_count > 0: 
            self.update_avatar_schedule_table_display()
        self.update_buttons_on_token_load()

    def update_avatar_schedule_table_display(self):
        self.avatar_schedule_table.setRowCount(0)
        for idx, entry in enumerate(self.avatar_schedule):
            self.avatar_schedule_table.insertRow(idx)
            avatar_name = os.path.basename(entry['photo'])
            self.avatar_schedule_table.setItem(idx, 0, QTableWidgetItem(str(idx + 1)))
            self.avatar_schedule_table.setItem(idx, 1, QTableWidgetItem(f"📸 {avatar_name}"))
        self.avatar_schedule_table.resizeColumnsToContents()
        self.avatar_schedule_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)

    def submit_upload_handler(self):
        if self.upload_thread and self.upload_thread.isRunning():
            self.log_message_slot("Процесс установки аватарок уже запущен.\n")
            return
        
        self.clear_previous_results()
        self.final_failed_tokens_for_ui_copy.clear()
        self.final_successful_tokens_for_ui_copy.clear()
        
        self.upload_start_time = time.monotonic()
        self.elapsed_seconds = 0
        self.elapsed_time_label.setText("⏱️ Время: 00:00:00")
        self.runtime_qtimer.start(1000)

        # Проверка галочки для смены IP в начале
        if self.change_ip_start_check.isChecked():
            self.log_message_slot("Смена IP перед началом процесса...\n")
            self.run_flight_mode_scenario()

        is_auto_avatar = self.auto_avatar_check.isChecked()
        delete_posts = self.delete_posts_check.isChecked()
        
        try:
            ip_change_limit = int(self.ip_limit_edit.text())
        except ValueError:
            ip_change_limit = 0
        
        self.upload_thread = AvatarWorker(
            list(self.tokens),
            list(self.avatar_schedule),
            is_auto_avatar,
            self.worker_signals,
            delete_posts,
            ip_change_limit,
            self  # Передаём self для доступа к методам смены IP
        )
        self.upload_thread.start()
        self.progress_bar.setValue(0)

    def retry_other_errors_handler(self):
        if self.upload_thread and self.upload_thread.isRunning():
            self.log_message_slot("Процесс установки аватарок уже запущен.\n")
            return

        if not self.other_error_tokens:
            self.log_message_slot("Нет токенов в категории 'Прочие ошибки' для повтора.\n")
            return

        tokens_for_retry = list(self.other_error_tokens)
        self.log_message_slot(f"Запуск повторной установки для {len(tokens_for_retry)} токенов из 'Прочих ошибок'...\n")
        
        self.clear_previous_results()
        self.final_failed_tokens_for_ui_copy.clear()
        self.final_successful_tokens_for_ui_copy.clear()
            
        is_auto_avatar = self.auto_avatar_check.isChecked()
        avatar_schedule_for_retry = []

        if not is_auto_avatar:
            if not self.avatar_schedule:
                self.log_message_slot("Нет аватарок в ручном режиме для повтора.\n")
                QMessageBox.information(self, "Повтор", "План аватарок пуст. Добавьте аватарки для установки.")
                self.update_retry_button_state()
                return
            avatar_schedule_for_retry = list(self.avatar_schedule)

        self.upload_start_time = time.monotonic()
        self.elapsed_seconds = 0
        self.elapsed_time_label.setText("⏱️ Время: 00:00:00")
        self.runtime_qtimer.start(1000)

        # Проверка галочки для смены IP в начале для повтора
        if self.change_ip_start_check.isChecked():
            self.log_message_slot("Смена IP перед началом процесса...\n")
            self.run_flight_mode_scenario()

        delete_posts = self.delete_posts_check.isChecked()
        
        try:
            ip_change_limit = int(self.ip_limit_edit.text())
        except ValueError:
            ip_change_limit = 0

        self.upload_thread = AvatarWorker(
            tokens_for_retry,
            avatar_schedule_for_retry,
            is_auto_avatar,
            self.worker_signals,
            delete_posts,
            ip_change_limit,
            self
        )
        self.upload_thread.start()
        self.progress_bar.setValue(0)

    def stop_upload_handler(self):
        if self.upload_thread and self.upload_thread.isRunning():
            stop_flag.set()
            self.log_message_slot("Остановка установки аватарок...\n")
            self.stop_button.setEnabled(False)

    def clear_previous_results(self):
        self.output_text.clear()
        self.successful_text.clear()
        self.failed_text.clear()
        self.successful_avatars_label.setText("✅ Успешные установки: 0")
        self.failed_avatars_label.setText("❌ Неуспешные установки: 0")
        
        self.token_error_specific_count = 0
        self.banned_token_specific_count = 0
        self.other_error_specific_count = 0
        self.token_errors_specific_label.setText("🔑 Ошибка авторизации: 0")
        self.banned_tokens_specific_label.setText("🚫 Аккаунт заблокирован: 0")
        self.other_errors_specific_label.setText("⚠️ Прочие ошибки: 0")

        self.auth_error_tokens.clear()
        self.banned_error_tokens.clear()
        self.other_error_tokens.clear()
        
        self.elapsed_time_label.setText("⏱️ Время: 00:00:00")
        self.elapsed_seconds = 0
        self.update_retry_button_state()

    def log_message_slot(self, message):
        self.output_text.append(message.strip())
        if self.auto_scroll_log: 
            self.output_text.verticalScrollBar().setValue(self.output_text.verticalScrollBar().maximum())

    def update_progress_labels_slot(self, success_count, failed_count, total_expected):
        self.successful_avatars_label.setText(f"✅ Успешные установки: {success_count}")
        self.failed_avatars_label.setText(f"❌ Неуспешные установки: {failed_count}")
        remaining = max(0, total_expected - (success_count + failed_count))
        self.progress_label.setText(f"🎯 Осталось установок: {remaining}")

    def successful_avatar_slot(self, message, link):
        self.successful_text.append(f"✅ {message} <a href='{link}' style='color: #89b4fa; text-decoration: none;'>{link}</a><br>")
        self.save_last_avatar_date()

    def failed_avatar_slot(self, message, token):
        self.failed_text.append(message.strip())

        is_token_error = "User authorization failed: invalid access_token (4)" in message
        is_banned_error = ("account has been blocked" in message.lower() or
                           "user is blocked" in message.lower() or
                           "user was banned" in message.lower())

        if token:
            if is_token_error:
                self.token_error_specific_count += 1
                self.token_errors_specific_label.setText(f"🔑 Ошибка авторизации: {self.token_error_specific_count}")
                self.auth_error_tokens.add(token)
            elif is_banned_error:
                self.banned_token_specific_count += 1
                self.banned_tokens_specific_label.setText(f"🚫 Аккаунт заблокирован: {self.banned_token_specific_count}")
                self.banned_error_tokens.add(token)
            else:
                self.other_error_specific_count += 1
                self.other_errors_specific_label.setText(f"⚠️ Прочие ошибки: {self.other_error_specific_count}")
                self.other_error_tokens.add(token)
        
        self.update_retry_button_state()

    def toggle_upload_buttons_slot(self, enable_main_actions):
        self.upload_button.setEnabled(enable_main_actions)
        self.stop_button.setEnabled(not enable_main_actions)
        
        self.add_avatar_button.setEnabled(enable_main_actions and not self.auto_avatar_check.isChecked())
        self.remove_avatar_button.setEnabled(enable_main_actions and bool(self.avatar_schedule))
        self.account_button.setEnabled(enable_main_actions)
        self.auto_avatar_check.setEnabled(enable_main_actions)
        self.update_retry_button_state()

    def on_upload_finished(self):
        self.runtime_qtimer.stop()
        if self.upload_thread:
            self.final_failed_tokens_for_ui_copy = self.upload_thread.get_final_failed_tokens()
            self.final_successful_tokens_for_ui_copy = self.upload_thread.get_final_successful_tokens()
        
        self.toggle_upload_buttons_slot(True)
        
        self.upload_thread = None
        stop_flag.clear()
        if self.progress_bar.value() < self.progress_bar.maximum():
            self.progress_bar.setValue(self.progress_bar.maximum())

        # Проверка галочки для смены IP в конце
        if self.change_ip_end_check.isChecked():
            self.log_message_slot("Смена IP после завершения процесса...\n")
            self.run_flight_mode_scenario()

        self.update_remaining_avatars()  # Обновление метки оставшихся аватарок после завершения

    def closeEvent(self, event):
        if self.upload_thread and self.upload_thread.isRunning():
            reply = QMessageBox.question(self, "Подтверждение",
                                         "Процесс установки аватарок еще активен. Вы уверены, что хотите выйти?",
                                         QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
            if reply == QMessageBox.Yes:
                stop_flag.set()
                if self.upload_thread: 
                    self.upload_thread.wait(5000)
                self.runtime_qtimer.stop()
                event.accept()
            else: 
                event.ignore()
        else:
            self.runtime_qtimer.stop()
            event.accept()

    # Методы для смены IP
    def scan_devices_async(self):
        """Асинхронное сканирование подключённых устройств через ADB"""
        def worker():
            global devices_by_model
            devices_by_model.clear()
            try:
                result = subprocess.run(["adb", "devices", "-l"], capture_output=True, text=True, check=False)
                output = result.stdout.strip().splitlines()
                for line in output[1:]:  # Пропускаем первую строку
                    line = line.strip()
                    if not line or "offline" in line or "unauthorized" in line or "unknown" in line:
                        continue
                    parts = line.split()
                    serial = parts[0]
                    model = None
                    for p in parts:
                        if p.startswith("model:"):
                            model = p.split(":", 1)[1]
                            break
                    if not model:
                        model = "UnknownModel"
                    devices_by_model[model] = serial
                print(f"Обнаруженные устройства: {devices_by_model}")
            except Exception as e:
                print(f"Ошибка при сканировании устройств: {e}")

            model_list = sorted(list(devices_by_model.keys()))  # Сортируем для стабильности
            self.worker_signals.device_scan_completed.emit(model_list)

        thread = threading.Thread(target=worker, daemon=True)
        thread.start()

    def update_device_list(self, model_list):
        """Обновление комбо-бокса списком устройств с сохранением выбора"""
        # Сохраняем текущий выбор перед обновлением
        current_model = self.device_combobox.currentText()
        
        self.device_combobox.blockSignals(True)  # Блокируем сигналы, чтобы не вызывать сохранение во время обновления
        self.device_combobox.clear()
        if model_list:
            self.device_combobox.addItems(model_list)
        else:
            self.device_combobox.addItem("Нет подключённых устройств")
        
        # Восстанавливаем предыдущий выбор
        index = self.device_combobox.findText(current_model)
        if index != -1:
            self.device_combobox.setCurrentIndex(index)
        else:
            saved_model = self.settings.value('selected_device', '')
            index = self.device_combobox.findText(saved_model)
            if index != -1:
                self.device_combobox.setCurrentIndex(index)
        
        self.device_combobox.blockSignals(False)  # Разблокируем сигналы
        
        # Сохраняем текущее значение после обновления
        current_after_update = self.device_combobox.currentText()
        self.settings.setValue('selected_device', current_after_update)

    def on_model_selected(self, model):
        """Обработчик выбора модели устройства"""
        print(f"Выбрана модель: {model}")
        self.settings.setValue('selected_device', model)

    def save_last_model(self, model):
        """Сохраняет выбранную модель в файл (резерв, если нужно)"""
        try:
            with open(LAST_MODEL_FILE_PATH, 'w', encoding='utf-8') as f:
                f.write(model)
        except Exception as e:
            self.log_message_slot(f"Ошибка сохранения выбранной модели: {e}\n")

    def load_last_model(self):
        """Загружает выбранную модель из файла (резерв)"""
        if os.path.exists(LAST_MODEL_FILE_PATH):
            try:
                with open(LAST_MODEL_FILE_PATH, 'r', encoding='utf-8') as f:
                    return f.read().strip()
            except Exception as e:
                self.log_message_slot(f"Ошибка загрузки выбранной модели: {e}\n")
                return ""
        return ""

    def run_flight_mode_scenario(self):
        """Сценарий смены IP: включение/выключение авиарежима и tethering на выбранном устройстве"""
        chosen_model = self.device_combobox.currentText()
        if chosen_model == "Нет подключённых устройств" or chosen_model not in devices_by_model:
            self.log_message_slot("Ошибка: Не выбрано устройство или оно недоступно.\n")
            return

        serial = devices_by_model[chosen_model]
        try:
            self.log_message_slot(f"Запуск сценария смены IP на устройстве '{chosen_model}' (serial: {serial}).\n")
            d = u2.connect(serial)  # Подключение к устройству через uiautomator2
            self.log_message_slot(f"Информация об устройстве: {d.device_info}\n")

            # Шаг 1: Открыть настройки авиарежима
            subprocess.run(["adb", "-s", serial, "shell", "am", "start", "-a", "android.settings.AIRPLANE_MODE_SETTINGS"])
            time.sleep(0.3)

            # Шаг 2: Переключить авиарежим (вкл/выкл)
            switch_element = d(resourceId="android:id/switch_widget")
            if switch_element.exists(timeout=3):
                switch_element.click()  # Включить/выключить
                time.sleep(0.3)
                switch_element.click()  # Вернуть в исходное (для цикла)
                time.sleep(0.5)
            else:
                self.log_message_slot("Ошибка: Переключатель авиарежима не найден.\n")

            # Шаг 3: Активировать tethering (мобильный hotspot для смены IP)
            d.app_start("com.android.settings", ".TetherSettings")
            time.sleep(0.3)
            if d(resourceId="com.android.settings:id/recycler_view").child(index=0).exists(timeout=2):
                d(resourceId="com.android.settings:id/recycler_view").child(index=0).click()
                time.sleep(0.3)
            else:
                self.log_message_slot("Ошибка: Элемент tethering не найден.\n")

            # Шаг 4: Закрыть настройки (нажатие 'back' несколько раз)
            for _ in range(3):
                d.press("back")
                time.sleep(0.2)

            self.log_message_slot("Сценарий смены IP завершён. Ожидайте подключения к новой сети.\n")
            time.sleep(1)  # Задержка для стабилизации соединения

        except Exception as e:
            self.log_message_slot(f"Ошибка при выполнении сценария смены IP: {e}\n")

if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    app.setStyle('Fusion')
    app.setStyleSheet(DARK_STYLESHEET)
    mainWin = VKAvatarUploaderApp()
    mainWin.show()
    sys.exit(app.exec_())
