import pytest
import shutil
import yaml
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock
from src.core.backup_database import BackupDatabase
from src.core.configuration import AppConfig

LOG_FILE = (Path(__file__).parent.parent / ".reaper_tests.log").resolve()

def _log(msg: str):
    """Write a message to the shared test log file."""
    with open(LOG_FILE, "a") as f:
        f.write(f"{msg}\n")

@pytest.fixture
def log():
    """Fixture to provide the logging function to tests."""
    return _log

def pytest_configure(config):
    """Register global warning filters and marks."""
    # Register marks if needed
    config.addinivalue_line("markers", "asyncio: mark test as asyncio")
    
    # Silence benign async mock and ResourceWarnings globally
    config.addinivalue_line("filterwarnings", "ignore::RuntimeWarning")
    config.addinivalue_line("filterwarnings", "ignore::ResourceWarning")

def pytest_sessionstart(session):
    """Clear the log file at the beginning of the test session."""
    with open(LOG_FILE, "w") as f:
        f.write("--- Reaper Test Session Started ---\n")

def pytest_report_header(config):
    """Print data source status to the console header."""
    test_data_dir = (Path(__file__).parent.parent / "ReaperFiles-AutoTest").resolve()
    if test_data_dir.exists():
        return f"[DATA_SOURCE] Automated Test Directory: FOUND ({test_data_dir.name})"
    else:
        return "[DATA_SOURCE] Automated Test Directory: NOT FOUND (Falling back to MOCKS)"

@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    """Hook to catch test results and log them to .reaper_tests.log."""
    outcome = yield
    rep = outcome.get_result()
    
    # We only log on the 'call' phase (the actual test run)
    if rep.when == "call":
        status = rep.outcome.upper()
        _log(f"RESULT: {item.nodeid} -> {status}")
        if rep.failed:
            _log(f"--- FAILURE DETAILS for {item.name} ---\n{rep.longreprtext}\n---------------------")
    elif not rep.passed:
        # Catch setup/teardown failures
        _log(f"ERROR in {item.nodeid} [{rep.when}]: {rep.outcome.upper()}")
        if rep.failed:
            _log(f"--- ERROR DETAILS ---\n{rep.longreprtext}\n---------------------")

def pytest_warning_recorded(warning_message, when, nodeid, location):
    """Hook to catch and log warnings to .reaper_tests.log."""
    msg = f"WARNING: {warning_message.message}"
    if nodeid:
        msg = f"WARNING in {nodeid} [{when}]: {warning_message.message}"
    _log(msg)

@pytest.fixture
def test_data_dir():
    return (Path(__file__).parent.parent / "ReaperFiles-AutoTest").resolve()

@pytest.fixture
def reaper_config(test_data_dir):
    config_path = test_data_dir / "reaper_config.yaml"
    if config_path.exists():
        with open(config_path, "r") as f:
            data = yaml.safe_load(f)
        return data
    # Fallback mock config
    return {
        "discord_bot_token": "mock_discord_token",
        "discord_server_id": "123456789012345678",
        "tool_mode": "backup_transfer",
        "target_platform": "fluxer",
        "fluxer_bot_token": "mock_fluxer_token",
        "fluxer_server_id": "987654321098765432",
        "stoat_bot_token": "mock_stoat_token",
        "stoat_server_id": "MOCK_STOAT_COMMUNITY",
        "anonymize_users": False,
        "log_level": "DEBUG"
    }

@pytest.fixture
def temp_db(test_data_dir, tmp_path, reaper_config):
    # If the real test data exists, use it
    if test_data_dir.exists():
        target_id = reaper_config.get("fluxer_server_id") if reaper_config.get("target_platform") == "fluxer" else reaper_config.get("stoat_server_id")
        db_files = list(test_data_dir.glob(f"*-{target_id}.db"))
        if not db_files:
            db_files = list(test_data_dir.glob("*.db"))
            
        if db_files:
            original_db = db_files[0]
            temp_db_path = tmp_path / "test_migration.db"
            print(f"[DATA_SOURCE] USE_SAMPLE_DB: {original_db.name}")
            _log(f"[DATA_SOURCE] USE_SAMPLE_DB: {original_db.name}")
            shutil.copy(original_db, temp_db_path)
            return temp_db_path

    # Fallback: Create an empty mock database with the required schema
    temp_db_path = tmp_path / "mock_migration.db"
    print("[DATA_SOURCE] USE_MOCK_DB: Fallback to empty schema")
    _log("[DATA_SOURCE] USE_MOCK_DB: Fallback to empty schema")
    db = BackupDatabase(temp_db_path)
    # The BackupDatabase constructor already initializes the schema
    return temp_db_path

@pytest.fixture
def backup_db(temp_db):
    db = BackupDatabase(temp_db)
    yield db
    # No explicit close needed for now as it's a persistent connection

@pytest.fixture
def backup_reader(test_data_dir, reaper_config, tmp_path):
    sid = reaper_config.get("discord_server_id")
    backup_path = test_data_dir / f"DISCORD_BACKUP-{sid}"
    
    if not test_data_dir.exists() or not backup_path.exists():
        # Fallback: create mock backup structure
        mock_path = tmp_path / f"DISCORD_BACKUP-{sid}"
        print(f"[DATA_SOURCE] USE_MOCK_BACKUP: {mock_path.name}")
        _log(f"[DATA_SOURCE] USE_MOCK_BACKUP: {mock_path.name}")
        mock_path.mkdir(parents=True, exist_ok=True)
        db_path = mock_path / "backup.db"
        db = BackupDatabase(db_path)
        # Populate with minimal mock data for BackupReader to work
        db._conn.execute("INSERT OR IGNORE INTO guild_profile (id, name) VALUES (?, ?)", (int(sid), "Mock Guild"))
        db._conn.execute("INSERT OR IGNORE INTO channels (id, name, type) VALUES (?, ?, ?)", (123, "mock-channel", 0))
        db._conn.commit()
        from src.core.backup_reader import BackupReader
        return BackupReader(mock_path)
        
    print(f"[DATA_SOURCE] USE_SAMPLE_BACKUP: {backup_path.name}")
    _log(f"[DATA_SOURCE] USE_SAMPLE_BACKUP: {backup_path.name}")
    from src.core.backup_reader import BackupReader
    return BackupReader(backup_path)

@pytest.fixture
def mock_discord_reader():
    reader = MagicMock()
    reader.guild = MagicMock()
    reader.fetch_message_history = AsyncMock()
    reader.download_attachment = AsyncMock(return_value=b"fake_data")
    reader.download_sticker = AsyncMock(return_value=b"fake_sticker_data")
    return reader

@pytest.fixture
def mock_fluxer_writer():
    writer = MagicMock()
    writer.send_message = AsyncMock(return_value="fluxer_msg_123")
    writer.send_marker = AsyncMock()
    return writer

@pytest.fixture
def mock_stoat_writer():
    writer = MagicMock()
    writer.send_message = AsyncMock(return_value="stoat_msg_123")
    writer.send_marker = AsyncMock()
    return writer
