"""Tests for app/tasks/check_credentials.py module."""

import json
import os
from unittest.mock import Mock, patch

import pytest

from app.tasks.check_credentials import (
    MockRequest,
    check_credentials,
    get_failure_state,
    save_failure_state,
    sync_test_azure_connection,
    sync_test_openai_connection,
    unwrap_decorated_function,
)


@pytest.mark.unit
class TestMockRequest:
    """Tests for MockRequest class."""

    def test_mock_request_has_session(self):
        """Test MockRequest has session attribute."""
        req = MockRequest()
        assert "user" in req.session
        assert req.session["user"]["id"] == "credential_checker"

    def test_mock_request_has_attributes(self):
        """Test MockRequest has required attributes."""
        req = MockRequest()
        assert req.app is None
        assert isinstance(req.headers, dict)
        assert isinstance(req.query_params, dict)

    @pytest.mark.asyncio
    async def test_mock_request_json(self):
        """Test MockRequest.json() returns empty dict."""
        req = MockRequest()
        result = await req.json()
        assert result == {}

    @pytest.mark.asyncio
    async def test_mock_request_form(self):
        """Test MockRequest.form() returns empty dict."""
        req = MockRequest()
        result = await req.form()
        assert result == {}


@pytest.mark.unit
class TestGetFailureState:
    """Tests for get_failure_state function."""

    @patch("app.tasks.check_credentials.FAILURE_STATE_FILE", "/tmp/test_failure_state.json")
    def test_returns_empty_dict_when_no_file(self):
        """Test returns empty dict when file doesn't exist."""
        # Ensure file doesn't exist
        if os.path.exists("/tmp/test_failure_state.json"):
            os.remove("/tmp/test_failure_state.json")
        result = get_failure_state()
        assert result == {}

    @patch("app.tasks.check_credentials.FAILURE_STATE_FILE", "/tmp/test_failure_state.json")
    def test_reads_existing_state(self):
        """Test reads state from existing file."""
        state = {"OpenAI": {"count": 2, "last_notified": 12345}}
        with open("/tmp/test_failure_state.json", "w") as f:
            json.dump(state, f)

        result = get_failure_state()
        assert result == state

        # Clean up
        os.remove("/tmp/test_failure_state.json")

    @patch("app.tasks.check_credentials.FAILURE_STATE_FILE", "/tmp/test_failure_state_invalid.json")
    def test_handles_invalid_json(self):
        """Test handles invalid JSON file."""
        with open("/tmp/test_failure_state_invalid.json", "w") as f:
            f.write("invalid json {")

        result = get_failure_state()
        assert result == {}

        # Clean up
        os.remove("/tmp/test_failure_state_invalid.json")


@pytest.mark.unit
class TestSaveFailureState:
    """Tests for save_failure_state function."""

    @patch("app.tasks.check_credentials.FAILURE_STATE_FILE", "/tmp/test_failure_state_save.json")
    def test_saves_state_to_file(self):
        """Test saves state to file."""
        state = {"OpenAI": {"count": 1, "last_notified": 0}}
        save_failure_state(state)

        with open("/tmp/test_failure_state_save.json", "r") as f:
            loaded = json.load(f)
        assert loaded == state

        # Clean up
        os.remove("/tmp/test_failure_state_save.json")

    @patch("app.tasks.check_credentials.FAILURE_STATE_FILE", "/invalid/path/test.json")
    def test_handles_save_error(self):
        """Test handles save error gracefully."""
        state = {"OpenAI": {"count": 1}}
        # Should not raise exception
        save_failure_state(state)


@pytest.mark.unit
class TestUnwrapDecoratedFunction:
    """Tests for unwrap_decorated_function."""

    def test_returns_same_function_if_not_decorated(self):
        """Test returns same function if not decorated."""

        def my_func():
            return "hello"

        result = unwrap_decorated_function(my_func)
        assert result is my_func

    def test_unwraps_decorated_function(self):
        """Test unwraps decorated function."""

        def inner():
            return "hello"

        def wrapper():
            return inner()

        wrapper.__wrapped__ = inner

        result = unwrap_decorated_function(wrapper)
        assert result is inner

    def test_unwraps_multiple_levels(self):
        """Test unwraps multiple levels of decoration."""

        def original():
            return "hello"

        def middle():
            return original()

        middle.__wrapped__ = original

        def outer():
            return middle()

        outer.__wrapped__ = middle

        result = unwrap_decorated_function(outer)
        assert result is original


@pytest.mark.unit
class TestSyncTestFunctions:
    """Tests for sync test wrapper functions."""

    @patch("app.tasks.check_credentials.test_openai_connection")
    @patch("app.tasks.check_credentials.unwrap_decorated_function")
    @patch("app.tasks.check_credentials.asyncio.run")
    def test_sync_test_openai_connection(self, mock_asyncio_run, mock_unwrap, mock_test_func):
        """Test sync wrapper for OpenAI connection test."""
        mock_inner = Mock()
        mock_inner.return_value = {"status": "success"}
        mock_unwrap.return_value = mock_inner

        # Mock as sync function
        import inspect

        with patch.object(inspect, "iscoroutinefunction", return_value=False):
            result = sync_test_openai_connection()

        mock_inner.assert_called_once()

    @patch("app.tasks.check_credentials.test_azure_connection")
    @patch("app.tasks.check_credentials.unwrap_decorated_function")
    def test_sync_test_azure_connection(self, mock_unwrap, mock_test_func):
        """Test sync wrapper for Azure connection test."""
        mock_inner = Mock()
        mock_inner.return_value = {"status": "success"}
        mock_unwrap.return_value = mock_inner

        import inspect

        with patch.object(inspect, "iscoroutinefunction", return_value=False):
            result = sync_test_azure_connection()

        mock_inner.assert_called_once()


@pytest.mark.unit
class TestCheckCredentialsTask:
    """Tests for check_credentials task."""

    @patch("app.tasks.check_credentials.save_failure_state")
    @patch("app.tasks.check_credentials.get_failure_state")
    @patch("app.tasks.check_credentials.get_provider_status")
    @patch("app.tasks.check_credentials.validate_storage_configs")
    @patch("app.tasks.check_credentials.sync_test_ai_provider_connection")
    @patch("app.tasks.check_credentials.sync_test_azure_connection")
    @patch("app.tasks.check_credentials.sync_test_dropbox_token")
    @patch("app.tasks.check_credentials.sync_test_google_drive_token")
    @patch("app.tasks.check_credentials.sync_test_onedrive_token")
    def test_checks_all_configured_services(
        self,
        mock_onedrive,
        mock_gdrive,
        mock_dropbox,
        mock_azure,
        mock_ai_provider,
        mock_storage_configs,
        mock_provider_status,
        mock_get_state,
        mock_save_state,
    ):
        """Test checks all configured services."""
        mock_get_state.return_value = {}
        mock_provider_status.return_value = {
            "AI Provider": {"configured": True},
            "Azure AI": {"configured": True},
            "Dropbox": {"configured": True},
            "Google Drive": {"configured": True},
            "OneDrive": {"configured": True},
        }
        mock_storage_configs.return_value = {"dropbox": [], "google_drive": [], "onedrive": []}

        # All tests succeed
        mock_ai_provider.return_value = {"status": "success"}
        mock_azure.return_value = {"status": "success"}
        mock_dropbox.return_value = {"status": "success"}
        mock_gdrive.return_value = {"status": "success"}
        mock_onedrive.return_value = {"status": "success"}

        result = check_credentials()

        assert result["checked"] == 5
        assert result["failures"] == 0

    @patch("app.tasks.check_credentials.save_failure_state")
    @patch("app.tasks.check_credentials.get_failure_state")
    @patch("app.tasks.check_credentials.get_provider_status")
    @patch("app.tasks.check_credentials.validate_storage_configs")
    @patch("app.tasks.check_credentials.sync_test_ai_provider_connection")
    def test_tracks_failures(
        self, mock_ai_provider, mock_storage_configs, mock_provider_status, mock_get_state, mock_save_state
    ):
        """Test tracks credential failures."""
        mock_get_state.return_value = {}
        mock_provider_status.return_value = {
            "AI Provider": {"configured": True},
            "Azure AI": {"configured": False},
            "Dropbox": {"configured": False},
            "Google Drive": {"configured": False},
            "OneDrive": {"configured": False},
        }
        mock_storage_configs.return_value = {}

        mock_ai_provider.return_value = {"status": "error", "message": "Invalid API key"}

        result = check_credentials()

        assert result["checked"] == 1
        assert result["failures"] == 1

    @patch("app.tasks.check_credentials.save_failure_state")
    @patch("app.tasks.check_credentials.get_failure_state")
    @patch("app.tasks.check_credentials.get_provider_status")
    @patch("app.tasks.check_credentials.validate_storage_configs")
    def test_skips_unconfigured_services(
        self, mock_storage_configs, mock_provider_status, mock_get_state, mock_save_state
    ):
        """Test skips unconfigured services."""
        mock_get_state.return_value = {}
        mock_provider_status.return_value = {
            "AI Provider": {"configured": False},
            "Azure AI": {"configured": False},
            "Dropbox": {"configured": False},
            "Google Drive": {"configured": False},
            "OneDrive": {"configured": False},
        }
        mock_storage_configs.return_value = {}

        result = check_credentials()

        assert result["checked"] == 0
        assert result["unconfigured"] == 5

    @patch("app.tasks.check_credentials.save_failure_state")
    @patch("app.tasks.check_credentials.get_failure_state")
    @patch("app.tasks.check_credentials.get_provider_status")
    @patch("app.tasks.check_credentials.validate_storage_configs")
    @patch("app.tasks.check_credentials.sync_test_ai_provider_connection")
    @patch("app.tasks.check_credentials.notify_credential_failure")
    def test_sends_notifications_on_failure(
        self, mock_notify, mock_ai_provider, mock_storage_configs, mock_provider_status, mock_get_state, mock_save_state
    ):
        """Test sends notifications on credential failure."""
        mock_get_state.return_value = {}
        mock_provider_status.return_value = {
            "AI Provider": {"configured": True},
            "Azure AI": {"configured": False},
            "Dropbox": {"configured": False},
            "Google Drive": {"configured": False},
            "OneDrive": {"configured": False},
        }
        mock_storage_configs.return_value = {}

        mock_ai_provider.return_value = {"status": "error", "message": "Invalid API key"}

        check_credentials()

        mock_notify.assert_called_once()

    @patch("app.tasks.check_credentials.save_failure_state")
    @patch("app.tasks.check_credentials.get_failure_state")
    @patch("app.tasks.check_credentials.get_provider_status")
    @patch("app.tasks.check_credentials.validate_storage_configs")
    @patch("app.tasks.check_credentials.sync_test_ai_provider_connection")
    @patch("app.tasks.check_credentials.notify_credential_failure")
    def test_suppresses_notifications_after_threshold(
        self, mock_notify, mock_ai_provider, mock_storage_configs, mock_provider_status, mock_get_state, mock_save_state
    ):
        """Test suppresses notifications after failure threshold."""
        # Existing state with 4 failures
        mock_get_state.return_value = {"AI Provider": {"count": 4, "last_notified": 12345}}
        mock_provider_status.return_value = {
            "AI Provider": {"configured": True},
            "Azure AI": {"configured": False},
            "Dropbox": {"configured": False},
            "Google Drive": {"configured": False},
            "OneDrive": {"configured": False},
        }
        mock_storage_configs.return_value = {}

        mock_ai_provider.return_value = {"status": "error", "message": "Invalid API key"}

        check_credentials()

        # Notification should be suppressed (already notified 3 times)
        mock_notify.assert_not_called()

    @patch("app.tasks.check_credentials.save_failure_state")
    @patch("app.tasks.check_credentials.get_failure_state")
    @patch("app.tasks.check_credentials.get_provider_status")
    @patch("app.tasks.check_credentials.validate_storage_configs")
    @patch("app.tasks.check_credentials.sync_test_ai_provider_connection")
    def test_tracks_recovery(
        self, mock_ai_provider, mock_storage_configs, mock_provider_status, mock_get_state, mock_save_state
    ):
        """Test tracks service recovery."""
        # Existing state with failures
        mock_get_state.return_value = {"AI Provider": {"count": 2, "last_notified": 12345}}
        mock_provider_status.return_value = {
            "AI Provider": {"configured": True},
            "Azure AI": {"configured": False},
            "Dropbox": {"configured": False},
            "Google Drive": {"configured": False},
            "OneDrive": {"configured": False},
        }
        mock_storage_configs.return_value = {}

        # Service is now valid
        mock_ai_provider.return_value = {"status": "success"}

        result = check_credentials()

        assert result["failures"] == 0

    @patch("app.tasks.check_credentials.save_failure_state")
    @patch("app.tasks.check_credentials.get_failure_state")
    @patch("app.tasks.check_credentials.get_provider_status")
    @patch("app.tasks.check_credentials.validate_storage_configs")
    @patch("app.tasks.check_credentials.sync_test_ai_provider_connection")
    def test_handles_exception_during_check(
        self, mock_ai_provider, mock_storage_configs, mock_provider_status, mock_get_state, mock_save_state
    ):
        """Test handles exception during credential check."""
        mock_get_state.return_value = {}
        mock_provider_status.return_value = {
            "AI Provider": {"configured": True},
            "Azure AI": {"configured": False},
            "Dropbox": {"configured": False},
            "Google Drive": {"configured": False},
            "OneDrive": {"configured": False},
        }
        mock_storage_configs.return_value = {}

        mock_ai_provider.side_effect = Exception("Network error")

        result = check_credentials()

        # Should still complete and record the error
        assert result["failures"] == 1
        assert "AI Provider" in result["results"]
        assert result["results"]["AI Provider"]["status"] == "error"
