import pytest
from unittest.mock import patch, MagicMock
import subprocess
from lastpass_vault import LastPassVault, LastPassError

@pytest.fixture
def vault():
    return LastPassVault()

def test_run_command_success(vault):
    with patch("subprocess.run") as mock_run:
        mock_run.return_value = MagicMock(
            stdout="success_output\n",
            stderr="",
            returncode=0
        )
        result = vault._run_command(["status"])
        assert result == "success_output"
        mock_run.assert_called_once_with(
            ["lpass", "status"],
            input=None,
            capture_output=True,
            text=True,
            check=True
        )

def test_run_command_failure(vault):
    with patch("subprocess.run") as mock_run:
        mock_run.side_effect = subprocess.CalledProcessError(
            returncode=1,
            cmd=["lpass", "status"],
            stderr="Error message"
        )
        with pytest.raises(LastPassError, match="Command 'lpass status' failed: Error message"):
            vault._run_command(["status"])

def test_login_success(vault):
    with patch.object(LastPassVault, "_run_command") as mock_run_cmd:
        mock_run_cmd.return_value = "Success"
        assert vault.login("user@example.com", "password123") is True
        mock_run_cmd.assert_called_once_with(["login", "user@example.com"], input_data="password123")

def test_login_failure(vault):
    with patch.object(LastPassVault, "_run_command") as mock_run_cmd:
        mock_run_cmd.side_effect = LastPassError("Login failed")
        assert vault.login("user@example.com", "wrong_pass") is False

def test_logout_success(vault):
    with patch.object(LastPassVault, "_run_command") as mock_run_cmd:
        assert vault.logout() is True
        mock_run_cmd.assert_called_once_with(["logout"])

def test_logout_force(vault):
    with patch.object(LastPassVault, "_run_command") as mock_run_cmd:
        assert vault.logout(force=True) is True
        mock_run_cmd.assert_called_once_with(["logout", "--force"])

def test_is_logged_in_true(vault):
    with patch.object(LastPassVault, "_run_command") as mock_run_cmd:
        mock_run_cmd.return_value = "Logged in as user@example.com."
        assert vault.is_logged_in() is True

def test_is_logged_in_false(vault):
    with patch.object(LastPassVault, "_run_command") as mock_run_cmd:
        mock_run_cmd.side_effect = LastPassError("Not logged in.")
        assert vault.is_logged_in() is False

def test_sync_success(vault):
    with patch.object(LastPassVault, "_run_command") as mock_run_cmd:
        assert vault.sync() is True
        mock_run_cmd.assert_called_once_with(["sync"])

def test_list_items(vault):
    mock_output = "123|Site A|userA\n456|Site B|userB"
    with patch.object(LastPassVault, "_run_command") as mock_run_cmd:
        mock_run_cmd.return_value = mock_output
        items = vault.list_items()
        assert len(items) == 2
        assert items[0] == {"id": "123", "name": "Site A", "username": "userA"}
        assert items[1] == {"id": "456", "name": "Site B", "username": "userB"}

def test_get_password_success(vault):
    with patch.object(LastPassVault, "_run_command") as mock_run_cmd:
        mock_run_cmd.return_value = "secret_password"
        assert vault.get_password("123") == "secret_password"
        mock_run_cmd.assert_called_once_with(["show", "--password", "123"])

def test_get_password_failure(vault):
    with patch.object(LastPassVault, "_run_command") as mock_run_cmd:
        mock_run_cmd.side_effect = LastPassError("Item not found")
        assert vault.get_password("999") is None

def test_get_item_details(vault):
    mock_output = "ID: 123\nName: My Site\nUsername: myuser\nURL: https://example.com"
    with patch.object(LastPassVault, "_run_command") as mock_run_cmd:
        mock_run_cmd.return_value = mock_output
        details = vault.get_item_details("123")
        assert details["ID"] == "123"
        assert details["Name"] == "My Site"
        assert details["Username"] == "myuser"
        assert details["URL"] == "https://example.com"

def test_list_items_empty(vault):
    with patch.object(LastPassVault, "_run_command") as mock_run_cmd:
        mock_run_cmd.return_value = ""
        assert vault.list_items() == []

def test_list_items_malformed(vault):
    mock_output = "123|Site A\n456|Site B|userB|extra"
    with patch.object(LastPassVault, "_run_command") as mock_run_cmd:
        mock_run_cmd.return_value = mock_output
        items = vault.list_items()
        assert items == []

def test_list_items_error(vault):
    with patch.object(LastPassVault, "_run_command") as mock_run_cmd:
        mock_run_cmd.side_effect = LastPassError("Failed")
        assert vault.list_items() == []

def test_get_item_details_empty(vault):
    with patch.object(LastPassVault, "_run_command") as mock_run_cmd:
        mock_run_cmd.return_value = ""
        assert vault.get_item_details("123") == {}

def test_get_item_details_malformed(vault):
    mock_output = "No colon here\nKey: Value"
    with patch.object(LastPassVault, "_run_command") as mock_run_cmd:
        mock_run_cmd.return_value = mock_output
        details = vault.get_item_details("123")
        assert "Key" in details
        assert details["Key"] == "Value"
        assert len(details) == 1

def test_get_item_details_error(vault):
    with patch.object(LastPassVault, "_run_command") as mock_run_cmd:
        mock_run_cmd.side_effect = LastPassError("Failed")
        assert vault.get_item_details("123") == {}

def test_search(vault):
    mock_items = [
        {"id": "1", "name": "Google", "username": "user1"},
        {"id": "2", "name": "GitHub", "username": "user2"},
        {"id": "3", "name": "GitLab", "username": "user1"}
    ]
    with patch.object(LastPassVault, "list_items") as mock_list:
        mock_list.return_value = mock_items
        
        # Search by name
        results = vault.search("Git")
        assert len(results) == 2
        assert results[0]["name"] == "GitHub"
        assert results[1]["name"] == "GitLab"
        
        # Search by username
        results = vault.search("user2")
        assert len(results) == 1
        assert results[0]["id"] == "2"
        
        # Search no results
        assert vault.search("unknown") == []
