"""
Unit tests for auth storage module.

Tests verify the public API of lattice.shell.auth using file isolation
via tmp_path and monkeypatch fixtures.

Per R7 (Boundary Isolation): Unit tests mock the filesystem via monkeypatch.
"""

import stat

import pytest
from returns.result import Failure, Success

from lattice.core.auth import validate_provider_name
from lattice.shell.auth import (
    AUTH_FILE,
    delete_auth,
    get_auth,
    get_auth_file_path,
    load_auth,
    save_auth,
)


class TestValidateProviderName:
    """Test validate_provider_name function."""

    def test_valid_simple_name(self):
        """Test simple alphanumeric name passes."""
        assert validate_provider_name("openai") is True

    def test_valid_with_hyphen(self):
        """Test name with hyphen passes."""
        assert validate_provider_name("anthropic-claude") is True

    def test_valid_with_underscore(self):
        """Test name with underscore passes."""
        assert validate_provider_name("google_gemini") is True

    def test_valid_max_length(self):
        """Test name at max length (50) passes."""
        assert validate_provider_name("a" * 50) is True

    def test_valid_min_length(self):
        """Test name at min length (1) passes."""
        assert validate_provider_name("a") is True

    def test_invalid_empty(self):
        """Test empty string fails."""
        result = validate_provider_name("")
        assert isinstance(result, str)
        assert "empty" in result.lower()

    def test_invalid_too_long(self):
        """Test name exceeding max length fails."""
        result = validate_provider_name("a" * 51)
        assert isinstance(result, str)
        assert "long" in result.lower()

    def test_invalid_special_chars(self):
        """Test name with special characters fails."""
        result = validate_provider_name("invalid name!")
        assert isinstance(result, str)
        assert "letters" in result.lower()

    def test_invalid_with_dot(self):
        """Test name with dot fails."""
        result = validate_provider_name("test.provider")
        assert isinstance(result, str)
        assert "letters" in result.lower()


class TestGetAuthFilePath:
    """Test get_auth_file_path function."""

    def test_get_auth_file_path_returns_correct_path(self):
        """Test auth file path is under ~/.config/lattice/auth.json"""
        path = get_auth_file_path()
        assert path.name == "auth.json"
        assert ".config" in str(path)
        assert "lattice" in str(path)


class TestSaveAuth:
    """Test save_auth function."""

    def test_save_auth_creates_file(self, tmp_path, monkeypatch):
        """Test save_auth creates file with correct permissions."""
        auth_file = tmp_path / "auth.json"
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        result = save_auth("openai", "sk-test")

        assert isinstance(result, Success)
        assert result.unwrap() is None
        assert auth_file.exists()
        # Check permissions (0o600 = stat.S_IRUSR | stat.S_IWUSR)
        assert auth_file.stat().st_mode & 0o777 == 0o600

    def test_save_auth_updates_existing_file(self, tmp_path, monkeypatch):
        """Test save_auth updates existing provider entry."""
        auth_file = tmp_path / "auth.json"
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        # Save initial key
        save_auth("openai", "sk-old").unwrap()

        # Update with new key
        result = save_auth("openai", "sk-new")

        assert isinstance(result, Success)
        # Verify key was updated
        assert get_auth("openai").unwrap() == "sk-new"

    def test_save_auth_multiple_providers(self, tmp_path, monkeypatch):
        """Test save_auth handles multiple providers."""
        auth_file = tmp_path / "auth.json"
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        save_auth("openai", "sk-openai").unwrap()
        save_auth("anthropic", "sk-anthropic").unwrap()

        assert get_auth("openai").unwrap() == "sk-openai"
        assert get_auth("anthropic").unwrap() == "sk-anthropic"

    def test_save_auth_rejects_empty_provider(self, tmp_path, monkeypatch):
        """Test save_auth rejects empty provider name."""
        auth_file = tmp_path / "auth.json"
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        result = save_auth("", "sk-test")

        assert isinstance(result, Failure)
        assert "empty" in result.failure().lower()

    def test_save_auth_rejects_invalid_provider(self, tmp_path, monkeypatch):
        """Test save_auth rejects provider name with invalid characters."""
        auth_file = tmp_path / "auth.json"
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        result = save_auth("test.provider", "sk-test")

        assert isinstance(result, Failure)
        assert "letters" in result.failure().lower()

    def test_save_auth_rejects_too_long_provider(self, tmp_path, monkeypatch):
        """Test save_auth rejects provider name exceeding max length."""
        auth_file = tmp_path / "auth.json"
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        result = save_auth("a" * 51, "sk-test")

        assert isinstance(result, Failure)
        assert "long" in result.failure().lower()


class TestLoadAuth:
    """Test load_auth function."""

    def test_load_auth_missing_file(self, tmp_path, monkeypatch):
        """Test load_auth returns empty dict for missing file."""
        auth_file = tmp_path / "auth.json"
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        result = load_auth()

        assert isinstance(result, Success)
        assert result.unwrap() == {}

    def test_load_auth_existing_data(self, tmp_path, monkeypatch):
        """Test load_auth returns existing data."""
        auth_file = tmp_path / "auth.json"
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        save_auth("openai", "sk-key1").unwrap()
        save_auth("anthropic", "sk-ant-key2").unwrap()

        result = load_auth()
        data = result.unwrap()

        assert isinstance(data, dict)
        assert data["openai"]["api_key"] == "sk-key1"
        assert data["anthropic"]["api_key"] == "sk-ant-key2"


class TestGetAuth:
    """Test get_auth function."""

    def test_get_auth_existing_provider(self, tmp_path, monkeypatch):
        """Test get_auth returns key for existing provider."""
        auth_file = tmp_path / "auth.json"
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        save_auth("openai", "sk-test").unwrap()

        result = get_auth("openai")

        assert isinstance(result, Success)
        assert result.unwrap() == "sk-test"

    def test_get_auth_missing_provider(self, tmp_path, monkeypatch):
        """Test get_auth returns None for missing provider."""
        auth_file = tmp_path / "auth.json"
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        result = get_auth("unknown")

        assert isinstance(result, Success)
        assert result.unwrap() is None

    def test_get_auth_from_empty_file(self, tmp_path, monkeypatch):
        """Test get_auth returns None when no providers exist."""
        auth_file = tmp_path / "auth.json"
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)
        # File doesn't exist yet

        result = get_auth("openai")

        assert isinstance(result, Success)
        assert result.unwrap() is None

    def test_get_auth_rejects_empty_provider(self, tmp_path, monkeypatch):
        """Test get_auth rejects empty provider name."""
        auth_file = tmp_path / "auth.json"
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        result = get_auth("")

        assert isinstance(result, Failure)
        assert "empty" in result.failure().lower()

    def test_get_auth_rejects_invalid_provider(self, tmp_path, monkeypatch):
        """Test get_auth rejects provider name with invalid characters."""
        auth_file = tmp_path / "auth.json"
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        result = get_auth("test.provider")

        assert isinstance(result, Failure)
        assert "letters" in result.failure().lower()


class TestDeleteAuth:
    """Test delete_auth function."""

    def test_delete_auth_removes_provider(self, tmp_path, monkeypatch):
        """Test delete_auth removes provider."""
        auth_file = tmp_path / "auth.json"
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        save_auth("openai", "sk-test").unwrap()
        result = delete_auth("openai")

        assert isinstance(result, Success)
        assert result.unwrap() is True
        assert get_auth("openai").unwrap() is None

    def test_delete_auth_missing_provider(self, tmp_path, monkeypatch):
        """Test delete_auth returns False for missing provider."""
        auth_file = tmp_path / "auth.json"
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        result = delete_auth("unknown")

        assert isinstance(result, Success)
        assert result.unwrap() is False

    def test_delete_auth_preserves_other_providers(self, tmp_path, monkeypatch):
        """Test delete_auth only removes specified provider."""
        auth_file = tmp_path / "auth.json"
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        save_auth("openai", "sk-openai").unwrap()
        save_auth("anthropic", "sk-anthropic").unwrap()

        delete_auth("openai").unwrap()

        assert get_auth("openai").unwrap() is None
        assert get_auth("anthropic").unwrap() == "sk-anthropic"

    def test_delete_auth_rejects_empty_provider(self, tmp_path, monkeypatch):
        """Test delete_auth rejects empty provider name."""
        auth_file = tmp_path / "auth.json"
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        result = delete_auth("")

        assert isinstance(result, Failure)
        assert "empty" in result.failure().lower()

    def test_delete_auth_rejects_invalid_provider(self, tmp_path, monkeypatch):
        """Test delete_auth rejects provider name with invalid characters."""
        auth_file = tmp_path / "auth.json"
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        result = delete_auth("test.provider")

        assert isinstance(result, Failure)
        assert "letters" in result.failure().lower()


class TestIntegration:
    """Integration tests for full auth workflow."""

    def test_full_auth_workflow(self, tmp_path, monkeypatch):
        """Test complete save, get, load, delete workflow."""
        auth_file = tmp_path / "auth.json"
        monkeypatch.setattr("lattice.shell.auth.AUTH_FILE", auth_file)

        # Save multiple providers
        save_auth("openai", "sk-openai-123").unwrap()
        save_auth("anthropic", "sk-ant-456").unwrap()

        # Get individual providers
        assert get_auth("openai").unwrap() == "sk-openai-123"
        assert get_auth("anthropic").unwrap() == "sk-ant-456"

        # Load all
        all_auth = load_auth().unwrap()
        assert len(all_auth) == 2

        # Delete one
        assert delete_auth("openai").unwrap() is True
        assert get_auth("openai").unwrap() is None
        assert get_auth("anthropic").unwrap() == "sk-ant-456"

        # Delete missing returns False
        assert delete_auth("nonexistent").unwrap() is False
