import os
import sqlite3
import sys
import threading
from pathlib import Path

sys.stdout.reconfigure(encoding='utf-8')
project_root = Path.cwd()
sys.path.insert(0, str(project_root))

from server.world.map_style_service import MapStyleService
import server.world.map_style_catalog as py_cat
from server.world.map_style_schema import init_db

print("=== STARTING ADVERSARIAL STRESS TESTS ===")

# Test 1: SQL Injection resistance in MapStyleService
print("\n[Stress Test 1] SQL Injection resistance:")
service = MapStyleService()
sqli_payloads = [
    "' OR '1'='1",
    "'; DROP TABLE map_styles; --",
    "STY_01_HOANG_MANG_CO_LO' UNION SELECT * FROM map_styles --",
    "' UNION SELECT 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 --"
]
for payload in sqli_payloads:
    res = service.get_style(payload)
    assert res is None, f"SQL injection payload {payload} did not return None: {res}"
    # Verify tables still exist
    assert len(service.list_styles()) == 30

print("-> SQL Injection resistance PASSED (parameterized queries strictly used).")

# Test 2: Boundary & invalid inputs
print("\n[Stress Test 2] Boundary & invalid inputs:")
invalid_codes = [-100, -1, 0, 31, 100, 255, 256, 999999]
for code in invalid_codes:
    res = service.get_style_by_code(code)
    assert res is None, f"Code {code} expected None, got {res}"

invalid_style_ids = ["", "   ", "UNKNOWN", "sty_01", None]
for sid in invalid_style_ids:
    if sid is not None:
        res = service.get_style(sid)
        assert res is None, f"Style ID {sid} expected None, got {res}"
        tiles = service.get_tiles_for_style(sid)
        assert tiles == [], f"Tiles for {sid} expected [], got {tiles}"
        props = service.get_props_for_style(sid)
        assert props == [], f"Props for {sid} expected [], got {props}"

print("-> Boundary & invalid inputs PASSED.")

# Test 3: Idempotent Seeding & Force Reseed
print("\n[Stress Test 3] Idempotent Seeding & Force Reseed:")
# In-memory service
mem_service = MapStyleService(db_path=":memory:")
initial_count = len(mem_service.list_styles())
assert initial_count == 30

# Calling seed_database again without force
cnt = mem_service.seed_database(force=False)
assert cnt == 30
assert len(mem_service.list_styles()) == 30

# Calling seed_database with force=True
cnt_forced = mem_service.seed_database(force=True)
assert cnt_forced == 30
assert len(mem_service.list_styles()) == 30

# Verify tile and prop counts after forced reseed
all_tiles = []
all_props = []
for s in mem_service.list_styles():
    all_tiles.extend(mem_service.get_tiles_for_style(s.style_id))
    all_props.extend(mem_service.get_props_for_style(s.style_id))

assert len(all_tiles) == 600, f"Expected 600 tiles after reseed, got {len(all_tiles)}"
assert len(all_props) == 90, f"Expected 90 props after reseed, got {len(all_props)}"
mem_service.close()
print("-> Idempotent Seeding & Force Reseed PASSED.")

# Test 4: Concurrency & Multi-threaded read stress
print("\n[Stress Test 4] Concurrency & Multi-threaded read stress:")
errors = []

def reader_worker(worker_id: int):
    try:
        worker_service = MapStyleService()
        for i in range(50):
            # Query random styles, codes, tiles, props
            code = (i % 30) + 1
            s = worker_service.get_style_by_code(code)
            assert s is not None and s.biome_code == code
            tiles = worker_service.get_tiles_for_style(s.style_id)
            assert len(tiles) == 20
            props = worker_service.get_props_for_style(s.style_id)
            assert len(props) >= 3
    except Exception as e:
        errors.append((worker_id, e))

threads = [threading.Thread(target=reader_worker, args=(t,)) for t in range(10)]
for t in threads:
    t.start()
for t in threads:
    t.join()

assert len(errors) == 0, f"Multi-threaded access encountered errors: {errors}"
print("-> 10 concurrent threads x 50 iterations (500 read batches) PASSED with 0 errors under WAL mode.")

# Test 5: Strict CHECK constraints adversarial attack
print("\n[Stress Test 5] Strict CHECK constraints adversarial attack:")
test_db = init_db(":memory:")
cur = test_db.cursor()

# 5.1 Biome code upper boundary (256 > 255)
try:
    cur.execute(
        "INSERT INTO map_styles (style_id, biome_code, name_vi, name_en, theme_category, "
        "theme_category_name, description, floor_color_hex, wall_color_hex, path_color_hex, "
        "liquid_color_hex, ambient_light_hex, asset_dir, monster_family) "
        "VALUES ('STY_FAIL_256', 256, 'V', 'E', 'HOANG_DA_DA_NGOAI', 'T', 'D', '#111', '#222', '#333', '#444', '#555', 'a', 'm')"
    )
    raise AssertionError("CHECK constraint on biome_code <= 255 failed to reject 256!")
except sqlite3.IntegrityError:
    pass

# 5.2 Invalid tile_type_id (20 > 19)
# First insert valid style
cur.execute(
    "INSERT INTO map_styles (style_id, biome_code, name_vi, name_en, theme_category, "
    "theme_category_name, description, floor_color_hex, wall_color_hex, path_color_hex, "
    "liquid_color_hex, ambient_light_hex, asset_dir, monster_family) "
    "VALUES ('STY_PARENT', 101, 'V', 'E', 'HOANG_DA_DA_NGOAI', 'T', 'D', '#111', '#222', '#333', '#444', '#555', 'a', 'm')"
)

try:
    cur.execute(
        "INSERT INTO map_style_tiles (style_id, tile_type_id, tile_type_name, texture_filename, "
        "normal_filename, base_color_fallback) VALUES ('STY_PARENT', 20, 'INVALID_TILE', 'f.png', 'n.png', '#111')"
    )
    raise AssertionError("CHECK constraint on tile_type_id <= 19 failed to reject 20!")
except sqlite3.IntegrityError:
    pass

# 5.3 Invalid spawn_weight (0.0 or 1.1)
try:
    cur.execute(
        "INSERT INTO map_style_props (prop_id, style_id, prop_name_vi, prop_name_en, sprite_key, "
        "sprite_path, spawn_weight, placement_rule) VALUES ('p_0', 'STY_PARENT', 'P', 'P', 'k', 'p.png', 0.0, 'ANY_FLOOR')"
    )
    raise AssertionError("CHECK constraint on spawn_weight > 0.0 failed to reject 0.0!")
except sqlite3.IntegrityError:
    pass

try:
    cur.execute(
        "INSERT INTO map_style_props (prop_id, style_id, prop_name_vi, prop_name_en, sprite_key, "
        "sprite_path, spawn_weight, placement_rule) VALUES ('p_1_1', 'STY_PARENT', 'P', 'P', 'k', 'p.png', 1.1, 'ANY_FLOOR')"
    )
    raise AssertionError("CHECK constraint on spawn_weight <= 1.0 failed to reject 1.1!")
except sqlite3.IntegrityError:
    pass

# 5.4 Invalid placement_rule
try:
    cur.execute(
        "INSERT INTO map_style_props (prop_id, style_id, prop_name_vi, prop_name_en, sprite_key, "
        "sprite_path, spawn_weight, placement_rule) VALUES ('p_rule', 'STY_PARENT', 'P', 'P', 'k', 'p.png', 0.5, 'INVALID_RULE')"
    )
    raise AssertionError("CHECK constraint on placement_rule failed to reject 'INVALID_RULE'!")
except sqlite3.IntegrityError:
    pass

# 5.5 Foreign key cascade verification
cur.execute(
    "INSERT INTO map_style_tiles (style_id, tile_type_id, tile_type_name, texture_filename, "
    "normal_filename, base_color_fallback) VALUES ('STY_PARENT', 1, 'FLOOR', 'f.png', 'n.png', '#111')"
)
cur.execute(
    "INSERT INTO map_style_props (prop_id, style_id, prop_name_vi, prop_name_en, sprite_key, "
    "sprite_path, spawn_weight, placement_rule) VALUES ('p_cascade', 'STY_PARENT', 'P', 'P', 'k', 'p.png', 0.5, 'ANY_FLOOR')"
)
test_db.commit()

# Delete parent style
cur.execute("DELETE FROM map_styles WHERE style_id = 'STY_PARENT'")
test_db.commit()

# Verify child records were automatically cascaded
cur.execute("SELECT count(*) FROM map_style_tiles WHERE style_id = 'STY_PARENT'")
assert cur.fetchone()[0] == 0, "Cascade delete failed for map_style_tiles!"
cur.execute("SELECT count(*) FROM map_style_props WHERE style_id = 'STY_PARENT'")
assert cur.fetchone()[0] == 0, "Cascade delete failed for map_style_props!"

test_db.close()
print("-> Strict CHECK constraints and CASCADE DELETE PASSED.")

print("\n=== ALL ADVERSARIAL STRESS TESTS PASSED SUCCESSFULLY! ===")
