import time
import random
import csv
import os

from phone_book import (
    ll_insert, ll_find, ll_delete, ll_list_all,
    ht_make, ht_insert, ht_find, ht_delete, ht_list_all,
    bst_insert, bst_find, bst_delete, bst_list_all,
)

N = 10_000
REPEATS = 5
SEARCH_COUNT = 110
DELETE_COUNT = 50
HT_SIZE = 256

RANDOM_SEED = 42
random.seed(RANDOM_SEED)

OUTPUT_DIR = os.path.dirname(__file__)
os.makedirs(OUTPUT_DIR, exist_ok=True)
CSV_PATH = os.path.join(OUTPUT_DIR, 'results.csv')

def generate_records(n):
    records = [(f"User_{i:05d}", f"+7{random.randint(1000000000, 9999999999)}")
               for i in range(n)]
    return records


records_base = generate_records(N)

records_shuffled = records_base[:]
random.shuffle(records_shuffled)

records_sorted = sorted(records_base, key=lambda x: x[0])

existing_names = [r[0] for r in random.sample(records_base, 100)]
missing_names = [f"None_{i}" for i in range(10)]
search_names = existing_names + missing_names

delete_names = [r[0] for r in random.sample(records_base, DELETE_COUNT)]

def measure(func, *args, **kwargs):
    start = time.perf_counter()
    result = func(*args, **kwargs)
    end = time.perf_counter()
    return end - start, result

def bench_linked_list(records, mode_label):
    times = {'insert': [], 'find': [], 'delete': []}

    for _ in range(REPEATS):
        head = None
        t_start = time.perf_counter()
        for name, phone in records:
            head = ll_insert(head, name, phone)
        times['insert'].append(time.perf_counter() - t_start)

        t_start = time.perf_counter()
        for name in search_names:
            ll_find(head, name)
        times['find'].append(time.perf_counter() - t_start)

        t_start = time.perf_counter()
        for name in delete_names:
            head = ll_delete(head, name)
        times['delete'].append(time.perf_counter() - t_start)

    return times


def bench_hash_table(records, mode_label):
    times = {'insert': [], 'find': [], 'delete': []}

    for _ in range(REPEATS):
        buckets = ht_make(HT_SIZE)
        t_start = time.perf_counter()
        for name, phone in records:
            ht_insert(buckets, name, phone)
        times['insert'].append(time.perf_counter() - t_start)

        t_start = time.perf_counter()
        for name in search_names:
            ht_find(buckets, name)
        times['find'].append(time.perf_counter() - t_start)

        t_start = time.perf_counter()
        for name in delete_names:
            ht_delete(buckets, name)
        times['delete'].append(time.perf_counter() - t_start)

    return times


def bench_bst(records, mode_label):
    times = {'insert': [], 'find': [], 'delete': []}

    for _ in range(REPEATS):
        root = None
        t_start = time.perf_counter()
        for name, phone in records:
            root = bst_insert(root, name, phone)
        times['insert'].append(time.perf_counter() - t_start)

        t_start = time.perf_counter()
        for name in search_names:
            bst_find(root, name)
        times['find'].append(time.perf_counter() - t_start)

        t_start = time.perf_counter()
        for name in delete_names:
            root = bst_delete(root, name)
        times['delete'].append(time.perf_counter() - t_start)

    return times

def avg(lst):
    return sum(lst) / len(lst)


def run_all():
    print(f"Запуск бенчмарков: N={N}, повторений={REPEATS}\n")
    print(f"{'Структура':<15} {'Режим':<12} {'Операция':<10} "
          f"{'Среднее (с)':<14} {'Все замеры'}")
    print("-" * 80)

    all_results = [["Структура", "Режим", "Операция", "Среднее (с)"] +
                   [f"Замер_{i+1}" for i in range(REPEATS)]]

    datasets = [
        (records_shuffled, "случайный"),
        (records_sorted,   "сортированный"),
    ]

    benchmarks = [
        ("LinkedList", bench_linked_list),
        ("HashTable",  bench_hash_table),
        ("BST",        bench_bst),
    ]

    for ds_records, ds_mode in datasets:
        for struct_name, bench_func in benchmarks:
            print(f"\n  [{struct_name}] режим: {ds_mode}")
            if struct_name == "BST" and ds_mode == "сортированный":
                import sys
                sys.setrecursionlimit(50_000)

            times = bench_func(ds_records, ds_mode)

            for op, op_times in times.items():
                mean = avg(op_times)
                row = [struct_name, ds_mode, op, f"{mean:.6f}"] + \
                      [f"{t:.6f}" for t in op_times]
                all_results.append(row)

                print(f"    {op:<10} среднее={mean:.6f}с  "
                      f"замеры={[f'{t:.4f}' for t in op_times]}")

    with open(CSV_PATH, 'w', newline='', encoding='utf-8') as f:
        writer = csv.writer(f)
        writer.writerows(all_results)

    print(f"\n✅ Результаты сохранены в: {CSV_PATH}")
    return all_results

def smoke_test():
    print("=== Smoke Test ===\n")

    test_data = [("Alice", "111"), ("Bob", "222"), ("Charlie", "333")]

    head = None
    for name, phone in test_data:
        head = ll_insert(head, name, phone)
    assert ll_find(head, "Alice") == "111"
    assert ll_find(head, "Bob") == "222"
    assert ll_find(head, "Nobody") is None
    head = ll_delete(head, "Bob")
    assert ll_find(head, "Bob") is None
    sorted_ll = ll_list_all(head)
    assert sorted_ll == [("Alice", "111"), ("Charlie", "333")]
    print("✅ LinkedList — OK")

    buckets = ht_make(16)
    for name, phone in test_data:
        ht_insert(buckets, name, phone)
    assert ht_find(buckets, "Charlie") == "333"
    assert ht_find(buckets, "Nobody") is None
    ht_delete(buckets, "Alice")
    assert ht_find(buckets, "Alice") is None
    sorted_ht = ht_list_all(buckets)
    assert sorted_ht == [("Bob", "222"), ("Charlie", "333")]
    print("✅ HashTable — OK")

    root = None
    for name, phone in test_data:
        root = bst_insert(root, name, phone)
    assert bst_find(root, "Alice") == "111"
    assert bst_find(root, "Nobody") is None
    root = bst_delete(root, "Alice")
    assert bst_find(root, "Alice") is None
    sorted_bst = bst_list_all(root)
    assert sorted_bst == [("Bob", "222"), ("Charlie", "333")]
    print("✅ BST — OK")

    print("\nВсе тесты пройдены!\n")

if __name__ == "__main__":
    smoke_test()
    results = run_all()
