"""Tests for backend authentication validation."""

from __future__ import annotations

import json
import os
import socket
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest


class TestClaudeBackendAuth:
    """Tests for ClaudeBackend.check_auth()."""

    def test_sdk_not_installed(self) -> None:
        """Should return False if SDK not installed."""
        with patch("cleave.tui.backends.claude._check_sdk", return_value=False):
            from cleave.tui.backends.claude import ClaudeBackend

            is_auth, msg = ClaudeBackend.check_auth()
            assert not is_auth
            assert "not installed" in msg.lower()

    def test_credentials_file_missing(self, tmp_path: Path) -> None:
        """Should return False if credentials file doesn't exist."""
        with patch("cleave.tui.backends.claude._check_sdk", return_value=True):
            with patch("cleave.tui.backends.claude.Path.home", return_value=tmp_path):
                from cleave.tui.backends.claude import ClaudeBackend

                is_auth, msg = ClaudeBackend.check_auth()
                assert not is_auth
                assert "not authenticated" in msg.lower()
                assert "claude" in msg.lower()

    def test_credentials_file_malformed_json(self, tmp_path: Path) -> None:
        """Should return False if credentials file is not valid JSON."""
        with patch("cleave.tui.backends.claude._check_sdk", return_value=True):
            with patch("cleave.tui.backends.claude.Path.home", return_value=tmp_path):
                claude_dir = tmp_path / ".claude"
                claude_dir.mkdir()
                creds_file = claude_dir / ".credentials.json"
                creds_file.write_text("not valid json {")

                from cleave.tui.backends.claude import ClaudeBackend

                is_auth, msg = ClaudeBackend.check_auth()
                assert not is_auth
                assert "not valid json" in msg.lower()

    def test_credentials_file_not_dict(self, tmp_path: Path) -> None:
        """Should return False if credentials file is not a dict."""
        with patch("cleave.tui.backends.claude._check_sdk", return_value=True):
            with patch("cleave.tui.backends.claude.Path.home", return_value=tmp_path):
                claude_dir = tmp_path / ".claude"
                claude_dir.mkdir()
                creds_file = claude_dir / ".credentials.json"
                creds_file.write_text(json.dumps(["not", "a", "dict"]))

                from cleave.tui.backends.claude import ClaudeBackend

                is_auth, msg = ClaudeBackend.check_auth()
                assert not is_auth
                assert "malformed" in msg.lower()

    def test_credentials_file_missing_api_key(self, tmp_path: Path) -> None:
        """Should return False if API key is missing."""
        with patch("cleave.tui.backends.claude._check_sdk", return_value=True):
            with patch("cleave.tui.backends.claude.Path.home", return_value=tmp_path):
                claude_dir = tmp_path / ".claude"
                claude_dir.mkdir()
                creds_file = claude_dir / ".credentials.json"
                creds_file.write_text(json.dumps({"some_field": "value"}))

                from cleave.tui.backends.claude import ClaudeBackend

                is_auth, msg = ClaudeBackend.check_auth()
                assert not is_auth
                assert "api key missing" in msg.lower()

    def test_credentials_file_invalid_api_key_type(self, tmp_path: Path) -> None:
        """Should return False if API key is not a string."""
        with patch("cleave.tui.backends.claude._check_sdk", return_value=True):
            with patch("cleave.tui.backends.claude.Path.home", return_value=tmp_path):
                claude_dir = tmp_path / ".claude"
                claude_dir.mkdir()
                creds_file = claude_dir / ".credentials.json"
                creds_file.write_text(json.dumps({"apiKey": 12345}))

                from cleave.tui.backends.claude import ClaudeBackend

                is_auth, msg = ClaudeBackend.check_auth()
                assert not is_auth
                assert "invalid" in msg.lower()

    def test_credentials_file_invalid_api_key_format(self, tmp_path: Path) -> None:
        """Should return False if API key doesn't start with sk-ant-."""
        with patch("cleave.tui.backends.claude._check_sdk", return_value=True):
            with patch("cleave.tui.backends.claude.Path.home", return_value=tmp_path):
                claude_dir = tmp_path / ".claude"
                claude_dir.mkdir()
                creds_file = claude_dir / ".credentials.json"
                creds_file.write_text(json.dumps({"apiKey": "invalid-key-format"}))

                from cleave.tui.backends.claude import ClaudeBackend

                is_auth, msg = ClaudeBackend.check_auth()
                assert not is_auth
                assert "invalid format" in msg.lower()

    def test_credentials_file_valid(self, tmp_path: Path) -> None:
        """Should return True if credentials file is valid."""
        with patch("cleave.tui.backends.claude._check_sdk", return_value=True):
            with patch("cleave.tui.backends.claude.Path.home", return_value=tmp_path):
                claude_dir = tmp_path / ".claude"
                claude_dir.mkdir()
                creds_file = claude_dir / ".credentials.json"
                creds_file.write_text(
                    json.dumps({"apiKey": "sk-ant-api03-test-key-here"})
                )

                from cleave.tui.backends.claude import ClaudeBackend

                is_auth, msg = ClaudeBackend.check_auth()
                assert is_auth
                assert "authenticated" in msg.lower()


class TestOpenAICompatBackendAuth:
    """Tests for OpenAICompatBackend.check_auth()."""

    def test_openai_package_not_installed(self) -> None:
        """Should return False if openai package not installed."""
        with patch(
            "cleave.tui.backends.openai_compat._check_openai", return_value=False
        ):
            from cleave.tui.backends.openai_compat import OpenAICompatBackend

            # Test with no provider (legacy behavior)
            is_auth, msg = OpenAICompatBackend.check_auth()
            assert not is_auth
            assert "not installed" in msg.lower()

            # Test with explicit provider
            is_auth, msg = OpenAICompatBackend.check_auth(provider="ollama")
            assert not is_auth
            assert "not installed" in msg.lower()

    def test_openai_api_key_missing(self, monkeypatch: pytest.MonkeyPatch) -> None:
        """Should return False if OpenAI API key not set."""
        monkeypatch.delenv("OPENAI_API_KEY", raising=False)
        with patch(
            "cleave.tui.backends.openai_compat._check_openai", return_value=True
        ):
            from cleave.tui.backends.openai_compat import OpenAICompatBackend

            is_auth, msg = OpenAICompatBackend.check_auth(provider="openai")
            assert not is_auth
            assert "not set" in msg.lower()

    def test_openai_api_key_too_short(self, monkeypatch: pytest.MonkeyPatch) -> None:
        """Should return False if OpenAI API key is too short."""
        monkeypatch.setenv("OPENAI_API_KEY", "sk-short")
        with patch(
            "cleave.tui.backends.openai_compat._check_openai", return_value=True
        ):
            from cleave.tui.backends.openai_compat import OpenAICompatBackend

            is_auth, msg = OpenAICompatBackend.check_auth(provider="openai")
            assert not is_auth
            assert "invalid" in msg.lower()

    def test_openai_api_key_invalid_format(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """Should return False if OpenAI API key doesn't start with sk-."""
        monkeypatch.setenv("OPENAI_API_KEY", "not-a-valid-key-format-but-long-enough")
        with patch(
            "cleave.tui.backends.openai_compat._check_openai", return_value=True
        ):
            from cleave.tui.backends.openai_compat import OpenAICompatBackend

            is_auth, msg = OpenAICompatBackend.check_auth(provider="openai")
            assert not is_auth
            assert "invalid format" in msg.lower()

    def test_openai_api_key_valid(self, monkeypatch: pytest.MonkeyPatch) -> None:
        """Should return True if OpenAI API key is valid."""
        monkeypatch.setenv("OPENAI_API_KEY", "sk-" + "x" * 40)
        with patch(
            "cleave.tui.backends.openai_compat._check_openai", return_value=True
        ):
            from cleave.tui.backends.openai_compat import OpenAICompatBackend

            is_auth, msg = OpenAICompatBackend.check_auth(provider="openai")
            assert is_auth
            assert "configured" in msg.lower()

    def test_ollama_not_running(self) -> None:
        """Should return False if Ollama server is not reachable."""
        with patch(
            "cleave.tui.backends.openai_compat._check_openai", return_value=True
        ):
            # Mock socket to simulate connection failure
            with patch("socket.socket") as mock_socket:
                mock_sock = MagicMock()
                mock_sock.connect_ex.return_value = 1  # Connection failed
                mock_socket.return_value = mock_sock

                from cleave.tui.backends.openai_compat import OpenAICompatBackend

                is_auth, msg = OpenAICompatBackend.check_auth(provider="ollama")
                assert not is_auth
                assert "not reachable" in msg.lower()

    def test_ollama_running(self) -> None:
        """Should return True if Ollama server is reachable."""
        with patch(
            "cleave.tui.backends.openai_compat._check_openai", return_value=True
        ):
            # Mock socket to simulate successful connection
            with patch("socket.socket") as mock_socket:
                mock_sock = MagicMock()
                mock_sock.connect_ex.return_value = 0  # Connection successful
                mock_socket.return_value = mock_sock

                from cleave.tui.backends.openai_compat import OpenAICompatBackend

                is_auth, msg = OpenAICompatBackend.check_auth(provider="ollama")
                assert is_auth
                assert "reachable" in msg.lower()

    def test_custom_base_url(self) -> None:
        """Should test custom base URL when provided."""
        with patch(
            "cleave.tui.backends.openai_compat._check_openai", return_value=True
        ):
            with patch("socket.socket") as mock_socket:
                mock_sock = MagicMock()
                mock_sock.connect_ex.return_value = 0
                mock_socket.return_value = mock_sock

                from cleave.tui.backends.openai_compat import OpenAICompatBackend

                is_auth, msg = OpenAICompatBackend.check_auth(
                    provider="vllm", base_url="http://custom-host:9000/v1"
                )
                assert is_auth
                # Verify custom URL was used
                mock_sock.connect_ex.assert_called_once_with(("custom-host", 9000))

    def test_connection_error_handling(self) -> None:
        """Should handle connection errors gracefully."""
        with patch(
            "cleave.tui.backends.openai_compat._check_openai", return_value=True
        ):
            with patch("socket.socket") as mock_socket:
                mock_socket.side_effect = OSError("Network unreachable")

                from cleave.tui.backends.openai_compat import OpenAICompatBackend

                is_auth, msg = OpenAICompatBackend.check_auth(provider="ollama")
                assert not is_auth
                assert "error" in msg.lower()
