"""
Adversarial Stress Harness for Milestone 1: Game Design Matrix & Core Contracts.
Author: Empirical Challenger 1
Role: critic, specialist
"""

from __future__ import annotations
import concurrent.futures
import os
import sqlite3
import pytest

from server.world.game_design_matrix_service import GameDesignMatrixService


@pytest.fixture
def matrix_service():
    """Provides an in-memory matrix service populated with canonical data."""
    service = GameDesignMatrixService(db_path=":memory:")
    service.seed_canonical_data(force=True)
    yield service
    service.close()


# ==============================================================================
# 1. SQLite Schema Constraints & Edge Cases
# ==============================================================================

def test_party_scaling_size_boundary_rejection(matrix_service: GameDesignMatrixService):
    """Verifies party_size outside [1, 6] is rejected by SQLite CHECK constraint."""
    with matrix_service._get_connection() as conn:
        cur = conn.cursor()
        # party_size = 0
        with pytest.raises(sqlite3.IntegrityError):
            cur.execute("INSERT INTO party_scaling_matrix VALUES (0, 1.0, 0, 0, 0, 0, 15.0, 'zero')")
        # party_size = 7
        with pytest.raises(sqlite3.IntegrityError):
            cur.execute("INSERT INTO party_scaling_matrix VALUES (7, 4.0, 180, 300, 300, 180, 15.0, 'seven')")
        # party_size = -1
        with pytest.raises(sqlite3.IntegrityError):
            cur.execute("INSERT INTO party_scaling_matrix VALUES (-1, 1.0, 0, 0, 0, 0, 15.0, 'neg')")


def test_party_scaling_negative_multipliers_rejection(matrix_service: GameDesignMatrixService):
    """Verifies hp_multiplier < 1.0 and negative bonuses are rejected by CHECK constraints."""
    with matrix_service._get_connection() as conn:
        cur = conn.cursor()
        # hp_multiplier = 0.5 (< 1.0)
        with pytest.raises(sqlite3.IntegrityError):
            cur.execute("UPDATE party_scaling_matrix SET hp_multiplier = 0.5 WHERE party_size = 1")
        # hp_multiplier = -1.0
        with pytest.raises(sqlite3.IntegrityError):
            cur.execute("UPDATE party_scaling_matrix SET hp_multiplier = -1.0 WHERE party_size = 1")
        # exp_bonus_pct = -10.0
        with pytest.raises(sqlite3.IntegrityError):
            cur.execute("UPDATE party_scaling_matrix SET exp_bonus_pct = -10.0 WHERE party_size = 2")
        # item_quantity_bonus_pct = -5.0
        with pytest.raises(sqlite3.IntegrityError):
            cur.execute("UPDATE party_scaling_matrix SET item_quantity_bonus_pct = -5.0 WHERE party_size = 2")
        # currency_quantity_bonus_pct = -5.0
        with pytest.raises(sqlite3.IntegrityError):
            cur.execute("UPDATE party_scaling_matrix SET currency_quantity_bonus_pct = -5.0 WHERE party_size = 2")
        # rarity_bonus_pct = -5.0
        with pytest.raises(sqlite3.IntegrityError):
            cur.execute("UPDATE party_scaling_matrix SET rarity_bonus_pct = -5.0 WHERE party_size = 2")
        # nearby_radius <= 0
        with pytest.raises(sqlite3.IntegrityError):
            cur.execute("UPDATE party_scaling_matrix SET nearby_radius = 0.0 WHERE party_size = 1")
        with pytest.raises(sqlite3.IntegrityError):
            cur.execute("UPDATE party_scaling_matrix SET nearby_radius = -15.0 WHERE party_size = 1")


def test_loot_allocation_modes_rejections(matrix_service: GameDesignMatrixService):
    """Verifies invalid loot allocation values are rejected."""
    with matrix_service._get_connection() as conn:
        cur = conn.cursor()
        # negative timeout
        with pytest.raises(sqlite3.IntegrityError):
            cur.execute("INSERT INTO loot_allocation_modes VALUES ('NEG_TIME', 'Neg', -1.0, 0, 'desc')")
        # invalid boolean flag for is_permanent
        with pytest.raises(sqlite3.IntegrityError):
            cur.execute("INSERT INTO loot_allocation_modes VALUES ('BAD_PERM', 'Bad', 0.0, 2, 'desc')")


def test_teleport_configs_rejections(matrix_service: GameDesignMatrixService):
    """Verifies invalid teleport configs are rejected by CHECK constraints."""
    with matrix_service._get_connection() as conn:
        cur = conn.cursor()
        # negative cast_time
        with pytest.raises(sqlite3.IntegrityError):
            cur.execute("INSERT INTO teleport_configs VALUES ('NEG_CAST', -3.5, 10.0, 1, 1, 'desc')")
        # negative cooldown
        with pytest.raises(sqlite3.IntegrityError):
            cur.execute("INSERT INTO teleport_configs VALUES ('NEG_CD', 3.5, -10.0, 1, 1, 'desc')")
        # invalid boolean for cancel_on_movement
        with pytest.raises(sqlite3.IntegrityError):
            cur.execute("INSERT INTO teleport_configs VALUES ('BAD_MOVE', 3.5, 10.0, 2, 1, 'desc')")
        # invalid boolean for cancel_on_damage
        with pytest.raises(sqlite3.IntegrityError):
            cur.execute("INSERT INTO teleport_configs VALUES ('BAD_DMG', 3.5, 10.0, 1, 2, 'desc')")


def test_portal_quarantine_rules_rejections(matrix_service: GameDesignMatrixService):
    """Verifies invalid quarantine rules are rejected by CHECK constraints."""
    with matrix_service._get_connection() as conn:
        cur = conn.cursor()
        # negative max_portals
        with pytest.raises(sqlite3.IntegrityError):
            cur.execute("INSERT INTO portal_quarantine_rules VALUES ('NEG_PORT', 'ZONE', 1, 1, -1, 'desc')")
        # invalid boolean for boss_lockout
        with pytest.raises(sqlite3.IntegrityError):
            cur.execute("INSERT INTO portal_quarantine_rules VALUES ('BAD_BOSS', 'ZONE', 2, 1, 6, 'desc')")
        # invalid boolean for secret_chamber_lockout
        with pytest.raises(sqlite3.IntegrityError):
            cur.execute("INSERT INTO portal_quarantine_rules VALUES ('BAD_SECRET', 'ZONE', 1, 2, 6, 'desc')")


# ==============================================================================
# 2. Adversarial Edge Cases: Schema Gaps (Weak or Missing Constraints)
# ==============================================================================

def test_loot_allocation_empty_mode_id_allowed_by_schema(matrix_service: GameDesignMatrixService):
    """Adversarial Finding: SQLite schema allows empty mode_id string because no length check exists."""
    with matrix_service._get_connection() as conn:
        cur = conn.cursor()
        cur.execute("INSERT INTO loot_allocation_modes VALUES ('', 'Empty Mode', 0.0, 0, 'Empty string key')")
    row = matrix_service.get_loot_allocation_mode("")
    assert row is not None
    assert row.mode_id == ""


def test_teleport_configs_arbitrary_action_type_allowed_by_schema(matrix_service: GameDesignMatrixService):
    """Adversarial Finding: action_type has no enum check, allowing arbitrary fake action types."""
    with matrix_service._get_connection() as conn:
        cur = conn.cursor()
        cur.execute("INSERT INTO teleport_configs VALUES ('EXPLOIT_TELEPORT', 3.5, 10.0, 1, 1, 'Fake target')")
    cfg = matrix_service.get_teleport_config("EXPLOIT_TELEPORT")
    assert cfg is not None
    assert cfg.action_type == "EXPLOIT_TELEPORT"


# ==============================================================================
# 3. Method Name and Contract Inspection
# ==============================================================================

def test_verify_matrix_integrity_method_presence(matrix_service: GameDesignMatrixService):
    """
    Checks whether verify_matrix_integrity() exists on GameDesignMatrixService.
    Worker's handoff and prompt specifically cited verify_matrix_integrity().
    """
    has_verify = hasattr(matrix_service, "verify_matrix_integrity")
    has_validate = hasattr(matrix_service, "validate_game_design_integrity")
    assert has_validate is True
    # If verify_matrix_integrity is expected as an alias or method, note its presence
    print(f"has verify_matrix_integrity: {has_verify}, has validate_game_design_integrity: {has_validate}")


# ==============================================================================
# 4. Integrity Validation Vulnerabilities: Currency & Rarity Monotonicity Gap
# ==============================================================================

def test_currency_and_rarity_non_monotonic_detection_gap(matrix_service: GameDesignMatrixService):
    """
    Adversarial Attack: Corrupt currency_quantity_bonus_pct and rarity_bonus_pct
    at party_size=6 to 0.0%.
    Does validate_game_design_integrity() catch this?
    """
    with matrix_service._get_connection() as conn:
        cur = conn.cursor()
        # Set party_size 6 currency and rarity to 0 (non-monotonic with size 5 which is 200% / 120%)
        cur.execute("""
            UPDATE party_scaling_matrix 
            SET currency_quantity_bonus_pct = 0.0, rarity_bonus_pct = 0.0 
            WHERE party_size = 6
        """)
    report = matrix_service.validate_game_design_integrity()
    # Check if this severe economic balance violation was caught:
    currency_caught = any("currency" in v.lower() for v in report.violations)
    rarity_caught = any("rarity" in v.lower() for v in report.violations)
    print(f"Currency violation caught: {currency_caught}, Rarity violation caught: {rarity_caught}")
    # Document empirical result:
    assert (currency_caught and rarity_caught) is True, "Service properly checks currency and rarity monotonicity!"


# ==============================================================================
# 5. Integrity Validation Vulnerabilities: Secret Chamber Quarantine Gap
# ==============================================================================

def test_secret_chamber_quarantine_bypass_detection_gap(matrix_service: GameDesignMatrixService):
    """
    Adversarial Attack: Alter SECRET_CHAMBER quarantine rule to allow infinite portals
    and remove secret_chamber_lockout.
    Worker handoff claimed: 'SECRET_CHAMBER has secret_chamber_lockout=True and max_portals == 0'.
    Does validate_game_design_integrity() actually check SECRET_CHAMBER?
    """
    with matrix_service._get_connection() as conn:
        cur = conn.cursor()
        cur.execute("""
            UPDATE portal_quarantine_rules 
            SET secret_chamber_lockout = 0, max_portals = 999 
            WHERE zone_type = 'SECRET_CHAMBER'
        """)
    report = matrix_service.validate_game_design_integrity()
    secret_caught = any("SECRET_CHAMBER" in v for v in report.violations)
    print(f"Secret chamber exploit caught: {secret_caught}")
    # Document empirical result:
    assert secret_caught is False, "Revealed gap: Service query only checks WHERE zone_type IN ('WILDERNESS', 'DUNGEON')!"


# ==============================================================================
# 6. Concurrency, Thread Safety & Database Locking
# ==============================================================================

def test_in_memory_service_thread_safety_failure():
    """
    Tests thread safety of GameDesignMatrixService(db_path=':memory:').
    Because sqlite3.connect default has check_same_thread=True,
    calling get_party_scaling across worker threads will raise ProgrammingError.
    """
    svc = GameDesignMatrixService(db_path=":memory:")
    svc.seed_canonical_data(force=True)

    errors = []

    def worker_read():
        try:
            return svc.get_party_scaling(1)
        except Exception as e:
            errors.append(e)
            return None

    with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
        futures = [executor.submit(worker_read) for _ in range(4)]
        for f in futures:
            f.result()

    svc.close()
    assert len(errors) > 0, "Expected ProgrammingError due to check_same_thread=True on :memory: connection"
    assert any(isinstance(e, sqlite3.ProgrammingError) for e in errors)


def test_file_database_concurrent_readers(tmp_path):
    """
    Tests concurrent readers on a file-based SQLite database with WAL mode.
    """
    db_file = str(tmp_path / "test_concurrent.db")
    svc = GameDesignMatrixService(db_path=db_file)
    svc.seed_canonical_data(force=True)

    results = []
    errors = []

    def reader(worker_id):
        try:
            reader_svc = GameDesignMatrixService(db_path=db_file)
            row = reader_svc.get_party_scaling(3)
            reader_svc.close()
            return row.party_size if row else None
        except Exception as e:
            errors.append((worker_id, e))
            return None

    with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
        futures = [executor.submit(reader, i) for i in range(20)]
        for f in futures:
            res = f.result()
            if res is not None:
                results.append(res)

    svc.close()
    assert len(errors) == 0, f"Concurrent reads had errors: {errors}"
    assert len(results) == 20
    assert all(r == 3 for r in results)


def test_file_database_concurrent_read_write(tmp_path):
    """
    Tests concurrent read and write operations under WAL mode on file database.
    Verifies database locking and contention behavior.
    """
    db_file = str(tmp_path / "test_read_write.db")
    svc = GameDesignMatrixService(db_path=db_file)
    svc.seed_canonical_data(force=True)

    read_count = 0
    write_count = 0
    lock_errors = []

    def writer_task(worker_id):
        nonlocal write_count
        try:
            w_svc = GameDesignMatrixService(db_path=db_file)
            with w_svc._get_connection() as conn:
                conn.cursor().execute("UPDATE party_scaling_matrix SET description = ? WHERE party_size = 1", (f"Worker {worker_id}",))
            w_svc.close()
            write_count += 1
        except Exception as e:
            lock_errors.append(("write", worker_id, e))

    def reader_task(worker_id):
        nonlocal read_count
        try:
            r_svc = GameDesignMatrixService(db_path=db_file)
            row = r_svc.get_party_scaling(1)
            r_svc.close()
            if row:
                read_count += 1
        except Exception as e:
            lock_errors.append(("read", worker_id, e))

    with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor:
        futures = []
        for i in range(15):
            futures.append(executor.submit(reader_task, i))
            futures.append(executor.submit(writer_task, i))
        for f in futures:
            f.result()

    svc.close()
    print(f"Concurrent RW: reads={read_count}, writes={write_count}, lock_errors={len(lock_errors)}")
    # WAL mode allows concurrent readers during writers; but concurrent writers might get busy if SQLite lock contention occurs
    assert read_count > 0
    assert write_count > 0
