"""
Test authentication routes including login, register, and logout.
"""

import os
import shutil
import tempfile
from pathlib import Path

import pytest

from local_deep_research.database.auth_db import (
    dispose_auth_engine,
    get_auth_db_session,
    init_auth_database,
)
from local_deep_research.database.encrypted_db import db_manager
from local_deep_research.database.models.auth import User
from local_deep_research.web.app_factory import create_app


@pytest.fixture
def temp_data_dir():
    """Create a temporary data directory for testing."""
    temp_dir = tempfile.mkdtemp()
    yield Path(temp_dir)
    shutil.rmtree(temp_dir, ignore_errors=True)


@pytest.fixture
def app(temp_data_dir, monkeypatch):
    """Create a Flask app configured for testing."""
    # Override data directory
    monkeypatch.setenv("LDR_DATA_DIR", str(temp_data_dir))

    # Disable rate limiting for tests
    monkeypatch.setenv("LDR_DISABLE_RATE_LIMITING", "true")

    # Use fast KDF iterations for tests (default 256000 is too slow in CI
    # after lru_cache removal from _get_key_from_password)
    monkeypatch.setenv("LDR_DB_CONFIG_KDF_ITERATIONS", "1000")

    # Clear database manager state
    db_manager.close_all_databases()

    # Reset db_manager's data directory to temp directory
    db_manager.data_dir = temp_data_dir / "encrypted_databases"
    db_manager.data_dir.mkdir(parents=True, exist_ok=True)

    # Create app with testing config
    app, _ = create_app()
    app.config["TESTING"] = True
    app.config["WTF_CSRF_ENABLED"] = False
    app.config["SESSION_COOKIE_SECURE"] = False  # For testing without HTTPS

    # Initialize auth database
    init_auth_database()

    # Clean up any existing test users
    auth_db = get_auth_db_session()
    auth_db.query(User).filter(User.username.like("testuser%")).delete()
    auth_db.commit()
    auth_db.close()

    yield app

    # Cleanup after test
    db_manager.close_all_databases()
    dispose_auth_engine()


@pytest.fixture
def client(app):
    """Create a test client."""
    return app.test_client()


class TestAuthRoutes:
    """Test authentication routes."""

    def test_root_redirects_to_login(self, client):
        """Test that unauthenticated users are redirected to login."""
        response = client.get("/")
        assert response.status_code == 302
        assert "/auth/login" in response.location

    def test_login_page_loads(self, client):
        """Test that login page loads successfully."""
        response = client.get("/auth/login")
        assert response.status_code == 200
        assert b"Local Deep Research" in response.data
        assert b"Your data is encrypted" in response.data

    def test_register_page_loads(self, client):
        """Test that register page loads successfully."""
        response = client.get("/auth/register")
        assert response.status_code == 200
        assert b"Create Account" in response.data
        assert b"NO way to recover your data" in response.data

    def test_successful_registration(self, client):
        """Test successful user registration."""
        response = client.post(
            "/auth/register",
            data={
                "username": "testuser",
                "password": "TestPass123",
                "confirm_password": "TestPass123",
                "acknowledge": "true",
            },
            follow_redirects=True,
        )

        assert response.status_code == 200

        # Check user was created in auth database
        auth_db = get_auth_db_session()
        user = auth_db.query(User).filter_by(username="testuser").first()
        assert user is not None
        assert user.username == "testuser"
        auth_db.close()

        # Check user database exists
        assert db_manager.user_exists("testuser")

    def test_registration_validation(self, client):
        """Test registration form validation."""
        # Test missing fields
        response = client.post("/auth/register", data={})
        assert response.status_code == 400
        assert b"Username is required" in response.data

        # Test username with invalid characters (special chars not allowed)
        response = client.post(
            "/auth/register",
            data={
                "username": "@invalid!user",
                "password": "TestPass123",
                "confirm_password": "TestPass123",
                "acknowledge": "true",
            },
        )
        assert response.status_code == 400
        assert b"Username can only contain" in response.data

        # Test password mismatch
        response = client.post(
            "/auth/register",
            data={
                "username": "testuser",
                "password": "password1",
                "confirm_password": "password2",
                "acknowledge": "true",
            },
        )
        assert response.status_code == 400
        assert b"Passwords do not match" in response.data

        # Test missing acknowledgment
        response = client.post(
            "/auth/register",
            data={
                "username": "testuser",
                "password": "TestPass123",
                "confirm_password": "TestPass123",
                "acknowledge": "false",
            },
        )
        assert response.status_code == 400
        assert b"You must acknowledge" in response.data

    def test_single_character_username(self, client):
        """Test that single character usernames are rejected (min 3 chars)."""
        response = client.post(
            "/auth/register",
            data={
                "username": "x",
                "password": "TestPass123",
                "confirm_password": "TestPass123",
                "acknowledge": "true",
            },
        )

        assert response.status_code == 400
        assert b"Username must be at least 3 characters" in response.data

    def test_empty_username_rejected(self, client):
        """Test that empty or whitespace-only usernames are rejected."""
        # Test empty username
        response = client.post(
            "/auth/register",
            data={
                "username": "",
                "password": "TestPass123",
                "confirm_password": "TestPass123",
                "acknowledge": "true",
            },
        )
        assert response.status_code == 400
        assert b"Username is required" in response.data

        # Test whitespace-only username
        response = client.post(
            "/auth/register",
            data={
                "username": "   ",
                "password": "TestPass123",
                "confirm_password": "TestPass123",
                "acknowledge": "true",
            },
        )
        assert response.status_code == 400
        # Whitespace gets trimmed, so it's treated as empty
        assert (
            b"Username is required" in response.data
            or b"Username can only contain" in response.data
        )

    def test_duplicate_username(self, client):
        """Test that duplicate usernames are rejected."""
        # Register first user
        client.post(
            "/auth/register",
            data={
                "username": "testuser",
                "password": "TestPass123",
                "confirm_password": "TestPass123",
                "acknowledge": "true",
            },
        )

        # Logout so we can try registering again
        client.post("/auth/logout")

        # Try to register same username
        response = client.post(
            "/auth/register",
            data={
                "username": "testuser",
                "password": "OtherPass123",
                "confirm_password": "OtherPass123",
                "acknowledge": "true",
            },
        )
        assert response.status_code == 400
        # Error message uses generic text to prevent account enumeration
        assert (
            b"Registration failed. Please try a different username"
            in response.data
        )

    def test_successful_login(self, client):
        """Test successful login."""
        # Register user first
        client.post(
            "/auth/register",
            data={
                "username": "testuser",
                "password": "TestPass123",
                "confirm_password": "TestPass123",
                "acknowledge": "true",
            },
        )

        # Logout
        client.post("/auth/logout")

        # Login
        response = client.post(
            "/auth/login",
            data={"username": "testuser", "password": "TestPass123"},
            follow_redirects=True,
        )

        assert response.status_code == 200

        # Check session
        with client.session_transaction() as sess:
            assert "username" in sess
            assert sess["username"] == "testuser"

    def test_invalid_login(self, client):
        """Test login with invalid credentials."""
        response = client.post(
            "/auth/login",
            data={"username": "nonexistent", "password": "wrongpassword"},
        )

        assert response.status_code == 401
        assert b"Invalid username or password" in response.data

    @pytest.mark.skipif(
        os.environ.get("CI") == "true"
        or os.environ.get("GITHUB_ACTIONS") == "true",
        reason="Encrypted DB registration can hit sqlcipher hmac errors in CI",
    )
    def test_logout(self, client):
        """Test logout functionality."""
        # Register and login
        reg_response = client.post(
            "/auth/register",
            data={
                "username": "testuser",
                "password": "TestPass123",
                "confirm_password": "TestPass123",
                "acknowledge": "true",
            },
            follow_redirects=False,
        )
        assert reg_response.status_code == 302, (
            f"Registration failed with status {reg_response.status_code}"
        )

        # Verify logged in
        with client.session_transaction() as sess:
            assert sess.get("username") == "testuser"

        # Logout
        response = client.post("/auth/logout", follow_redirects=False)
        assert response.status_code == 302
        assert "/auth/login" in response.location

        # Check session is cleared
        with client.session_transaction() as sess:
            assert "username" not in sess

    @pytest.mark.skipif(
        os.environ.get("CI") == "true"
        or os.environ.get("GITHUB_ACTIONS") == "true",
        reason="Password change with encrypted DB re-keying is complex to test in CI",
    )
    def test_change_password(self, client):
        """Test password change functionality."""
        # Register user
        client.post(
            "/auth/register",
            data={
                "username": "testuser",
                "password": "OldPass123",
                "confirm_password": "OldPass123",
                "acknowledge": "true",
            },
        )

        # Change password - don't follow redirects to check status
        response = client.post(
            "/auth/change-password",
            data={
                "current_password": "OldPass123",
                "new_password": "NewPass456",
                "confirm_password": "NewPass456",
            },
            follow_redirects=False,
        )

        # Should redirect to login after successful password change
        assert response.status_code == 302
        assert "/auth/login" in response.location

        # Now follow the redirect to login page
        response = client.get("/auth/login")
        assert response.status_code == 200

        # Try to login with new password
        response = client.post(
            "/auth/login",
            data={"username": "testuser", "password": "NewPass456"},
            follow_redirects=True,
        )

        assert response.status_code == 200

    def test_remember_me(self, client):
        """Test remember me functionality."""
        # Register user
        client.post(
            "/auth/register",
            data={
                "username": "testuser",
                "password": "TestPass123",
                "confirm_password": "TestPass123",
                "acknowledge": "true",
            },
        )

        # Logout
        client.post("/auth/logout")

        # Login with remember me
        client.post(
            "/auth/login",
            data={
                "username": "testuser",
                "password": "TestPass123",
                "remember": "true",
            },
        )

        with client.session_transaction() as sess:
            assert sess.permanent is True

    @pytest.mark.skipif(
        os.environ.get("CI") == "true"
        or os.environ.get("GITHUB_ACTIONS") == "true",
        reason="Encrypted DB registration can hit sqlcipher hmac errors in CI",
    )
    def test_auth_check_endpoint(self, client):
        """Test the authentication check endpoint."""
        # Not logged in
        response = client.get("/auth/check")
        assert response.status_code == 401
        data = response.get_json()
        assert data["authenticated"] is False

        # Register and check
        reg_response = client.post(
            "/auth/register",
            data={
                "username": "testuser",
                "password": "TestPass123",
                "confirm_password": "TestPass123",
                "acknowledge": "true",
            },
            follow_redirects=False,
        )
        assert reg_response.status_code == 302, (
            f"Registration failed with status {reg_response.status_code}"
        )

        response = client.get("/auth/check")
        assert response.status_code == 200
        data = response.get_json()
        assert data["authenticated"] is True
        assert data["username"] == "testuser"

    def test_blocked_registration_get(self, client, monkeypatch):
        """Test that GET register redirects when registrations are disabled."""

        def mock_load_config():
            return {"allow_registrations": False}

        monkeypatch.setattr(
            "local_deep_research.web.auth.routes.load_server_config",
            mock_load_config,
        )

        response = client.get("/auth/register", follow_redirects=False)
        assert response.status_code == 302
        assert "/auth/login" in response.location

    def test_blocked_registration_post(self, client, monkeypatch):
        """Test that POST register redirects and doesn't create user when disabled."""

        def mock_load_config():
            return {"allow_registrations": False}

        monkeypatch.setattr(
            "local_deep_research.web.auth.routes.load_server_config",
            mock_load_config,
        )

        response = client.post(
            "/auth/register",
            data={
                "username": "testuser_blocked",
                "password": "TestPass123",
                "confirm_password": "TestPass123",
                "acknowledge": "true",
            },
            follow_redirects=False,
        )

        assert response.status_code == 302
        assert "/auth/login" in response.location

        # Check no user was created
        auth_db = get_auth_db_session()
        user = (
            auth_db.query(User).filter_by(username="testuser_blocked").first()
        )
        assert user is None
        auth_db.close()
