from datetime import datetime, timedelta, timezone
from unittest.mock import MagicMock, patch

import pytest

from app.core.exceptions import RateLimitError, UnauthorizedError
from app.services.auth_service import AuthService
from app.utils.rate_limit import InMemoryRateLimiter


def test_rate_limiter_blocks_excess_requests():
    limiter = InMemoryRateLimiter()
    for _ in range(3):
        limiter.check("127.0.0.1:/auth/login", limit=3, window_seconds=60)
    with pytest.raises(RateLimitError):
        limiter.check("127.0.0.1:/auth/login", limit=3, window_seconds=60)


def test_rate_limiter_isolated_by_key():
    limiter = InMemoryRateLimiter()
    limiter.check("a", limit=1, window_seconds=60)
    with pytest.raises(RateLimitError):
        limiter.check("a", limit=1, window_seconds=60)
    limiter.check("b", limit=1, window_seconds=60)


def test_account_lockout_after_max_attempts():
    db = MagicMock()
    user = MagicMock()
    user.is_active = True
    user.failed_login_attempts = 0
    user.locked_until = None
    user.role.value = "admin"
    user.agency_id = 1
    user.id = 1
    user.hashed_password = "hash"

    service = AuthService(db)
    service.settings.auth_lockout_max_attempts = 3
    service.settings.auth_lockout_minutes = 15
    service.users.get_by_email = MagicMock(return_value=user)
    service.users.get_by_id = MagicMock(return_value=user)

    with patch("app.services.auth_service.verify_password", return_value=False):
        for _ in range(3):
            with pytest.raises(UnauthorizedError):
                service.login("user@test.com", "wrong")
        assert user.locked_until is not None
        with pytest.raises(UnauthorizedError, match="locked"):
            service.login("user@test.com", "wrong")


def test_successful_login_resets_lockout_counters():
    db = MagicMock()
    user = MagicMock()
    user.is_active = True
    user.failed_login_attempts = 2
    user.locked_until = None
    user.role.value = "agent"
    user.agency_id = 1
    user.id = 2
    user.hashed_password = "hash"

    service = AuthService(db)
    service.users.get_by_email = MagicMock(return_value=user)

    with patch("app.services.auth_service.verify_password", return_value=True):
        tokens = service.login("user@test.com", "correct")
        assert "access_token" in tokens
        assert user.failed_login_attempts == 0
        assert user.locked_until is None
        db.commit.assert_called()


def test_expired_lock_allows_login():
    db = MagicMock()
    user = MagicMock()
    user.is_active = True
    user.failed_login_attempts = 0
    user.locked_until = datetime.now(timezone.utc) - timedelta(minutes=1)
    user.role.value = "agent"
    user.agency_id = 1
    user.id = 3
    user.hashed_password = "hash"

    service = AuthService(db)
    service.users.get_by_email = MagicMock(return_value=user)

    with patch("app.services.auth_service.verify_password", return_value=True):
        tokens = service.login("user@test.com", "correct")
        assert "access_token" in tokens
        assert user.locked_until is None
