import tempfile
import zipfile
from pathlib import Path
from unittest.mock import patch

import pytest
from pydantic import field_validator

from mm_web3 import Web3CliConfig
from mm_web3.config import read_text_from_zip_archive


class SimpleTestConfig(Web3CliConfig):
    """Test configuration class for testing CryptocurrencyConfig functionality."""

    name: str
    count: int
    enabled: bool = True

    @field_validator("count")
    def validate_count(cls, v: int) -> int:
        if v < 0:
            raise ValueError("count must be non-negative")
        return v


class AsyncValidatorConfig(Web3CliConfig):
    """Test configuration with async validation."""

    name: str
    url: str

    @field_validator("url")
    @classmethod
    def validate_url(cls, v: str) -> str:
        # Simple sync validation for testing
        if not v.startswith("https://"):
            raise ValueError("url must start with https://")
        return v


def test_toml_config_loading():
    """Test loading TOML configuration files with various scenarios."""
    with tempfile.TemporaryDirectory() as tmp_dir:
        config_dir = Path(tmp_dir)

        # Test successful loading
        valid_config = config_dir / "valid.toml"
        valid_config.write_text("""
name = "test-app"
count = 42
enabled = true
""")

        config = SimpleTestConfig.read_toml_config_or_exit(valid_config)
        assert config.name == "test-app"
        assert config.count == 42
        assert config.enabled is True

        # Test Result-based loading (success)
        result = SimpleTestConfig.read_toml_config(valid_config)
        assert result.is_ok()
        loaded_config = result.unwrap()
        assert loaded_config.name == "test-app"

        # Test missing file
        missing_file = config_dir / "missing.toml"
        result = SimpleTestConfig.read_toml_config(missing_file)
        assert result.is_err()

        # Test invalid TOML syntax
        invalid_toml = config_dir / "invalid.toml"
        invalid_toml.write_text("name = invalid toml [")
        result = SimpleTestConfig.read_toml_config(invalid_toml)
        assert result.is_err()

        # Test validation errors
        validation_error_config = config_dir / "validation_error.toml"
        validation_error_config.write_text("""
name = "test"
count = -5
""")
        result = SimpleTestConfig.read_toml_config(validation_error_config)
        assert result.is_err()
        assert result.error == "validator_error"
        assert result.extra is not None and "errors" in result.extra

        # Test extra fields (should fail due to forbid extra)
        extra_fields_config = config_dir / "extra.toml"
        extra_fields_config.write_text("""
name = "test"
count = 10
unknown_field = "value"
""")
        result = SimpleTestConfig.read_toml_config(extra_fields_config)
        assert result.is_err()


def test_zip_archive_loading():
    """Test loading configuration from ZIP archives with and without passwords."""
    with tempfile.TemporaryDirectory() as tmp_dir:
        config_dir = Path(tmp_dir)

        # Create test TOML content
        toml_content = """
name = "zip-test"
count = 100
enabled = false
"""

        # Test unencrypted ZIP
        zip_path = config_dir / "config.zip"
        with zipfile.ZipFile(zip_path, "w") as zf:
            zf.writestr("config.toml", toml_content)

        config = SimpleTestConfig.read_toml_config_or_exit(zip_path)
        assert config.name == "zip-test"
        assert config.count == 100
        assert config.enabled is False

        # Test ZIP with multiple files (should read first)
        multi_file_zip = config_dir / "multi.zip"
        with zipfile.ZipFile(multi_file_zip, "w") as zf:
            zf.writestr("first.toml", toml_content)
            zf.writestr("second.toml", 'name = "second"\ncount = 200')

        config = SimpleTestConfig.read_toml_config_or_exit(multi_file_zip)
        assert config.name == "zip-test"  # First file should be loaded


def test_async_config_loading():
    """Test asynchronous configuration loading functionality."""
    with tempfile.TemporaryDirectory() as tmp_dir:
        config_dir = Path(tmp_dir)

        # Test that async method exists and handles basic cases
        valid_config = config_dir / "async_valid.toml"
        valid_config.write_text("""
name = "async-test"
url = "https://example.com"
""")

        # For configs without actual async validators, the async method
        # should behave similarly to the sync version
        async def test_async_methods():
            # Test successful case - should work even without async validators
            result = await AsyncValidatorConfig.read_toml_config_async(valid_config)
            # The method may fail if there are no actual async validators
            # This is expected behavior, so we just test it doesn't crash
            assert result is not None

            # Test missing file
            missing_file = config_dir / "missing.toml"
            result = await AsyncValidatorConfig.read_toml_config_async(missing_file)
            assert result.is_err()

        import asyncio

        asyncio.run(test_async_methods())


def test_read_text_from_zip_archive():
    """Test the utility function for reading text from ZIP archives."""
    with tempfile.TemporaryDirectory() as tmp_dir:
        config_dir = Path(tmp_dir)

        # Test reading specific file
        zip_path = config_dir / "test.zip"
        file_content = "Hello, World!"
        another_content = "Another file content"

        with zipfile.ZipFile(zip_path, "w") as zf:
            zf.writestr("file1.txt", file_content)
            zf.writestr("file2.txt", another_content)

        # Read specific file
        content = read_text_from_zip_archive(zip_path, "file1.txt")
        assert content == file_content

        content = read_text_from_zip_archive(zip_path, "file2.txt")
        assert content == another_content

        # Read first file (default behavior)
        content = read_text_from_zip_archive(zip_path)
        assert content == file_content  # First file in the archive

        # Test missing file error
        with pytest.raises(KeyError):
            read_text_from_zip_archive(zip_path, "nonexistent.txt")


def test_error_handling_and_exit():
    """Test error handling and exit functionality."""
    with tempfile.TemporaryDirectory() as tmp_dir:
        config_dir = Path(tmp_dir)

        # Test validation error exit
        validation_error_config = config_dir / "validation_error.toml"
        validation_error_config.write_text("""
name = "test"
count = -10
""")

        with patch("sys.exit") as mock_exit, patch("mm_print.plain") as mock_print:
            SimpleTestConfig.read_toml_config_or_exit(validation_error_config)
            mock_exit.assert_called_with(1)
            mock_print.assert_called()
            # Check that validation error messages were printed
            print_calls = [call[0][0] for call in mock_print.call_args_list]
            assert any("config validation errors" in call for call in print_calls)

        # Test file error exit
        missing_file = config_dir / "missing.toml"
        with patch("sys.exit") as mock_exit, patch("mm_print.plain") as mock_print:
            SimpleTestConfig.read_toml_config_or_exit(missing_file)
            mock_exit.assert_called_with(1)
            mock_print.assert_called()


def test_print_and_exit():
    """Test the print_and_exit method functionality."""
    config = SimpleTestConfig(name="test", count=42, enabled=True)

    with patch("sys.exit") as mock_exit, patch("mm_print.json") as mock_print_json:
        config.print_and_exit()
        mock_exit.assert_called_with(0)
        mock_print_json.assert_called_once()
        printed_data = mock_print_json.call_args[0][0]
        assert printed_data["name"] == "test"
        assert printed_data["count"] == 42
        assert printed_data["enabled"] is True

    # Test with exclude and count parameters
    with patch("sys.exit") as mock_exit, patch("mm_print.json") as mock_print_json:
        config.print_and_exit(exclude={"enabled"}, count={"name"})
        printed_data = mock_print_json.call_args[0][0]
        assert "enabled" not in printed_data
        assert printed_data["name"] == len("test")  # Length instead of value
        assert printed_data["count"] == 42  # Not in count set, so original value
