"""
Тесты POST /api/upload — загрузка файлов в сессию.

Запуск:
    python3 tests/test_upload.py
"""

import io
import os
import sys
import time
import json
import gc

# Настраиваем пути
_site = os.path.join(os.path.dirname(__file__), "..", "site")
sys.path.insert(0, _site)
sys.path.insert(0, os.path.dirname(_site))  # project root for drhider

from flask import Flask
from routes.api_bp import api_bp


# ═══════════════════════════════════════════════════════════════════════════
# Helpers
# ═══════════════════════════════════════════════════════════════════════════

def _new_client():
    """Создать свежий тестовый клиент."""
    app = Flask(__name__)
    app.register_blueprint(api_bp)
    return app.test_client()


def _get_rss():
    """RSS процесса в байтах (Linux)."""
    try:
        with open("/proc/self/statm", "r") as f:
            parts = f.read().split()
            return int(parts[1]) * 4096
    except Exception:
        return 0


# ═══════════════════════════════════════════════════════════════════════════
# Базовые тесты
# ═══════════════════════════════════════════════════════════════════════════

def test_no_file():
    """POST без файла → 400."""
    c = _new_client()
    rv = c.post("/api/upload")
    assert rv.status_code == 400
    data = json.loads(rv.data)
    assert data["ok"] is False
    assert "No file" in data["error"]


def test_no_filename():
    """POST с файлом без имени → 400."""
    c = _new_client()
    data = {"files": (io.BytesIO(b"hello"), "")}
    rv = c.post("/api/upload", data=data, content_type="multipart/form-data")
    assert rv.status_code == 400
    resp = json.loads(rv.data)
    assert resp["ok"] is False
    assert "filename" in resp["error"].lower()


def test_single_file():
    """POST с одним файлом → 200, сессия + count=1."""
    c = _new_client()
    data = {"files": (io.BytesIO(b"hello world"), "test.txt")}
    rv = c.post("/api/upload", data=data, content_type="multipart/form-data")
    assert rv.status_code == 200
    resp = json.loads(rv.data)
    assert resp["ok"] is True
    assert len(resp["session"]) == 12
    assert resp["count"] == 1


def test_two_files_same_session():
    """Два файла в одну сессию → count=2."""
    c = _new_client()
    data1 = {"files": (io.BytesIO(b"first"), "file1.txt")}
    rv1 = c.post("/api/upload", data=data1, content_type="multipart/form-data")
    assert rv1.status_code == 200
    sid = json.loads(rv1.data)["session"]

    data2 = {
        "files": (io.BytesIO(b"second"), "file2.txt"),
        "session": sid,
    }
    rv2 = c.post("/api/upload", data=data2, content_type="multipart/form-data")
    assert rv2.status_code == 200
    resp2 = json.loads(rv2.data)
    assert resp2["session"] == sid
    assert resp2["count"] == 2


def test_fake_session_404():
    """Несуществующая сессия → 404."""
    c = _new_client()
    data = {
        "files": (io.BytesIO(b"data"), "f.txt"),
        "session": "deadbeef1234",
    }
    rv = c.post("/api/upload", data=data, content_type="multipart/form-data")
    assert rv.status_code == 404
    resp = json.loads(rv.data)
    assert resp["ok"] is False


def test_empty_file():
    """Пустой файл (0 байт) → 200."""
    c = _new_client()
    data = {"files": (io.BytesIO(b""), "empty.txt")}
    rv = c.post("/api/upload", data=data, content_type="multipart/form-data")
    assert rv.status_code == 200
    resp = json.loads(rv.data)
    assert resp["ok"] is True
    assert resp["count"] == 1


def test_cyrillic_filename():
    """Кириллическое имя файла → 200."""
    c = _new_client()
    content = "кириллица".encode("utf-8")
    data = {"files": (io.BytesIO(content), "договор_№123.txt")}
    rv = c.post("/api/upload", data=data, content_type="multipart/form-data")
    assert rv.status_code == 200
    resp = json.loads(rv.data)
    assert resp["ok"] is True
    assert resp["count"] == 1


def test_content_type_json():
    """Ответ — application/json."""
    c = _new_client()
    data = {"files": (io.BytesIO(b"x"), "f.txt")}
    rv = c.post("/api/upload", data=data, content_type="multipart/form-data")
    assert rv.status_code == 200
    assert "application/json" in rv.content_type


# ═══════════════════════════════════════════════════════════════════════════
# Большие файлы
# ═══════════════════════════════════════════════════════════════════════════

def test_large_10mb():
    """Файл 10 MB — загружается без ошибок."""
    c = _new_client()
    size = 10 * 1024 * 1024
    big_data = b"A" * size
    data = {"files": (io.BytesIO(big_data), "big_10mb.bin")}
    rv = c.post("/api/upload", data=data, content_type="multipart/form-data")
    assert rv.status_code == 200
    resp = json.loads(rv.data)
    assert resp["ok"] is True
    assert resp["count"] == 1


def test_large_50mb():
    """Файл 50 MB — загружается без ошибок."""
    c = _new_client()
    size = 50 * 1024 * 1024
    big_data = b"B" * size
    data = {"files": (io.BytesIO(big_data), "big_50mb.bin")}
    rv = c.post("/api/upload", data=data, content_type="multipart/form-data")
    assert rv.status_code == 200
    resp = json.loads(rv.data)
    assert resp["ok"] is True
    assert resp["count"] == 1


# ═══════════════════════════════════════════════════════════════════════════
# Много мелких файлов
# ═══════════════════════════════════════════════════════════════════════════

def test_many_small_files():
    """100 файлов по 1 KB в одну сессию."""
    c = _new_client()
    data1 = {"files": (io.BytesIO(b"x" * 1024), "file_000.txt")}
    rv1 = c.post("/api/upload", data=data1, content_type="multipart/form-data")
    sid = json.loads(rv1.data)["session"]

    for i in range(1, 100):
        data = {
            "files": (io.BytesIO(b"y" * 1024), f"file_{i:03d}.txt"),
            "session": sid,
        }
        rv = c.post("/api/upload", data=data, content_type="multipart/form-data")
        assert rv.status_code == 200
        resp = json.loads(rv.data)
        assert resp["session"] == sid
        assert resp["count"] == i + 1

    assert resp["count"] == 100


# ═══════════════════════════════════════════════════════════════════════════
# Утечка памяти
# ═══════════════════════════════════════════════════════════════════════════

def test_memory_after_large_files():
    """После загрузки больших файлов память не течёт."""
    c = _new_client()
    gc.collect()
    mem_before = _get_rss()

    for i in range(10):
        data = {"files": (io.BytesIO(b"M" * (1024 * 1024)), f"memtest_{i}.bin")}
        rv = c.post("/api/upload", data=data, content_type="multipart/form-data")
        assert rv.status_code == 200

    gc.collect()
    mem_after = _get_rss()
    diff_mb = (mem_after - mem_before) / (1024 * 1024)
    assert diff_mb < 50, f"Memory grew {diff_mb:.1f} MB, expected < 50 MB"


# ═══════════════════════════════════════════════════════════════════════════
# Бенчмарк скорости
# ═══════════════════════════════════════════════════════════════════════════

def test_upload_speed():
    """1 MB файл — загрузка < 500 ms."""
    c = _new_client()
    data = {"files": (io.BytesIO(b"S" * (1024 * 1024)), "speed.bin")}

    start = time.time()
    rv = c.post("/api/upload", data=data, content_type="multipart/form-data")
    elapsed = time.time() - start

    assert rv.status_code == 200
    assert elapsed < 0.5, f"Upload took {elapsed:.3f}s, expected < 0.5s"


# ═══════════════════════════════════════════════════════════════════════════
# Основной блок
# ═══════════════════════════════════════════════════════════════════════════

if __name__ == "__main__":
    passed = 0
    failed = 0
    tests = [
        test_no_file,
        test_no_filename,
        test_single_file,
        test_two_files_same_session,
        test_fake_session_404,
        test_empty_file,
        test_cyrillic_filename,
        test_content_type_json,
        test_large_10mb,
        test_large_50mb,
        test_many_small_files,
        test_memory_after_large_files,
        test_upload_speed,
    ]
    for test in tests:
        try:
            test()
            print(f"  ✅ {test.__name__}")
            passed += 1
        except Exception as e:
            print(f"  ❌ {test.__name__}: {e}")
            failed += 1
    print(f"\n{'='*50}")
    print(f"Результат: {passed} OK, {failed} FAIL из {len(tests)}")
    if failed == 0:
        print("✅ ВСЕ ТЕСТЫ ПРОЙДЕНЫ")
    else:
        print("❌ ЕСТЬ ПРОВАЛЫ")
        sys.exit(1)
