"""Tests for session management."""

import asyncio
from datetime import datetime, timedelta, timezone
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from app.session_manager import BrowserSession, SessionManager, utc_now


class TestBrowserSession:
    """Tests for the BrowserSession class."""

    def test_touch_updates_last_activity(self):
        """touch() should update last_activity timestamp."""
        mock_context = MagicMock()
        mock_page = MagicMock()

        session = BrowserSession(
            session_id="test-123",
            allowed_domains=["example.com"],
            context=mock_context,
            page=mock_page,
        )

        original_activity = session.last_activity
        session.touch()

        assert session.last_activity >= original_activity

    def test_increment_actions(self):
        """increment_actions() should update counter and touch."""
        mock_context = MagicMock()
        mock_page = MagicMock()

        session = BrowserSession(
            session_id="test-123",
            allowed_domains=["example.com"],
            context=mock_context,
            page=mock_page,
        )

        assert session.actions_used == 0
        session.increment_actions()
        assert session.actions_used == 1

    def test_actions_remaining(self):
        """actions_remaining should calculate correctly."""
        mock_context = MagicMock()
        mock_page = MagicMock()

        session = BrowserSession(
            session_id="test-123",
            allowed_domains=["example.com"],
            context=mock_context,
            page=mock_page,
        )

        initial_remaining = session.actions_remaining
        session.increment_actions()
        assert session.actions_remaining == initial_remaining - 1

    def test_is_expired_idle_timeout(self):
        """Session should expire after idle timeout."""
        mock_context = MagicMock()
        mock_page = MagicMock()

        session = BrowserSession(
            session_id="test-123",
            allowed_domains=["example.com"],
            context=mock_context,
            page=mock_page,
        )

        # Set last_activity to past
        session.last_activity = utc_now() - timedelta(seconds=400)
        assert session.is_expired

    def test_is_expired_max_lifetime(self):
        """Session should expire after max lifetime."""
        mock_context = MagicMock()
        mock_page = MagicMock()

        session = BrowserSession(
            session_id="test-123",
            allowed_domains=["example.com"],
            context=mock_context,
            page=mock_page,
        )

        # Set created_at to past
        session.created_at = utc_now() - timedelta(seconds=2000)
        session.last_activity = utc_now()  # Recent activity
        assert session.is_expired

    def test_not_expired_fresh_session(self):
        """Fresh session should not be expired."""
        mock_context = MagicMock()
        mock_page = MagicMock()

        session = BrowserSession(
            session_id="test-123",
            allowed_domains=["example.com"],
            context=mock_context,
            page=mock_page,
        )

        assert not session.is_expired


@pytest.mark.asyncio
class TestSessionManager:
    """Tests for the SessionManager class."""

    async def test_create_session_success(self):
        """create_session should create a new session."""
        mock_browser = AsyncMock()
        mock_context = AsyncMock()
        mock_page = AsyncMock()

        mock_browser.new_context.return_value = mock_context
        mock_context.new_page.return_value = mock_page

        manager = SessionManager(mock_browser)
        session = await manager.create_session(["example.com"])

        assert session is not None
        assert session.session_id is not None
        assert session.allowed_domains == ["example.com"]
        assert manager.active_session_count == 1

    async def test_create_session_empty_domains_raises(self):
        """create_session with empty domains should raise."""
        mock_browser = AsyncMock()
        manager = SessionManager(mock_browser)

        with pytest.raises(ValueError):
            await manager.create_session([])

    async def test_create_session_max_limit(self):
        """create_session should fail when max sessions reached."""
        mock_browser = AsyncMock()
        mock_context = AsyncMock()
        mock_page = AsyncMock()

        mock_browser.new_context.return_value = mock_context
        mock_context.new_page.return_value = mock_page

        manager = SessionManager(mock_browser)

        # Create max sessions
        with patch("app.session_manager.settings") as mock_settings:
            mock_settings.max_sessions = 2
            mock_settings.max_actions_per_session = 50
            mock_settings.idle_timeout_seconds = 300
            mock_settings.max_session_lifetime_seconds = 1800
            mock_settings.default_timeout_ms = 30000
            mock_settings.navigation_timeout_ms = 60000

            await manager.create_session(["example.com"])
            await manager.create_session(["other.com"])

            with pytest.raises(ValueError) as exc_info:
                await manager.create_session(["blocked.com"])

            assert "Maximum sessions" in str(exc_info.value)

    async def test_get_session_returns_session(self):
        """get_session should return existing session."""
        mock_browser = AsyncMock()
        mock_context = AsyncMock()
        mock_page = AsyncMock()

        mock_browser.new_context.return_value = mock_context
        mock_context.new_page.return_value = mock_page

        manager = SessionManager(mock_browser)
        session = await manager.create_session(["example.com"])

        retrieved = await manager.get_session(session.session_id)
        assert retrieved is session

    async def test_get_session_returns_none_for_unknown(self):
        """get_session should return None for unknown session."""
        mock_browser = AsyncMock()
        manager = SessionManager(mock_browser)

        retrieved = await manager.get_session("unknown-id")
        assert retrieved is None

    async def test_close_session_removes_session(self):
        """close_session should remove and close session."""
        mock_browser = AsyncMock()
        mock_context = AsyncMock()
        mock_page = AsyncMock()

        mock_browser.new_context.return_value = mock_context
        mock_context.new_page.return_value = mock_page

        manager = SessionManager(mock_browser)
        session = await manager.create_session(["example.com"])

        assert manager.active_session_count == 1

        result = await manager.close_session(session.session_id)

        assert result is True
        assert manager.active_session_count == 0
        mock_context.close.assert_called_once()

    async def test_close_session_idempotent(self):
        """close_session should be idempotent."""
        mock_browser = AsyncMock()
        mock_context = AsyncMock()
        mock_page = AsyncMock()

        mock_browser.new_context.return_value = mock_context
        mock_context.new_page.return_value = mock_page

        manager = SessionManager(mock_browser)
        session = await manager.create_session(["example.com"])

        await manager.close_session(session.session_id)
        result = await manager.close_session(session.session_id)

        assert result is False  # Already closed

    async def test_close_all_sessions(self):
        """close_all_sessions should close all sessions."""
        mock_browser = AsyncMock()
        mock_context = AsyncMock()
        mock_page = AsyncMock()

        mock_browser.new_context.return_value = mock_context
        mock_context.new_page.return_value = mock_page

        manager = SessionManager(mock_browser)

        await manager.create_session(["example.com"])
        await manager.create_session(["other.com"])

        assert manager.active_session_count == 2

        await manager.close_all_sessions()

        assert manager.active_session_count == 0
