"""Tests for async checkers (ASYNC001-ASYNC003)."""

import pytest
from pathlib import Path

from stablestack.checkers.async_checks import (
    MissingAwaitChecker,
    BlockingInAsyncChecker,
    FireAndForgetChecker,
)


class TestMissingAwaitChecker:
    """Tests for ASYNC001: missing-await (JS/TS)."""

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

    def test_no_flag_async_function_declaration(self, checker, temp_ts_file):
        """Should NOT flag async function declarations themselves."""
        code = '''
async function fetchUser(id: string) {
    const res = await fetch(`/api/users/${id}`);
    return res.json();
}

export async function getAll() {
    return await db.query("SELECT * FROM users");
}
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_flags_missing_await_at_call_site(self, checker, temp_ts_file):
        """Should flag async function called without await."""
        code = '''
async function fetchUser(id: string) {
    return await db.query("SELECT * FROM users WHERE id = $1", [id]);
}

async function main() {
    const user = fetchUser("123");
    console.log(user);
}
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1
        assert any("fetchUser" in f.message for f in findings)

    def test_no_flag_awaited_call(self, checker, temp_ts_file):
        """Should NOT flag properly awaited calls."""
        code = '''
async function fetchUser(id: string) {
    return await db.query("SELECT * FROM users WHERE id = $1", [id]);
}

async function main() {
    const user = await fetchUser("123");
    console.log(user);
}
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_no_flag_arrow_function_declaration(self, checker, temp_ts_file):
        """Should NOT flag async arrow function declarations."""
        code = '''
const fetchUser = async (id: string) => {
    const res = await fetch(`/api/users/${id}`);
    return res.json();
};
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0


    def test_no_flag_class_method_declaration(self, checker, temp_ts_file):
        """Should NOT flag async class method declarations."""
        code = '''
class GmailHelper {
    async init() {
        this.gmail = await getGmailClient(this.refreshToken);
    }

    async searchEmails(options) {
        if (!this.gmail) {
            await this.init();
        }
        return await this.gmail.users.messages.list(options);
    }

    async deleteEmailsMatching(options) {
        const emails = await this.searchEmails(options);
        return emails.length;
    }
}
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        # Should not flag any declarations — only actual missing-await call sites
        assert len(findings) == 0

    def test_no_flag_access_modifier_async_method(self, checker, temp_ts_file):
        """Should NOT flag async methods with access modifiers (public/private/static)."""
        code = '''
class Service {
    public async fetchData() {
        return await db.query("SELECT 1");
    }

    private async init() {
        this.pool = await createPool();
    }

    static async create() {
        const svc = new Service();
        await svc.init();
        return svc;
    }
}
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_no_flag_nextauth_callback_methods(self, checker, temp_ts_file):
        """Should NOT flag async callback shorthand methods (NextAuth, etc)."""
        code = '''
const config = {
    callbacks: {
        async session({ session, user }) {
            if (session.user) {
                session.user.id = user.id;
            }
            return session;
        },
        async createUser({ user }) {
            if (user.email && user.id) {
                await db.update(user);
            }
        },
    },
};
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_still_flags_missing_await_in_class_method(self, checker, temp_ts_file):
        """Should still flag missing await for calls inside class methods."""
        code = '''
async function fetchUser(id) {
    return await db.query("SELECT * FROM users WHERE id = $1", [id]);
}

class Controller {
    async handle() {
        const user = fetchUser("123");
        console.log(user);
    }
}
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1
        assert any("fetchUser" in f.message for f in findings)


class TestBlockingInAsyncChecker:
    """Tests for ASYNC002: blocking-in-async."""

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

    def test_detects_time_sleep_in_async(self, checker, temp_py_file):
        """Should detect time.sleep() in async function."""
        code = '''
async def process():
    time.sleep(1)
    return "done"
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1
        assert any(f.rule_id == "ASYNC002" for f in findings)

    def test_detects_requests_in_async(self, checker, temp_py_file):
        """Should detect requests.get() in async function."""
        code = '''
async def fetch_data():
    response = requests.get(url)
    return response.json()
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_allows_asyncio_sleep(self, checker, temp_py_file):
        """Should not flag asyncio.sleep()."""
        code = '''
async def process():
    await asyncio.sleep(1)
    return "done"
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        # Should not flag asyncio.sleep
        assert not any(f.rule_id == "ASYNC002" and "asyncio.sleep" in f.code for f in findings)


class TestFireAndForgetChecker:
    """Tests for ASYNC003: fire-and-forget-task."""

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

    def test_detects_untracked_create_task(self, checker, temp_py_file):
        """Should detect asyncio.create_task() without tracking."""
        code = '''
async def start_background():
    asyncio.create_task(process_data())
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1
        assert any(f.rule_id == "ASYNC003" for f in findings)

    def test_allows_tracked_task(self, checker, temp_py_file):
        """Should not flag tasks that are assigned to a variable."""
        code = '''
async def start_background():
    task = asyncio.create_task(process_data())
    task.add_done_callback(handle_result)
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0
