"""Tests for settings routes."""

from __future__ import annotations

from unittest.mock import AsyncMock, MagicMock, patch

import pytest


@pytest.fixture
async def client(route_client):
    """Use shared route_client fixture."""
    return route_client


@pytest.fixture
async def db(base_app):
    """Get db from base_app."""
    _, db, _ = base_app
    return db


@pytest.mark.asyncio
async def test_settings_page_renders(client):
    """Test settings page renders."""
    with patch(
        "src.web.routes.settings.AgentProviderService.load_provider_configs",
        AsyncMock(return_value=[]),
    ), patch(
        "src.web.routes.settings.AgentProviderService.load_model_cache",
        AsyncMock(return_value={}),
    ):
        resp = await client.get("/settings/")
        assert resp.status_code == 200


@pytest.mark.asyncio
async def test_settings_shows_accounts(client):
    """Test settings page shows accounts."""
    with patch(
        "src.web.routes.settings.AgentProviderService.load_provider_configs",
        AsyncMock(return_value=[]),
    ), patch(
        "src.web.routes.settings.AgentProviderService.load_model_cache",
        AsyncMock(return_value={}),
    ):
        resp = await client.get("/settings/")
        assert resp.status_code == 200
        assert "+1234567890" in resp.text


@pytest.mark.asyncio
async def test_settings_msg_param(client):
    """Test settings page with message param."""
    with patch(
        "src.web.routes.settings.AgentProviderService.load_provider_configs",
        AsyncMock(return_value=[]),
    ), patch(
        "src.web.routes.settings.AgentProviderService.load_model_cache",
        AsyncMock(return_value={}),
    ):
        resp = await client.get("/settings/?msg=credentials_saved")
        assert resp.status_code == 200


@pytest.mark.asyncio
async def test_save_scheduler(client, db):
    """Test save scheduler settings."""
    resp = await client.post(
        "/settings/save-scheduler",
        data={"collect_interval_minutes": "30"},
        follow_redirects=False,
    )
    assert resp.status_code == 303
    assert "msg=scheduler_saved" in resp.headers["location"]


@pytest.mark.asyncio
async def test_save_scheduler_invalid(client):
    """Test save scheduler with invalid value."""
    resp = await client.post(
        "/settings/save-scheduler",
        data={"collect_interval_minutes": "abc"},
        follow_redirects=False,
    )
    assert resp.status_code == 303
    assert "error=invalid_value" in resp.headers["location"]


@pytest.mark.asyncio
async def test_save_credentials_valid(client, db):
    """Test save credentials with valid values."""
    resp = await client.post(
        "/settings/save-credentials",
        data={"api_id": "99999", "api_hash": "testhash123456"},
        follow_redirects=False,
    )
    assert resp.status_code == 303
    assert "msg=credentials_saved" in resp.headers["location"]


@pytest.mark.asyncio
async def test_save_credentials_invalid_id(client):
    """Test save credentials with invalid api_id."""
    resp = await client.post(
        "/settings/save-credentials",
        data={"api_id": "not_a_number", "api_hash": "testhash"},
        follow_redirects=False,
    )
    assert resp.status_code == 303
    assert "error=invalid_api_id" in resp.headers["location"]


@pytest.mark.asyncio
async def test_toggle_account(client):
    """Test toggle account."""
    with patch("src.web.routes.settings.deps.account_service") as mock_svc:
        mock_svc.return_value.toggle = AsyncMock()
        resp = await client.post("/settings/1/toggle", follow_redirects=False)
        assert resp.status_code == 303
        assert "msg=account_toggled" in resp.headers["location"]


@pytest.mark.asyncio
async def test_delete_account(client):
    """Test delete account."""
    with patch("src.web.routes.settings.deps.account_service") as mock_svc:
        mock_svc.return_value.delete = AsyncMock()
        resp = await client.post("/settings/1/delete", follow_redirects=False)
        assert resp.status_code == 303
        assert "msg=account_deleted" in resp.headers["location"]


@pytest.mark.asyncio
async def test_save_filters(client, db):
    """Test save filter settings."""
    resp = await client.post(
        "/settings/save-filters",
        data={
            "min_subscribers_filter": "100",
            "auto_delete_filtered": "1",
            "auto_delete_on_collect": "0",
        },
        follow_redirects=False,
    )
    assert resp.status_code == 303
    assert "msg=filters_saved" in resp.headers["location"]


@pytest.mark.asyncio
async def test_save_filters_invalid(client):
    """Test save filters with invalid value."""
    resp = await client.post(
        "/settings/save-filters",
        data={"min_subscribers_filter": "not_a_number"},
        follow_redirects=False,
    )
    assert resp.status_code == 303
    assert "error=invalid_value" in resp.headers["location"]


@pytest.mark.asyncio
async def test_save_semantic_search(client, db):
    """Test save semantic search settings."""
    resp = await client.post(
        "/settings/save-semantic-search",
        data={
            "semantic_embeddings_provider": "openai",
            "semantic_embeddings_model": "text-embedding-3-small",
            "semantic_embeddings_batch_size": "100",
        },
        follow_redirects=False,
    )
    assert resp.status_code == 303
    assert "msg=semantic_saved" in resp.headers["location"]


@pytest.mark.asyncio
async def test_save_agent_backend(client, db):
    """Test save agent backend settings."""
    resp = await client.post(
        "/settings/save-agent",
        data={
            "agent_form_scope": "backend_override",
            "agent_backend_override": "deepagents",
        },
        follow_redirects=False,
    )
    assert resp.status_code == 303
    assert "msg=agent_saved" in resp.headers["location"]


@pytest.mark.asyncio
async def test_save_notification_account(client, db):
    """Test save notification account."""
    with patch(
        "src.web.routes.settings.deps.get_notification_target_service"
    ) as mock_svc, patch(
        "src.web.routes.settings.deps.get_notifier"
    ) as mock_notifier:
        mock_svc.return_value.set_configured_phone = AsyncMock()
        mock_notifier.return_value = None
        resp = await client.post(
            "/settings/save-notification-account",
            data={"notification_account_phone": "+1234567890"},
            follow_redirects=False,
        )
        assert resp.status_code == 303
        assert "msg=notification_account_saved" in resp.headers["location"]


@pytest.mark.asyncio
async def test_save_notification_account_invalid(client, db):
    """Test save notification account with invalid phone."""
    resp = await client.post(
        "/settings/save-notification-account",
        data={"notification_account_phone": "+9999999999"},
        follow_redirects=False,
    )
    assert resp.status_code == 303
    assert "error=notification_account_invalid" in resp.headers["location"]


# === Semantic Search Settings tests ===


@pytest.mark.asyncio
async def test_save_semantic_search_invalid_batch_size(client, db):
    """Test save semantic search with invalid batch size."""
    resp = await client.post(
        "/settings/save-semantic-search",
        data={
            "semantic_embeddings_provider": "openai",
            "semantic_embeddings_model": "text-embedding-3-small",
            "semantic_embeddings_batch_size": "not_a_number",
        },
        follow_redirects=False,
    )
    assert resp.status_code == 303
    assert "error=semantic_invalid_value" in resp.headers["location"]


@pytest.mark.asyncio
async def test_save_semantic_search_empty_provider(client, db):
    """Test save semantic search with empty provider."""
    resp = await client.post(
        "/settings/save-semantic-search",
        data={
            "semantic_embeddings_provider": "",
            "semantic_embeddings_model": "text-embedding-3-small",
            "semantic_embeddings_batch_size": "100",
        },
        follow_redirects=False,
    )
    assert resp.status_code == 303
    assert "error=semantic_invalid_value" in resp.headers["location"]


@pytest.mark.asyncio
async def test_save_semantic_search_empty_model(client, db):
    """Test save semantic search with empty model."""
    resp = await client.post(
        "/settings/save-semantic-search",
        data={
            "semantic_embeddings_provider": "openai",
            "semantic_embeddings_model": "",
            "semantic_embeddings_batch_size": "100",
        },
        follow_redirects=False,
    )
    assert resp.status_code == 303
    assert "error=semantic_invalid_value" in resp.headers["location"]


@pytest.mark.asyncio
async def test_save_semantic_search_reset_index(client, db):
    """Test save semantic search with reset index flag."""
    # First set some initial values
    await db.set_setting("semantic_embeddings_provider", "openai")
    await db.set_setting("semantic_embeddings_model", "text-embedding-3-small")

    resp = await client.post(
        "/settings/save-semantic-search",
        data={
            "semantic_embeddings_provider": "cohere",
            "semantic_embeddings_model": "embed-english-v3.0",
            "semantic_embeddings_batch_size": "100",
            "semantic_reset_index": "1",
        },
        follow_redirects=False,
    )
    assert resp.status_code == 303
    assert "msg=semantic_saved" in resp.headers["location"]


@pytest.mark.asyncio
async def test_run_semantic_index(client, db):
    """Test running semantic index."""
    with patch(
        "src.web.routes.settings.EmbeddingService.index_pending_messages",
        AsyncMock(return_value=5),
    ):
        resp = await client.post(
            "/settings/semantic-index",
            data={},
            follow_redirects=False,
        )
        assert resp.status_code == 303
        assert "msg=semantic_indexed" in resp.headers["location"]
        assert "indexed=5" in resp.headers["location"]


@pytest.mark.asyncio
async def test_run_semantic_index_with_reset(client, db):
    """Test running semantic index with reset."""
    with patch(
        "src.web.routes.settings.EmbeddingService.index_pending_messages",
        AsyncMock(return_value=3),
    ):
        resp = await client.post(
            "/settings/semantic-index",
            data={"semantic_reset_index": "1"},
            follow_redirects=False,
        )
        assert resp.status_code == 303
        assert "msg=semantic_indexed" in resp.headers["location"]


# === Agent Settings tests ===


@pytest.mark.asyncio
async def test_save_agent_prompt_template_invalid(client, db):
    """Test save agent with invalid prompt template."""
    await db.set_setting("agent_dev_mode_enabled", "1")

    resp = await client.post(
        "/settings/save-agent",
        data={
            "agent_form_scope": "prompt_template",
            "agent_prompt_template": "Invalid {unknown_var}",
        },
        follow_redirects=False,
    )
    assert resp.status_code == 303
    assert "error=agent_prompt_template_invalid" in resp.headers["location"]


@pytest.mark.asyncio
async def test_save_agent_prompt_template_valid(client, db):
    """Test save agent with valid prompt template."""
    await db.set_setting("agent_dev_mode_enabled", "1")

    resp = await client.post(
        "/settings/save-agent",
        data={
            "agent_form_scope": "prompt_template",
            "agent_prompt_template": "Summarize {source_messages} for {channel_title}",
        },
        follow_redirects=False,
    )
    assert resp.status_code == 303
    assert "msg=agent_saved" in resp.headers["location"]


@pytest.mark.asyncio
async def test_save_agent_dev_mode_with_disclaimer(client, db):
    """Test enabling dev mode with disclaimer."""
    resp = await client.post(
        "/settings/save-agent",
        data={
            "agent_form_scope": "dev_mode",
            "agent_dev_mode_enabled": "1",
            "agent_dev_mode_disclaimer": "1",
        },
        follow_redirects=False,
    )
    assert resp.status_code == 303
    assert "msg=agent_saved" in resp.headers["location"]

    # Verify dev mode was enabled
    enabled = await db.get_setting("agent_dev_mode_enabled")
    assert enabled == "1"


@pytest.mark.asyncio
async def test_save_agent_dev_mode_without_disclaimer(client, db):
    """Test enabling dev mode without disclaimer (should not enable)."""
    await db.set_setting("agent_dev_mode_enabled", "0")

    resp = await client.post(
        "/settings/save-agent",
        data={
            "agent_form_scope": "dev_mode",
            "agent_dev_mode_enabled": "1",
            "agent_dev_mode_disclaimer": "0",
        },
        follow_redirects=False,
    )
    assert resp.status_code == 303
    assert "msg=agent_saved" in resp.headers["location"]

    # Dev mode should NOT be enabled without disclaimer
    enabled = await db.get_setting("agent_dev_mode_enabled")
    assert enabled == "0"


# === Agent Provider tests ===


@pytest.mark.asyncio
async def test_add_agent_provider_writes_disabled(client, db):
    """Test add agent provider when writes are disabled."""
    await db.set_setting("agent_dev_mode_enabled", "1")

    with patch("src.web.routes.settings.AgentProviderService") as mock_service_cls:
        mock_service = MagicMock()
        mock_service.writes_enabled = False
        mock_service.load_provider_configs = AsyncMock(return_value=[])
        mock_service_cls.return_value = mock_service

        resp = await client.post(
            "/settings/agent-providers/add",
            data={"provider": "openai"},
            follow_redirects=False,
        )
        assert resp.status_code == 303
        assert "error=agent_provider_secret_required" in resp.headers["location"]


@pytest.mark.asyncio
async def test_add_agent_provider_dev_mode_required(client, db):
    """Test add agent provider requires dev mode."""
    await db.set_setting("agent_dev_mode_enabled", "0")

    with patch("src.web.routes.settings.AgentProviderService") as mock_service_cls:
        mock_service = MagicMock()
        mock_service.writes_enabled = True  # Set writes_enabled first
        mock_service.load_provider_configs = AsyncMock(return_value=[])
        mock_service.provider_specs = {}
        mock_service_cls.return_value = mock_service

        resp = await client.post(
            "/settings/agent-providers/add",
            data={"provider": "openai"},
            follow_redirects=False,
        )
        assert resp.status_code == 303
        assert "error=agent_dev_mode_required" in resp.headers["location"]


@pytest.mark.asyncio
async def test_save_agent_providers_writes_disabled(client, db):
    """Test save agent providers when writes are disabled."""
    await db.set_setting("agent_dev_mode_enabled", "1")

    with patch("src.web.routes.settings.AgentProviderService") as mock_service_cls:
        mock_service = MagicMock()
        mock_service.writes_enabled = False
        mock_service.load_provider_configs = AsyncMock(return_value=[])
        mock_service_cls.return_value = mock_service

        resp = await client.post(
            "/settings/agent-providers/save",
            data={},
            follow_redirects=False,
        )
        assert resp.status_code == 303
        assert "error=agent_provider_secret_required" in resp.headers["location"]


@pytest.mark.asyncio
async def test_delete_agent_provider_writes_disabled(client, db):
    """Test delete agent provider when writes are disabled."""
    await db.set_setting("agent_dev_mode_enabled", "1")

    with patch("src.web.routes.settings.AgentProviderService") as mock_service_cls:
        mock_service = MagicMock()
        mock_service.writes_enabled = False
        mock_service.load_provider_configs = AsyncMock(return_value=[])
        mock_service_cls.return_value = mock_service

        resp = await client.post(
            "/settings/agent-providers/openai/delete",
            follow_redirects=False,
        )
        assert resp.status_code == 303
        assert "error=agent_provider_secret_required" in resp.headers["location"]


@pytest.mark.asyncio
async def test_refresh_agent_provider_models_writes_disabled(client, db):
    """Test refresh agent provider models when writes are disabled."""
    await db.set_setting("agent_dev_mode_enabled", "1")

    resp = await client.post(
        "/settings/agent-providers/openai/refresh",
        follow_redirects=False,
    )
    # Should return 409 because writes_enabled is False (no SESSION_ENCRYPTION_KEY)
    assert resp.status_code == 409


@pytest.mark.asyncio
async def test_refresh_all_agent_provider_models_writes_disabled(client, db):
    """Test refresh all provider models when writes are disabled."""
    await db.set_setting("agent_dev_mode_enabled", "1")

    resp = await client.post(
        "/settings/agent-providers/refresh-all",
        follow_redirects=False,
    )
    assert resp.status_code == 409


@pytest.mark.asyncio
async def test_probe_agent_provider_model_writes_disabled(client, db):
    """Test probe agent provider model when writes are disabled."""
    await db.set_setting("agent_dev_mode_enabled", "1")

    resp = await client.post(
        "/settings/agent-providers/openai/probe",
        follow_redirects=False,
    )
    assert resp.status_code == 409


# === Test All Agent Provider Models ===


@pytest.mark.asyncio
async def test_test_all_agent_provider_models_writes_disabled(client, db):
    """Test test all agent provider models when writes are disabled."""
    await db.set_setting("agent_dev_mode_enabled", "1")

    resp = await client.post(
        "/settings/agent-providers/test-all",
        follow_redirects=False,
    )
    assert resp.status_code == 409


@pytest.mark.asyncio
async def test_test_all_agent_provider_models_dev_mode_required(client, db):
    """Test test all requires dev mode."""
    await db.set_setting("agent_dev_mode_enabled", "0")

    with patch("src.web.routes.settings.AgentProviderService") as mock_service_cls:
        mock_service = MagicMock()
        mock_service.writes_enabled = True  # Must be True to reach dev_mode check
        mock_service_cls.return_value = mock_service

        resp = await client.post(
            "/settings/agent-providers/test-all",
            follow_redirects=False,
        )
        assert resp.status_code == 403


@pytest.mark.asyncio
async def test_test_all_status_writes_disabled(client, db):
    """Test test all status when writes are disabled."""
    await db.set_setting("agent_dev_mode_enabled", "1")

    resp = await client.get("/settings/agent-providers/test-all/status")
    assert resp.status_code == 409


@pytest.mark.asyncio
async def test_test_all_status_dev_mode_required(client, db):
    """Test test all status requires dev mode."""
    await db.set_setting("agent_dev_mode_enabled", "0")

    with patch("src.web.routes.settings.AgentProviderService") as mock_service_cls:
        mock_service = MagicMock()
        mock_service.writes_enabled = True  # Must be True to reach dev_mode check
        mock_service_cls.return_value = mock_service

        resp = await client.get("/settings/agent-providers/test-all/status")
        assert resp.status_code == 403


# === Notification tests ===


@pytest.mark.asyncio
async def test_notification_setup_json_response(client, db):
    """Test notification setup with JSON accept header."""
    with patch(
        "src.web.routes.settings.deps.get_notification_target_service"
    ) as mock_svc:
        mock_target_service = MagicMock()
        mock_target_service.describe_target = AsyncMock(
            return_value=MagicMock(state="available", configured_phone="+1234567890")
        )
        mock_svc.return_value = mock_target_service

        with patch("src.web.routes.settings.NotificationService") as mock_notif_service_cls:
            mock_notif_service = MagicMock()
            mock_notif_service.setup_bot = AsyncMock(
                return_value=MagicMock(bot_username="test_bot", bot_id=12345)
            )
            mock_notif_service_cls.return_value = mock_notif_service

            resp = await client.post(
                "/settings/notifications/setup",
                headers={"Accept": "application/json"},
                follow_redirects=False,
            )
            assert resp.status_code == 200
            data = resp.json()
            assert data["bot_username"] == "test_bot"
            assert data["bot_id"] == 12345


@pytest.mark.asyncio
async def test_notification_setup_runtime_error(client, db):
    """Test notification setup with runtime error."""
    with patch(
        "src.web.routes.settings.deps.get_notification_target_service"
    ) as mock_svc:
        mock_target_service = MagicMock()
        mock_target_service.describe_target = AsyncMock(
            return_value=MagicMock(state="available", configured_phone="+1234567890")
        )
        mock_svc.return_value = mock_target_service

        with patch("src.web.routes.settings.NotificationService") as mock_notif_service_cls:
            mock_notif_service = MagicMock()
            mock_notif_service.setup_bot = AsyncMock(
                side_effect=RuntimeError("Account not available")
            )
            mock_notif_service_cls.return_value = mock_notif_service

            resp = await client.post(
                "/settings/notifications/setup",
                follow_redirects=False,
            )
            assert resp.status_code == 303
            assert "error=notification_account_unavailable" in resp.headers["location"]


@pytest.mark.asyncio
async def test_notification_setup_runtime_error_json(client, db):
    """Test notification setup runtime error with JSON response."""
    with patch(
        "src.web.routes.settings.deps.get_notification_target_service"
    ) as mock_svc:
        mock_target_service = MagicMock()
        mock_target_service.describe_target = AsyncMock(
            return_value=MagicMock(state="available", configured_phone="+1234567890")
        )
        mock_svc.return_value = mock_target_service

        with patch("src.web.routes.settings.NotificationService") as mock_notif_service_cls:
            mock_notif_service = MagicMock()
            mock_notif_service.setup_bot = AsyncMock(
                side_effect=RuntimeError("Account not available")
            )
            mock_notif_service_cls.return_value = mock_notif_service

            resp = await client.post(
                "/settings/notifications/setup",
                headers={"Accept": "application/json"},
                follow_redirects=False,
            )
            assert resp.status_code == 409
            data = resp.json()
            assert "Account not available" in data["error"]


@pytest.mark.asyncio
async def test_notification_bot_status(client, db):
    """Test notification bot status endpoint."""
    with patch(
        "src.web.routes.settings.deps.get_notification_target_service"
    ) as mock_svc:
        mock_target_service = MagicMock()
        mock_target_service.describe_target = AsyncMock(
            return_value=MagicMock(state="unavailable")
        )
        mock_svc.return_value = mock_target_service

        with patch("src.web.routes.settings.NotificationService") as mock_notif_service_cls:
            mock_notif_service = MagicMock()
            mock_notif_service.get_status = AsyncMock(return_value=None)
            mock_notif_service_cls.return_value = mock_notif_service

            resp = await client.get("/settings/notifications/status")
            assert resp.status_code == 200
            data = resp.json()
            assert data["configured"] is False


@pytest.mark.asyncio
async def test_notification_delete(client, db):
    """Test notification bot deletion."""
    with patch(
        "src.web.routes.settings.deps.get_notification_target_service"
    ) as mock_svc:
        mock_target_service = MagicMock()
        mock_target_service.describe_target = AsyncMock(
            return_value=MagicMock(state="available", configured_phone="+1234567890")
        )
        mock_svc.return_value = mock_target_service

        with patch("src.web.routes.settings.NotificationService") as mock_notif_service_cls:
            mock_notif_service = MagicMock()
            mock_notif_service.teardown_bot = AsyncMock(return_value=None)
            mock_notif_service_cls.return_value = mock_notif_service

            resp = await client.post(
                "/settings/notifications/delete",
                follow_redirects=False,
            )
            assert resp.status_code == 303
            assert "msg=notification_bot_deleted" in resp.headers["location"]


@pytest.mark.asyncio
async def test_notification_delete_json(client, db):
    """Test notification bot deletion with JSON response."""
    with patch(
        "src.web.routes.settings.deps.get_notification_target_service"
    ) as mock_svc:
        mock_target_service = MagicMock()
        mock_target_service.describe_target = AsyncMock(
            return_value=MagicMock(state="available", configured_phone="+1234567890")
        )
        mock_svc.return_value = mock_target_service

        with patch("src.web.routes.settings.NotificationService") as mock_notif_service_cls:
            mock_notif_service = MagicMock()
            mock_notif_service.teardown_bot = AsyncMock(return_value=None)
            mock_notif_service_cls.return_value = mock_notif_service

            resp = await client.post(
                "/settings/notifications/delete",
                headers={"Accept": "application/json"},
                follow_redirects=False,
            )
            assert resp.status_code == 200
            data = resp.json()
            assert data["deleted"] is True


# === Test notification ===


@pytest.mark.asyncio
async def test_test_notification_success(client, db):
    """Test test notification success."""
    with patch("src.web.routes.settings.deps.get_notifier") as mock_get_notifier:
        mock_notifier = MagicMock()
        mock_notifier.admin_chat_id = 12345
        mock_get_notifier.return_value = mock_notifier

        with patch(
            "src.web.routes.settings.deps.get_notification_target_service"
        ) as mock_svc:
            mock_target_service = MagicMock()
            mock_svc.return_value = mock_target_service

            with patch("src.web.routes.settings.NotificationService") as mock_notif_service_cls:
                mock_notif_service = MagicMock()
                mock_notif_service.get_status = AsyncMock(return_value=None)
                mock_notif_service_cls.return_value = mock_notif_service

                with patch("src.web.routes.settings.Notifier") as mock_notifier_cls:
                    mock_notifier_instance = MagicMock()
                    mock_notifier_instance.notify = AsyncMock(return_value=True)
                    mock_notifier_cls.return_value = mock_notifier_instance

                    resp = await client.post(
                        "/settings/notifications/test",
                        follow_redirects=False,
                    )
                    assert resp.status_code == 303
                    assert "msg=notification_test_sent" in resp.headers["location"]
