"""Tests for the CLI interface."""
from typing import Generator
from unittest.mock import patch, MagicMock, PropertyMock

import pytest
from typer.testing import CliRunner
from web3_google_hsm._cli import app

@pytest.fixture
def runner() -> CliRunner:
    """
    Creates a CLI test runner for executing commands in isolation.
    This ensures each test has a fresh CLI environment.
    """
    return CliRunner()

@pytest.fixture
def mock_kms_client() -> Generator[MagicMock, None, None]:
    """
    Mocks the Google KMS client to prevent real API calls.
    This includes mocking both the client initialization and key-related methods.
    """
    with patch("google.cloud.kms.KeyManagementServiceClient") as mock:
        # Create a mock instance for method chaining
        client_instance = mock.return_value

        # Mock the key path method to return a consistent path
        client_instance.crypto_key_version_path.return_value = (
            "projects/test-project/locations/test-region/keyRings/test-keyring/"
            "cryptoKeys/test-key/cryptoKeyVersions/1"
        )

        # Mock the public key retrieval
        public_key_response = MagicMock()
        public_key_response.pem = "-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAE0hPxTjwIf407JpkjCdf9kwVPvGdMOZUq\nGaVPbV4qdocIUoJlxmWoOQeL/mR28cLrRqgn+Uj8HAoman2lndsp3w==\n-----END PUBLIC KEY-----\n"

        client_instance.get_public_key.return_value = public_key_response

        yield mock

@pytest.fixture
def mock_account_setup(mock_kms_client: MagicMock) -> Generator[MagicMock, None, None]:
    """
    Creates a complete mock for GCPKmsAccount with all necessary properties and methods.
    This prevents real API calls while providing realistic test responses.
    """
    with patch("web3_google_hsm.GCPKmsAccount") as mock_account:
        # Set up the instance that will be returned
        instance = mock_account.return_value

        # Mock the address property with our specific test address
        type(instance).address = PropertyMock(
            return_value="0x0545640A0EcD6FB6ae94766811F30dCDA4746DFC"
        )

        # Mock the key creation classmethod
        mock_key = MagicMock()
        mock_key.name = (
            "projects/test-project/locations/test-region/keyRings/test-keyring/"
            "cryptoKeys/test-key"
        )
        mock_account.create_eth_key.return_value = mock_key

        # Create a realistic signature mock with our specific test values
        mock_signature = MagicMock()
        mock_signature.v = 27
        mock_signature.r = bytes.fromhex(
            "0ef3b04562ace166a974a96ccbaa05c4bfdc85add2a621added6754e75717d9e"
        )
        mock_signature.s = bytes.fromhex(
            "0669929c316bd7a6cd4688526f4fa446ffa25457d9d50cf089e71d111eaed9b0"
        )
        # Combine r and s into the complete signature
        mock_signature.to_hex.return_value = (
            "0x0ef3b04562ace166a974a96ccbaa05c4bfdc85add2a621added6754e75717d9e"
            "0669929c316bd7a6cd4688526f4fa446ffa25457d9d50cf089e71d111eaed9b0"
        )
        instance.sign_message.return_value = mock_signature

        yield mock_account


class TestGenerateCommand:
    """Tests for the 'generate' command."""

    def test_generate_with_env_vars(
        self, runner: CliRunner, mock_account_setup: MagicMock
    ) -> None:
        """Test key generation using environment variables."""
        with patch.dict(
            "os.environ",
            {
                "GOOGLE_CLOUD_PROJECT": "test-project",
                "GOOGLE_CLOUD_REGION": "test-region",
                "KEY_RING": "test-keyring",
                "KEY_NAME": "test-key",
            },
            clear=True,  # Important: clear other env vars
        ):
            result = runner.invoke(app, ["generate"])

            assert result.exit_code == 0
            assert "Created Ethereum signing key" in result.stdout
            assert "0x0545640A0EcD6FB6ae94766811F30dCDA4746DFC" in result.stdout

    def test_generate_with_explicit_args(
        self, runner: CliRunner, mock_account_setup: MagicMock
    ) -> None:
        """Test key generation using explicit command line arguments."""
        result = runner.invoke(
            app,
            [
                "generate",
                "--project-id", "cli-project",
                "--location", "cli-region",
                "--keyring", "cli-keyring",
                "--key-id", "cli-key",
                "--retention-days", "180",
            ],
        )

        assert result.exit_code == 0
        assert "Created Ethereum signing key" in result.stdout

    def test_fail_generate_missing_required_args(self, runner: CliRunner, monkeypatch) -> None:
        """Test key generation fails when required arguments are missing."""
        # ? Need to clrear the env vars in order to raise error in the cli
        env_vars_to_clear = [
            "GOOGLE_CLOUD_PROJECT",
            "GOOGLE_CLOUD_REGION",
            "KEY_RING",
            "KEY_NAME",
            "GOOGLE_APPLICATION_CREDENTIALS",
            "GCP_ADC_CREDENTIALS_STRING"
        ]

        for env_var in env_vars_to_clear:
            monkeypatch.delenv(env_var, raising=False)
        result = runner.invoke(app, ["generate"])
        assert result.exit_code == 2
        assert "Usage:" in result.stdout

    #? These tests are skipped because of `memoryview: a bytes-like object is required, not 'MagicMock'`
    # def test_generate_handles_error(
    #     self, runner: CliRunner, mock_account_setup: MagicMock
    # ) -> None:
    #     """
    #     Test error handling during key generation by simulating a failure
    #     in the key creation process.
    #     """
    #     # Setup the error at the correct level in the dependency chain
    #     with patch("web3_google_hsm.accounts.gcp_kms_account.GCPKmsAccount.create_eth_key") as mock_create:
    #         # Make the create_eth_key method raise our test error
    #         mock_create.side_effect = Exception("Test error")

    #         # Execute the command with environment variables
    #         with patch.dict(
    #             "os.environ",
    #             {
    #                 "GOOGLE_CLOUD_PROJECT": "test-project",
    #                 "GOOGLE_CLOUD_REGION": "test-region",
    #                 "KEY_RING": "test-keyring",
    #                 "KEY_NAME": "test-key",
    #             },
    #             clear=True,
    #         ):
    #             result = runner.invoke(app, ["generate"])

    #             # For debugging, let's see what actually happened
    #             print(f"Command output: {result.stdout}")

    #             # Verify the error handling
    #             assert result.exit_code == 1
    #             assert "Test error" in result.stdout


class TestSignCommand:
    """Tests for the 'sign' command."""

    # def test_sign_message(
    #     self, runner: CliRunner, mock_account_setup: MagicMock
    # ) -> None:
    #     """Test signing a message with correct account."""
    #     with patch.dict(
    #         "os.environ",
    #         {
    #             "GOOGLE_CLOUD_PROJECT": "test-project",
    #             "GOOGLE_CLOUD_REGION": "test-region",
    #             "KEY_RING": "test-keyring",
    #             "KEY_NAME": "test-key",
    #         },
    #         clear=True,
    #     ):
    #         result = runner.invoke(
    #             app,
    #             [
    #                 "sign",
    #                 "Hello Ethereum!",
    #                 "--account", "0x0545640A0EcD6FB6ae94766811F30dCDA4746DFC",
    #             ],
    #         )
    #         print(result)
    #         assert result.exit_code == 0
    #         assert "Message signed successfully!" in result.stdout
    #         assert "Hello Ethereum!" in result.stdout

    def test_fail_sign_with_wrong_account(
        self, runner: CliRunner, mock_account_setup: MagicMock
    ) -> None:
        """Test signing fails with wrong account address."""
        with patch.dict(
            "os.environ",
            {
                "GOOGLE_CLOUD_PROJECT": "test-project",
                "GOOGLE_CLOUD_REGION": "test-region",
                "KEY_RING": "test-keyring",
                "KEY_NAME": "test-key",
            },
            clear=True,
        ):
            result = runner.invoke(
                app,
                [
                    "sign",
                    "Hello Ethereum!",
                    "--account", "0x0000000000000000000000000000000000000000",
                ],
            )

            assert result.exit_code == 1
            assert "Account mismatch" in result.stdout

    # def test_fail_sign_handles_signing_error(
    #     self, runner: CliRunner, mock_account_setup: MagicMock
    # ) -> None:
    #     """Test error handling during signing."""
    #     mock_account_setup.return_value.sign_message.side_effect = Exception(
    #         "Signing failed"
    #     )

    #     with patch.dict(
    #         "os.environ",
    #         {
    #             "GOOGLE_CLOUD_PROJECT": "test-project",
    #             "GOOGLE_CLOUD_REGION": "test-region",
    #             "KEY_RING": "test-keyring",
    #             "KEY_NAME": "test-key",
    #         },
    #         clear=True,
    #     ):
    #         result = runner.invoke(
    #             app,
    #             [
    #                 "sign",
    #                 "Hello Ethereum!",
    #                 "--account", "0x0545640A0EcD6FB6ae94766811F30dCDA4746DFC",
    #             ],
    #         )

    #         assert result.exit_code == 1
    #         assert "Signing failed" in result.stdout
