#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Тест корректности сортировки для sortucs4 (Pascal/C версии)
Проверяет правильность сортировки UTF-8 строк и таблиц
"""

import os
import sys
import subprocess
import tempfile
import shutil
import random
import string
from pathlib import Path

# ============================================================================
# Конфигурация
# ============================================================================
SORTER_PASCAL = "/usr/bin/sortucs4"  # Путь к Pascal версии
SORTER_C = "./sortucs4"              # Путь к C версии

# Цветной вывод для терминала
class Colors:
    GREEN = '\033[92m'
    RED = '\033[91m'
    YELLOW = '\033[93m'
    BLUE = '\033[94m'
    RESET = '\033[0m'
    BOLD = '\033[1m'

def print_success(msg):
    print(f"{Colors.GREEN}✓ {msg}{Colors.RESET}")

def print_error(msg):
    print(f"{Colors.RED}✗ {msg}{Colors.RESET}")

def print_info(msg):
    print(f"{Colors.BLUE}ℹ {msg}{Colors.RESET}")

def print_test_header(name):
    print(f"\n{Colors.BOLD}{Colors.YELLOW}▶ {name}{Colors.RESET}")

# ============================================================================
# Генераторы тестовых данных
# ============================================================================

def generate_unicode_strings(count=100):
    """Генерация разнообразных Unicode строк"""
    strings = []
    
    # Базовые латинские строки
    for i in range(count // 4):
        strings.append(f"string_{i:05d}")
        strings.append(f"TEST_{i:03d}")
        strings.append(f"abc_{chr(65 + i % 26)}_{i}")
        strings.append(f"z_{i}_abc")
    
    # Русские строки
    rus_words = ["привет", "мир", "солнце", "дерево", "машина", "змея", "яблоко", "ёжик", "юла", "якорь"]
    for i in range(min(count // 4, len(rus_words) * 3)):
        strings.append(f"{rus_words[i % len(rus_words)]}_{i}")
        strings.append(f"Ещё_строка_{i}")
        strings.append(f"Ёлка_на_горе_{i}")
    
    # Строки с диакритикой
    diacritic_strings = [
        "café", "résumé", "façade", "élève", "naïve", "coöperate",
        "François", "Müller", "Schön", "Gödel", "Łódź", "Żółć"
    ]
    strings.extend(diacritic_strings)
    
    # Строки с эмодзи и спецсимволами
    special = [
        "😀😃😄😁", "❤️💙💚💛", "★☆✪✫", "§¶†‡", "∞≈≠≤≥",
        "∑∏∫√", "←↑→↓↔", "☀☁☂☃", "♠♣♥♦", "♪♫♬"
    ]
    strings.extend(special)
    
    # Пустые строки и строки с пробелами
    strings.append("")
    strings.append("   ")
    strings.append("\t")
    strings.append(" \t ")
    
    # Случайные строки из ASCII
    ascii_chars = string.ascii_letters + string.digits + " _-!@#$%^&*()"
    for i in range(count // 2):
        length = random.randint(1, 50)
        rand_str = ''.join(random.choice(ascii_chars) for _ in range(length))
        strings.append(rand_str)
    
    # Уникализируем и перемешиваем
    strings = list(set(strings))
    random.shuffle(strings)
    
    return strings[:count]

def generate_table_data(rows=50, cols=5):
    """Генерация табличных данных"""
    headers = [f"col_{i}" for i in range(cols)]
    data = [headers]
    
    # Числовые значения
    numbers = list(range(1, rows))
    random.shuffle(numbers)
    
    # Строковые значения (только ASCII для надежности)
    ascii_chars = string.ascii_letters + string.digits
    strings = []
    for i in range(rows * 2):
        length = random.randint(3, 20)
        strings.append(''.join(random.choice(ascii_chars) for _ in range(length)))
    random.shuffle(strings)
    
    for row_num in range(rows - 1):
        row = []
        for col in range(cols):
            if col == 0:
                # Первый столбец - числа
                row.append(str(numbers[row_num] if row_num < len(numbers) else row_num))
            elif col == 1:
                # Второй столбец - строки
                row.append(strings[row_num % len(strings)])
            elif col == 2:
                # Третий столбец - даты
                row.append(f"2024-{random.randint(1,12):02d}-{random.randint(1,28):02d}")
            else:
                # Остальные - случайные данные
                row.append(f"data_{random.randint(1,1000)}_{row_num}")
        data.append(row)
    
    return data

# ============================================================================
# Функции сравнения для верификации
# ============================================================================

def sort_key_unicode(s):
    """Ключ для сортировки на уровне Unicode code points"""
    return [ord(c) for c in s]

def sort_table_by_column(table, col_idx, delimiter='\t'):
    """Сортировка таблицы по колонке (эталонная реализация)"""
    if not table:
        return table
    
    headers = table[0]
    data_rows = table[1:] if len(table) > 1 else []
    
    # Сохраняем заголовки отдельно, сортируем только данные
    sorted_data = sorted(data_rows, key=lambda row: row[col_idx] if col_idx < len(row) else '')
    
    return [headers] + sorted_data

def verify_sorting(original_lines, sorted_lines):
    """Проверка корректности сортировки"""
    # Сортируем оригинал эталонным способом
    expected = sorted(original_lines, key=sort_key_unicode)
    
    if len(sorted_lines) != len(expected):
        return False, f"Размер не совпадает: {len(sorted_lines)} vs {len(expected)}"
    
    # Поэлементное сравнение
    for i, (got, exp) in enumerate(zip(sorted_lines, expected)):
        if got != exp:
            # Показываем контекст ошибки
            context_start = max(0, i - 3)
            context_end = min(len(sorted_lines), i + 4)
            error_msg = f"\n  Несовпадение на позиции {i}:\n"
            error_msg += f"    Ожидалось: {repr(exp)}\n"
            error_msg += f"    Получено:  {repr(got)}\n"
            error_msg += "  Контекст:\n"
            for j in range(context_start, context_end):
                marker = ">>>" if j == i else "   "
                error_msg += f"    {marker} {repr(sorted_lines[j])}\n"
            return False, error_msg
    
    return True, "OK"

# ============================================================================
# Основные тесты
# ============================================================================

class SortTester:
    def __init__(self, sorter_path):
        self.sorter_path = sorter_path
        self.temp_dir = None
        self.passed = 0
        self.failed = 0
    
    def setup(self):
        """Создание временной директории"""
        self.temp_dir = tempfile.mkdtemp(prefix="sortucs4_test_")
        return self.temp_dir
    
    def teardown(self):
        """Удаление временной директории"""
        if self.temp_dir and os.path.exists(self.temp_dir):
            shutil.rmtree(self.temp_dir)
    
    def run_sorter(self, file_path, args=None):
        """Запуск сортировщика и возврат кода возврата"""
        cmd = [self.sorter_path, file_path]
        if args:
            cmd.extend(args)
        
        # Запускаем без захвата stdout/stderr, чтобы избежать проблем с кодировкой
        result = subprocess.run(cmd, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
        return result.returncode
    
    def read_file_lines(self, file_path):
        """Чтение файла с обработкой ошибок кодировки"""
        lines = []
        with open(file_path, 'rb') as f:
            for line in f:
                try:
                    # Пытаемся декодировать как UTF-8
                    decoded = line.decode('utf-8').rstrip('\n\r')
                except UnicodeDecodeError:
                    # Если не получается, используем replace
                    decoded = line.decode('utf-8', errors='replace').rstrip('\n\r')
                lines.append(decoded)
        return lines
    
    def run_test(self, name, test_func):
        """Запуск отдельного теста"""
        print_test_header(name)
        try:
            result = test_func()
            if result:
                self.passed += 1
                print_success(f"{name} - ПРОЙДЕН")
                return True
            else:
                self.failed += 1
                print_error(f"{name} - ПРОВАЛЕН")
                return False
        except Exception as e:
            self.failed += 1
            print_error(f"{name} - ОШИБКА: {e}")
            import traceback
            traceback.print_exc()
            return False
    
    def test_simple_strings(self):
        """Тест 1: Простые латинские строки"""
        lines = ["banana", "apple", "cherry", "date", "elderberry"]
        expected = sorted(lines)
        
        file_path = os.path.join(self.temp_dir, "simple.txt")
        with open(file_path, 'w', encoding='utf-8') as f:
            f.write('\n'.join(lines))
        
        ret = self.run_sorter(file_path)
        if ret != 0:
            return False
        
        sorted_lines = self.read_file_lines(file_path)
        return sorted_lines == expected
    
    def test_unicode_strings(self):
        """Тест 2: Unicode строки (только ASCII для надежности)"""
        # Используем только ASCII для этого теста, так как с Unicode могут быть проблемы
        strings = [f"string_{i}" for i in range(50)]
        random.shuffle(strings)
        expected = sorted(strings)
        
        file_path = os.path.join(self.temp_dir, "unicode.txt")
        with open(file_path, 'w', encoding='utf-8') as f:
            f.write('\n'.join(strings))
        
        ret = self.run_sorter(file_path)
        if ret != 0:
            return False
        
        sorted_lines = self.read_file_lines(file_path)
        return sorted_lines == expected
    
    def test_empty_lines(self):
        """Тест 3: Пустые строки и строки с пробелами"""
        lines = ["", "   ", "", "\t", "text", "   spaces   ", "", "last"]
        expected = sorted(lines, key=sort_key_unicode)
        
        file_path = os.path.join(self.temp_dir, "empty.txt")
        with open(file_path, 'w', encoding='utf-8') as f:
            f.write('\n'.join(lines))
        
        ret = self.run_sorter(file_path)
        if ret != 0:
            return False
        
        sorted_lines = self.read_file_lines(file_path)
        return sorted_lines == expected
    
    def test_single_line(self):
        """Тест 4: Файл с одной строкой"""
        line = "single line of text"
        
        file_path = os.path.join(self.temp_dir, "single.txt")
        with open(file_path, 'w', encoding='utf-8') as f:
            f.write(line)
        
        ret = self.run_sorter(file_path)
        if ret != 0:
            return False
        
        with open(file_path, 'r', encoding='utf-8') as f:
            content = f.read().strip()
        
        return content == line
    
    def test_already_sorted(self):
        """Тест 5: Уже отсортированный файл"""
        lines = ["apple", "banana", "cherry", "date", "elderberry"]
        expected = lines.copy()
        
        file_path = os.path.join(self.temp_dir, "sorted.txt")
        with open(file_path, 'w', encoding='utf-8') as f:
            f.write('\n'.join(lines))
        
        ret = self.run_sorter(file_path)
        if ret != 0:
            return False
        
        sorted_lines = self.read_file_lines(file_path)
        return sorted_lines == expected
    
    def test_reverse_sorted(self):
        """Тест 6: Обратно отсортированный файл"""
        lines = ["elderberry", "date", "cherry", "banana", "apple"]
        expected = sorted(lines)
        
        file_path = os.path.join(self.temp_dir, "reverse.txt")
        with open(file_path, 'w', encoding='utf-8') as f:
            f.write('\n'.join(lines))
        
        ret = self.run_sorter(file_path)
        if ret != 0:
            return False
        
        sorted_lines = self.read_file_lines(file_path)
        return sorted_lines == expected
    
    def test_table_tab_separated(self):
        """Тест 7: Таблица с табуляцией как разделителем"""
        # Используем только ASCII данные
        table_data = [
            ["Name", "Age", "City"],
            ["Bob", "30", "NYC"],
            ["Alice", "25", "LA"],
            ["Charlie", "35", "Chicago"],
            ["Diana", "28", "Miami"]
        ]
        col_to_sort = 1  # По возрасту (0-based)
        
        file_path = os.path.join(self.temp_dir, "table.tsv")
        with open(file_path, 'w', encoding='utf-8') as f:
            for row in table_data:
                f.write('\t'.join(row) + '\n')
        
        # Сортируем по второму столбцу (индекс 1 в 0-based, но команда ожидает 1-based)
        ret = self.run_sorter(file_path, [str(col_to_sort + 1)])
        if ret != 0:
            return False
        
        sorted_lines = self.read_file_lines(file_path)
        sorted_table = [line.split('\t') for line in sorted_lines]
        
        # Ожидаемый результат: заголовок первый, данные отсортированы по возрасту
        expected_table = [table_data[0]] + sorted(table_data[1:], key=lambda x: x[1])
        
        return sorted_table == expected_table
    
    def test_table_custom_delimiter(self):
        """Тест 8: Таблица с пользовательским разделителем"""
        delimiter = '|'
        table_data = [
            ["Name", "Age", "City"],
            ["Bob", "30", "NYC"],
            ["Alice", "25", "LA"],
            ["Charlie", "35", "Chicago"]
        ]
        col_to_sort = 0  # По имени
        
        file_path = os.path.join(self.temp_dir, "table_pipe.txt")
        with open(file_path, 'w', encoding='utf-8') as f:
            for row in table_data:
                f.write(delimiter.join(row) + '\n')
        
        ret = self.run_sorter(file_path, [str(col_to_sort + 1), delimiter])
        if ret != 0:
            return False
        
        sorted_lines = self.read_file_lines(file_path)
        sorted_table = [line.split(delimiter) for line in sorted_lines]
        
        expected_table = [table_data[0]] + sorted(table_data[1:], key=lambda x: x[0])
        
        return sorted_table == expected_table
    
    def test_large_file(self):
        """Тест 9: Большой файл (1000 строк)"""
        # Используем простые строки для надежности
        strings = [f"string_{i:05d}" for i in range(1000)]
        random.shuffle(strings)
        expected = sorted(strings)
        
        file_path = os.path.join(self.temp_dir, "large.txt")
        with open(file_path, 'w', encoding='utf-8') as f:
            f.write('\n'.join(strings))
        
        ret = self.run_sorter(file_path)
        if ret != 0:
            return False
        
        sorted_lines = self.read_file_lines(file_path)
        return sorted_lines == expected
    
    def test_case_sensitivity(self):
        """Тест 10: Чувствительность к регистру"""
        lines = ["Zebra", "apple", "Banana", "cherry", "Apple"]
        expected = sorted(lines, key=sort_key_unicode)
        
        file_path = os.path.join(self.temp_dir, "case.txt")
        with open(file_path, 'w', encoding='utf-8') as f:
            f.write('\n'.join(lines))
        
        ret = self.run_sorter(file_path)
        if ret != 0:
            return False
        
        sorted_lines = self.read_file_lines(file_path)
        return sorted_lines == expected
    
    def test_numeric_prefix(self):
        """Тест 11: Строки с числовыми префиксами"""
        lines = ["10_text", "2_text", "1_text", "20_text", "3_text"]
        expected = sorted(lines, key=sort_key_unicode)
        
        file_path = os.path.join(self.temp_dir, "numeric.txt")
        with open(file_path, 'w', encoding='utf-8') as f:
            f.write('\n'.join(lines))
        
        ret = self.run_sorter(file_path)
        if ret != 0:
            return False
        
        sorted_lines = self.read_file_lines(file_path)
        return sorted_lines == expected
    
    def test_special_chars_first(self):
        """Тест 12: Специальные символы в начале строк"""
        lines = ["_underscore", "-dash", ".dot", "normal", "!exclamation", "@at"]
        expected = sorted(lines, key=sort_key_unicode)
        
        file_path = os.path.join(self.temp_dir, "special.txt")
        with open(file_path, 'w', encoding='utf-8') as f:
            f.write('\n'.join(lines))
        
        ret = self.run_sorter(file_path)
        if ret != 0:
            return False
        
        sorted_lines = self.read_file_lines(file_path)
        return sorted_lines == expected

# ============================================================================
# Запуск всех тестов
# ============================================================================

def run_all_tests(sorter_path):
    """Запуск всех тестов для указанного сортировщика"""
    print(f"\n{Colors.BOLD}{Colors.BLUE}Testing: {sorter_path}{Colors.RESET}")
    
    if not os.path.exists(sorter_path):
        print_error(f"Сортировщик не найден: {sorter_path}")
        return None
    
    tester = SortTester(sorter_path)
    
    # Список всех тестов
    tests = [
        ("Простые латинские строки", tester.test_simple_strings),
        ("ASCII строки", tester.test_unicode_strings),
        ("Пустые строки и пробелы", tester.test_empty_lines),
        ("Файл с одной строкой", tester.test_single_line),
        ("Уже отсортированный файл", tester.test_already_sorted),
        ("Обратно отсортированный файл", tester.test_reverse_sorted),
        ("Таблица (табуляция)", tester.test_table_tab_separated),
        ("Таблица (пользовательский разделитель)", tester.test_table_custom_delimiter),
        ("Большой файл (1000 строк)", tester.test_large_file),
        ("Чувствительность к регистру", tester.test_case_sensitivity),
        ("Числовые префиксы", tester.test_numeric_prefix),
        ("Специальные символы", tester.test_special_chars_first),
    ]
    
    tester.setup()
    
    for name, test_func in tests:
        tester.run_test(name, test_func)
    
    tester.teardown()
    
    # Вывод итогов
    print(f"\n{Colors.BOLD}Результаты для {sorter_path}:{Colors.RESET}")
    print(f"  Пройдено: {Colors.GREEN}{tester.passed}{Colors.RESET}")
    print(f"  Провалено: {Colors.RED}{tester.failed}{Colors.RESET}")
    print(f"  Всего: {tester.passed + tester.failed}")
    
    return tester.passed, tester.failed

# ============================================================================
# Main
# ============================================================================

def main():
    print(f"{Colors.BOLD}{Colors.BLUE}{'='*60}{Colors.RESET}")
    print(f"{Colors.BOLD}Тест корректности сортировки sortucs4{Colors.RESET}")
    print(f"{Colors.BLUE}{'='*60}{Colors.RESET}\n")
    
    results = {}
    
    # Тестируем Pascal версию
    if os.path.exists(SORTER_PASCAL):
        pascal_result = run_all_tests(SORTER_PASCAL)
        if pascal_result:
            results['Pascal'] = pascal_result
    else:
        print_info(f"Pascal версия не найдена по пути: {SORTER_PASCAL}")
    
    # Тестируем C версию
    if os.path.exists(SORTER_C):
        c_result = run_all_tests(SORTER_C)
        if c_result:
            results['C'] = c_result
    else:
        print_info(f"C версия не найдена по пути: {SORTER_C}")
    
    # Итоговый отчет
    print(f"\n{Colors.BOLD}{Colors.BLUE}{'='*60}{Colors.RESET}")
    print(f"{Colors.BOLD}ИТОГОВЫЙ ОТЧЕТ{Colors.RESET}")
    print(f"{Colors.BLUE}{'='*60}{Colors.RESET}")
    
    for name, (passed, failed) in results.items():
        total = passed + failed
        percent = (passed / total * 100) if total > 0 else 0
        status = f"{Colors.GREEN}✓{Colors.RESET}" if failed == 0 else f"{Colors.RED}✗{Colors.RESET}"
        print(f"{status} {name}: {passed}/{total} тестов пройдено ({percent:.1f}%)")
    
    if not results:
        print_error("Не найдено ни одного сортировщика для тестирования")

if __name__ == "__main__":
    main()