# -*- coding: utf-8 -*-

# Built-in modules
import time
import inspect
import random
import sys
from functools import wraps
from collections import defaultdict
from typing import Optional, Dict, Any, List, Tuple, Callable
import string
import msvcrt
import platform
import os
import tkinter as tk
from tkinter import font as tkfont

# Third-party modules
os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "1"  # Disable startup message
import pygame
import pygame.locals

# Local modules
from yltop.umodules.edit_file import _open
from yltop.recording import locate, check_type


# Decorators
def with_timing(func: Callable, callback: Callable[[str, float], None]) -> Callable:
    """Execute function with timing and report via callback

    Args:
        func: Function to time
        callback: Timing report callback (function_name, duration)

    Returns:
        Decorated function
    """

    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = None
        exception = None

        try:
            result = func(*args, **kwargs)
        except Exception as e:
            exception = e
        finally:
            elapsed = time.perf_counter() - start
            callback(func.__name__, elapsed)

            if exception is not None:
                raise exception

        return result

    return wrapper


def log_time(name: str, duration: float):
    """Log and print function execution time

    Args:
        name: Function name
        duration: Execution time in seconds
    """
    print(f"⏱️ {name} took: {duration:.2f} seconds")


class Warning(Exception):
    """Custom warning exception class"""

    def __init__(self, msg: str):
        super().__init__(msg)


def print_sequentially(file_path: str, speed: float = 0.1, encoding: str = 'utf-8'):
    """Print file content sequentially with arrow indicator

    Args:
        file_path: Path to file
        speed: Print speed in seconds per line (0 = immediate)
        encoding: File encoding
    """
    try:
        with open(file_path, 'r', encoding=encoding) as file:
            previous_lines = []
            current_line = None

            while True:
                line = file.readline()
                if not line:  # EOF
                    break

                clear_console()

                # Update current arrow line
                current_line = line

                # Print all previous lines
                for prev_line in previous_lines:
                    print(f"  {prev_line}", end='')

                # Print current line with arrow
                print(f"—▷ {current_line}", end='')

                # Add current line to previous lines
                previous_lines.append(current_line)

                if speed > 0:
                    time.sleep(speed)  # Delay between lines
            print('\r')
    except FileNotFoundError:
        print(f"Error: File not found: {file_path}")
    except Exception as e:
        print(f"Error reading file: {e}")



# File operations
def count_lines(file_path: str, encoding: str = "utf-8") -> int:
    """Count lines in a file

    Args:
        file_path: Path to file
        encoding: File encoding

    Returns:
        Number of lines or 0 on error
    """
    try:
        with _open(file_path, 'r', encoding=encoding) as f:
            return sum(1 for _ in f)
    except FileNotFoundError:
        print(f"Error: File not found: {file_path}")
        return 0
    except Exception as e:
        print(f"Error reading file: {e}")
        return 0


def rename_file(file_path: str, suffix: str):
    """Rename file by adding suffix to filename

    Args:
        file_path: Path to file
        suffix: Suffix to add to filename
    """
    try:
        name, ext = os.path.splitext(file_path)
        new_name = f"{name}{suffix}{ext}"
        os.rename(file_path, new_name)
        print(f"File renamed to: {new_name}")
    except FileNotFoundError:
        print(f"Error: File not found: {file_path}")
    except Exception as e:
        print(f"Error renaming file: {e}")


# String manipulation
def uniform_case(text: str, case_mode: str = 'lower') -> str:
    """Convert string to uniform case

    Args:
        text: String to convert
        case_mode: 'lower' for lowercase, 'upper' for uppercase

    Returns:
        Converted string

    Raises:
        TypeError: If input not string
        ValueError: If invalid case_mode
    """
    if not isinstance(text, str):
        raise TypeError("Input must be a string")

    if case_mode == 'lower':
        return text.lower()
    elif case_mode == 'upper':
        return text.upper()
    else:
        raise ValueError("case_mode must be 'lower' or 'upper'")


def invert_dict(data: dict) -> dict:
    """Invert dictionary handling duplicate values

    Args:
        data: Dictionary to invert

    Returns:
        Inverted dictionary with values grouped in lists

    Raises:
        TypeError: If input not dictionary
    """
    if not isinstance(data, dict):
        raise TypeError("Input must be a dictionary")

    inverted = defaultdict(list)
    for key, value in data.items():
        inverted[value].append(key)

    return dict(inverted)


def insert_string(original: str, position: int, content: Any) -> str:
    """Insert content into string at specified position

    Args:
        original: Original string
        position: Insert position
        content: Content to insert

    Returns:
        New string with inserted content
    """
    str_list = list(original)
    str_list.insert(position, str(content))
    return "".join(str_list)


def is_digit(text: str) -> bool:
    """Check if string contains only digits

    Args:
        text: String to check

    Returns:
        True if only digits, False otherwise
    """
    return text.isdigit()


def is_alpha(text: str) -> bool:
    """Check if string contains only letters

    Args:
        text: String to check

    Returns:
        True if only letters, False otherwise
    """
    return text.isalpha()


def is_alnum(text: str) -> bool:
    """Check if string contains only alphanumeric characters

    Args:
        text: String to check

    Returns:
        True if only alphanumeric, False otherwise
    """
    return text.isalnum()


def is_symbol(text: str) -> bool:
    """Check if string contains only symbol characters

    Args:
        text: String to check

    Returns:
        True if only symbols, False otherwise
    """
    for char in text:
        if char in string.ascii_letters or char in string.digits or char.isspace():
            return False
    return True


def is_space(text: str) -> bool:
    """Check if string contains only whitespace

    Args:
        text: String to check

    Returns:
        True if only whitespace, False otherwise
    """
    return text.isspace()


def find_all_occurrences(text: str, substring: str) -> List[int]:
    """Find all occurrences of substring in text

    Args:
        text: Text to search
        substring: Substring to find

    Returns:
        List of start indices of occurrences
    """
    positions = []
    start = 0
    while True:
        index = text.find(substring, start)
        if index == -1:
            break
        positions.append(index)
        start = index + 1
    return positions


def find_pattern(text: str, pattern: str) -> List[Tuple[int, int]]:
    """Find all occurrences of pattern in text

    Args:
        text: Text to search
        pattern: Pattern to find

    Returns:
        List of (start, end) indices of matches
    """
    matches = []
    start = 0
    while start < len(text):
        first_index = text.find(pattern[0], start)
        if first_index == -1:
            break

        match = True
        for i in range(1, len(pattern)):
            if first_index + i >= len(text) or text[first_index + i] != pattern[i]:
                match = False
                break

        if match:
            matches.append((first_index, first_index + len(pattern) - 1))
            start = first_index + len(pattern)
        else:
            start = first_index + 1

    return matches


# Getter functions
def get_filename(path: str = __file__) -> str:
    """Get filename without external from path

    Args:
        path: File path (default: current file)

    Returns:
        Filename without external

    Raises:
        FileNotFoundError: If file doesn't exist
    """
    if os.path.exists(path):
        return os.path.splitext(os.path.basename(path))[0]
    else:
        raise FileNotFoundError(f"File not found: {path}")


def get_loaded_modules() -> List[str]:
    """Get names of all loaded modules

    Returns:
        List of module names
    """
    return list(sys.modules.keys())


def get_module_classes(module: Any) -> List[str]:
    """Get all class names defined in a module

    Args:
        module: Module to inspect

    Returns:
        List of class names
    """
    return [name for name, obj in inspect.getmembers(module, inspect.isclass)]


def get_function_path(func: Callable) -> str:
    """Get file path where function is defined

    Args:
        func: Function to inspect

    Returns:
        File path of function definition
    """
    check_type(func, Callable, 'func', get_function_path.__name__)
    return func.__code__.co_filename


def print_object_attributes(obj: Any):
    """Print all attributes of an object and their types

    Args:
        obj: Object to inspect
    """
    for attr_name in dir(obj):
        if not attr_name.startswith('__'):  # Skip built-ins
            attr_value = getattr(obj, attr_name)
            attr_type = type(attr_value).__name__
            print(f"{attr_name}: {attr_type}")


def get_function_parameters(func: Callable) -> Optional[List[Dict[str, Any]]]:
    """Get detailed parameter information for a function

    Args:
        func: Function to inspect

    Returns:
        List of parameter info dicts or None if not callable
    """
    if not callable(func):
        return None

    signature = inspect.signature(func)
    parameters = []
    for param in signature.parameters.values():
        param_info = {
            "name": param.name,
            "kind": param.kind.name,
            "default": param.default if param.default is not param.empty else None,
            "annotation": str(param.annotation) if param.annotation is not param.empty else None
        }
        parameters.append(param_info)
    return parameters


def get_function_start_line(func: Callable) -> Optional[int]:
    """Get starting line number of function definition

    Args:
        func: Function to inspect

    Returns:
        Starting line number or None if unavailable
    """
    try:
        return inspect.getsourcelines(func)[1]
    except (OSError, TypeError):
        return None


def get_object_description(obj: Any) -> str:
    """Get basic description of an object (optimized)

    Extracts description part after colon from locate string
    Example: "main.variable: my_var (int)" → "my_var (int)"

    Args:
        obj: Object to describe

    Returns:
        Description string
    """
    location = locate(obj)
    separator = ': '

    if separator in location:
        return location.partition(separator)[2]
    return location


def get_os_name() -> str:
    """Get operating system name

    Returns:
        OS name ("Windows", "Linux", "macOS", etc.)
    """
    os_name = os.name
    system_name = platform.system()

    if os_name == 'nt' or system_name == 'Windows':
        return "Windows"
    elif os_name == 'posix':
        if system_name == 'Darwin':
            return "macOS"
        elif system_name == 'Linux':
            return "Linux"
        else:
            return "Unix-like"
    else:
        return "Unknown"


# Animation functions
def display_battery_animation(start_percent: int):
    """Display battery charging animation

    Args:
        start_percent: Starting battery percentage
    """
    start_percent = max(0, min(start_percent, 100))

    for percent in range(start_percent, 101):
        time.sleep(0.01)
        filled = "■" * percent
        empty = "□" * (100 - percent)
        print(f'\rBattery: {filled}{empty} {percent}%', end='', flush=True)
    print('\nBattery fully charged!')


def get_month_abbreviation(month_num: int) -> Optional[str]:
    """Get month abbreviation

    Args:
        month_num: Month number (1-12)

    Returns:
        Month abbreviation or None if invalid
    """
    if 1 <= month_num <= 12:
        months = 'JanFebMarAprMayJunJulAugSepOctNovDec'
        pos = (month_num - 1) * 3
        return months[pos:pos + 3]
    else:
        print("Invalid month number, must be 1-12")
        return None


# Password utilities
def generate_password_legacy(length: int = 16) -> str:
    """Generate random password (legacy method)

    Args:
        length: Password length

    Returns:
        Generated password
    """
    password = ''
    char_ranges = [[97, 122], [65, 90], [48, 57], [33, 47]]
    for _ in range(length):
        char_range = random.choice(char_ranges)
        password += chr(random.randint(*char_range))
    return password


def generate_password(length: int = 16) -> str:
    """Generate secure random password

    Args:
        length: Password length

    Returns:
        Generated password
    """
    # Character sets
    characters = string.ascii_letters + string.digits + string.punctuation

    # Ensure password has at least one of each character type
    password_chars = [
        random.choice(string.ascii_lowercase),
        random.choice(string.ascii_uppercase),
        random.choice(string.digits),
        random.choice(string.punctuation)
    ]

    # Fill remaining characters
    password_chars.extend(random.choice(characters) for _ in range(length - 4))

    # Shuffle characters
    random.shuffle(password_chars)

    return ''.join(password_chars)


def generate_verification_code(length: int = 4) -> str:
    """Generate verification code

    Args:
        length: Code length

    Returns:
        Generated verification code
    """
    characters = string.ascii_letters + string.digits
    return ''.join(random.choices(characters, k=length))


def display_verification_code_tkinter(code: str):
    """Display verification code in Tkinter window

    Args:
        code: Verification code to display
    """
    root = tk.Tk()
    root.title("Verification Code")

    # Set window size and position
    window_width = 200
    window_height = 100
    screen_width = root.winfo_screenwidth()
    screen_height = root.winfo_screenheight()
    x = (screen_width - window_width) // 2
    y = (screen_height - window_height) // 2
    root.geometry(f"{window_width}x{window_height}+{x}+{y}")

    # Create canvas
    canvas = tk.Canvas(root, width=window_width, height=window_height, bg='white')
    canvas.pack()

    # Set font
    custom_font = tkfont.Font(size=30, weight='bold')

    # Draw text
    canvas.create_text(window_width / 2, window_height / 2,
                       text=code,
                       font=custom_font)

    # Add noise lines
    for _ in range(5):
        x1 = random.randint(0, window_width)
        y1 = random.randint(0, window_height)
        x2 = random.randint(0, window_width)
        y2 = random.randint(0, window_height)
        canvas.create_line(x1, y1, x2, y2, fill='gray')

    root.mainloop()


def display_verification_code_pygame(code: str):
    """Display verification code in Pygame window

    Args:
        code: Verification code to display
    """
    pygame.init()

    # Setup window
    width, height = 200, 100
    screen = pygame.display.set_mode((width, height))
    pygame.display.set_caption("Verification Code")

    # Setup font
    font = pygame.font.Font(None, 60)

    # Main loop
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.locals.QUIT:
                running = False

        # White background
        screen.fill((255, 255, 255))

        # Render code
        text = font.render(code, True, (0, 0, 0))
        text_rect = text.get_rect(center=(width / 2, height / 2))

        # Add noise lines
        for _ in range(5):
            start_pos = (random.randint(0, width), random.randint(0, height))
            end_pos = (random.randint(0, width), random.randint(0, height))
            pygame.draw.line(screen, (128, 128, 128), start_pos, end_pos)

        screen.blit(text, text_rect)
        pygame.display.flip()

    pygame.quit()



class AdvancedPasswordEncoder:
    """增强型密码加密工具，确保加密解密完全可逆"""
    # 定义所有支持的字符分类
    ALNUM_CHARS = set(string.ascii_letters + string.digits)
    SPECIAL_CHARS = {'_', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', '+', '='}

    # 简化的字符映射表（确保严格可逆）
    BASE_MAP = {
        # 小写字母映射
        'a': 'q', 'b': 'w', 'c': 'e', 'd': 'r', 'e': 't', 'f': 'y', 'g': 'u', 'h': 'i',
        'i': 'o', 'j': 'p', 'k': 'a', 'l': 's', 'm': 'd', 'n': 'f', 'o': 'g', 'p': 'h',
        'q': 'j', 'r': 'k', 's': 'l', 't': 'z', 'u': 'x', 'v': 'c', 'w': 'v', 'x': 'b',
        'y': 'n', 'z': 'm',
        # 数字映射
        '0': '5', '1': '6', '2': '7', '3': '8', '4': '9', '5': '0', '6': '1',
        '7': '2', '8': '3', '9': '4',
        # 大写字母映射
        'A': 'Q', 'B': 'W', 'C': 'E', 'D': 'R', 'E': 'T', 'F': 'Y', 'G': 'U', 'H': 'I',
        'I': 'O', 'J': 'P', 'K': 'A', 'L': 'S', 'M': 'D', 'N': 'F', 'O': 'G', 'P': 'H',
        'Q': 'J', 'R': 'K', 'S': 'L', 'T': 'Z', 'U': 'X', 'V': 'C', 'W': 'V', 'X': 'B',
        'Y': 'N', 'Z': 'M',
        # 特殊字符映射
        '_': '!', '!': '@', '@': '#', '#': '$', '$': '%', '%': '^', '^': '&',
        '&': '*', '*': '(', '(': ')', ')': '-', '-': '+', '+': '=', '=': '_'
    }

    def __init_subclass__(cls):
        # 验证映射表的双向唯一性
        values = list(cls.BASE_MAP.values())
        if len(values) != len(set(values)):
            raise ValueError("BASE_MAP values must be unique for proper decryption")
        if set(cls.BASE_MAP.keys()) != set(values):
            raise ValueError("BASE_MAP must be closed - all values must be in keys")

    def __init__(self, password: str, rounds: int = 3):
        """初始化密码编码器"""
        if not isinstance(password, str):
            raise TypeError("Password must be a string")

        # 检查密码中是否包含不支持的字符
        supported_chars = set(self.BASE_MAP.keys())
        for char in password:
            if char not in supported_chars:
                raise ValueError(f"Password contains unsupported character: '{char}'")

        if len(password) >= 200:
            raise ValueError("Password must be less than 200 characters")
        if not isinstance(rounds, int) or rounds < 1:
            raise ValueError("Rounds must be integer >= 1")

        self.password = password
        self.rounds = rounds
        self.encrypted = None
        # 创建反向映射表
        self.reverse_map = {v: k for k, v in self.BASE_MAP.items()}

    def _transform(self, text: str, mapping: dict) -> str:
        """执行字符替换转换"""
        return ''.join(mapping[char] for char in text)

    def _get_rotation_map(self, rotation: int) -> dict:
        """生成指定轮次的映射表"""
        keys = list(self.BASE_MAP.keys())
        # 根据轮次计算偏移量
        offset = rotation % len(keys)
        # 创建旋转后的映射表
        rotated_map = {}
        for i, key in enumerate(keys):
            rotated_key = keys[(i + offset) % len(keys)]
            rotated_map[key] = self.BASE_MAP[rotated_key]
        return rotated_map

    def _get_reverse_rotation_map(self, rotation: int) -> dict:
        """生成解密用的反向旋转映射表"""
        rotation_map = self._get_rotation_map(rotation)
        return {v: k for k, v in rotation_map.items()}

    def encrypt(self) -> str:
        """执行加密"""
        current = self.password

        # 多轮加密
        for i in range(self.rounds):
            rotation_map = self._get_rotation_map(i)
            current = self._transform(current, rotation_map)

        self.encrypted = current
        return self.encrypted

    def decrypt(self) -> str:
        """执行解密"""
        if not self.encrypted:
            raise ValueError("No encrypted data, call encrypt() first")

        current = self.encrypted

        # 反向多轮解密
        for i in range(self.rounds - 1, -1, -1):
            reverse_map = self._get_reverse_rotation_map(i)
            current = self._transform(current, reverse_map)

        return current


if __name__ == "__main__":
    # 测试密码加密解密
    test_password = "YL_top01"
    encoder = AdvancedPasswordEncoder(test_password, rounds=1)

    encrypted = encoder.encrypt()
    print(f"Encrypted: {encrypted}")

    decrypted = encoder.decrypt()
    print(f"Decrypted: {decrypted}")
    print(f"Decryption successful: {decrypted == test_password}")  # 应该显示True

    # 验证相同密码相同轮次加密结果一致
    encoder2 = AdvancedPasswordEncoder("SamePass", rounds=5)
    enc1 = encoder2.encrypt()

    encoder3 = AdvancedPasswordEncoder("SamePass", rounds=5)
    enc2 = encoder3.encrypt()

    print(f"Same encryption result: {enc1 == enc2}")  # 应该显示True

