"""Tests for session checkers (SESS001-SESS002)."""

import pytest
from pathlib import Path

from stablestack.checkers.session import ThreadSessionSafetyChecker
from stablestack.checkers.session.background_task_session import BackgroundTaskSessionChecker


class TestBackgroundTaskSessionChecker:
    """Tests for SESS001: background-task-session."""

    @pytest.fixture
    def checker(self):
        return BackgroundTaskSessionChecker()

    def test_detects_db_in_background_task(self, checker, temp_py_file):
        """Should detect database session passed to background task."""
        code = '''
@app.post("/process")
async def process(db: Session, background_tasks: BackgroundTasks):
    background_tasks.add_task(do_work, db)
    return {"status": "processing"}
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 1
        assert findings[0].rule_id == "SESS001"

    def test_detects_session_in_background_task(self, checker, temp_py_file):
        """Should detect session variable passed to background task."""
        code = '''
async def handler(session: AsyncSession, background_tasks: BackgroundTasks):
    background_tasks.add_task(process_data, session)
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 1

    def test_allows_no_session_in_task(self, checker, temp_py_file):
        """Should not flag background tasks without session."""
        code = '''
@app.post("/process")
async def process(background_tasks: BackgroundTasks):
    background_tasks.add_task(do_work, item_id)
    return {"status": "processing"}
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0


class TestThreadSessionSafetyChecker:
    """Tests for SESS002: thread-session-safety."""

    @pytest.fixture
    def checker(self):
        return ThreadSessionSafetyChecker()

    def test_detects_direct_session_commit(self, checker, temp_py_file):
        """Should flag submitted function that calls self.db_session.commit()."""
        code = '''
from concurrent.futures import ThreadPoolExecutor

class DataProcessor:
    def process(self):
        with ThreadPoolExecutor() as executor:
            executor.submit(self._worker, data)

    def _worker(self, data):
        result = fetch_api(data)
        self.db_session.commit()
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 1
        assert findings[0].rule_id == "SESS002"
        assert "db_session.commit" in findings[0].message

    def test_detects_direct_session_execute(self, checker, temp_py_file):
        """Should flag submitted function that calls session.execute()."""
        code = '''
from concurrent.futures import ThreadPoolExecutor

class Importer:
    def run(self):
        with ThreadPoolExecutor() as executor:
            executor.submit(self._import_chunk, chunk)

    def _import_chunk(self, chunk):
        for row in chunk:
            session.execute(insert_stmt)
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 1
        assert "session.execute" in findings[0].message

    def test_detects_indirect_callback(self, checker, temp_py_file):
        """Should flag submitted function calling _progress_callback without guard."""
        code = '''
from concurrent.futures import ThreadPoolExecutor

class Fetcher:
    def fetch_all(self):
        self._progress_callback = self._update_heartbeat
        with ThreadPoolExecutor() as executor:
            executor.submit(self._fetch_chunk, url)

    def _fetch_chunk(self, url):
        data = requests.get(url)
        self._progress_callback("done")
        return data
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 1
        assert "_progress_callback" in findings[0].message

    def test_allows_no_threadpool(self, checker, temp_py_file):
        """Should not flag code without ThreadPoolExecutor."""
        code = '''
class Processor:
    def process(self):
        self.db_session.commit()
        self._progress_callback("done")
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_allows_clean_worker(self, checker, temp_py_file):
        """Should not flag submitted function that doesn't touch session."""
        code = '''
from concurrent.futures import ThreadPoolExecutor

class Fetcher:
    def fetch_all(self):
        with ThreadPoolExecutor() as executor:
            executor.submit(self._fetch_url, url)

    def _fetch_url(self, url):
        return requests.get(url).json()
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_allows_guarded_callback(self, checker, temp_py_file):
        """Should not flag when callback is guarded (saved + replaced with log-only)."""
        code = '''
from concurrent.futures import ThreadPoolExecutor
import logging

class Fetcher:
    def fetch_all(self):
        saved_callback = self._progress_callback
        self._progress_callback = lambda msg: logging.info(msg)
        try:
            with ThreadPoolExecutor() as executor:
                executor.submit(self._fetch_chunk, url)
        finally:
            self._progress_callback = saved_callback

    def _fetch_chunk(self, url):
        data = requests.get(url)
        self._progress_callback("done")
        return data
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_allows_session_only_in_main_thread(self, checker, temp_py_file):
        """Should not flag session used only in main thread, not in submitted callable."""
        code = '''
from concurrent.futures import ThreadPoolExecutor, as_completed

class Processor:
    def process_all(self, items):
        with ThreadPoolExecutor() as executor:
            futures = [executor.submit(self._fetch_item, item) for item in items]
            for future in as_completed(futures):
                result = future.result()
                self.db_session.add(result)
            self.db_session.commit()

    def _fetch_item(self, item):
        return requests.get(item["url"]).json()
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0
