"""Tests for S3 Upload node."""

from unittest.mock import MagicMock, patch

import pytest

from fw_nodes_core.nodes.s3_upload import S3UploadNode


@pytest.fixture
def s3_context(node_execution_context):
    """Context with S3 credentials."""
    node_execution_context._credentials = {
        "cred_s3": {
            "access_key": "AKIAIOSFODNN7EXAMPLE",
            "secret_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
            "region": "us-west-2",
        }
    }
    return node_execution_context


@pytest.fixture
def mock_boto3():
    """Mock boto3 for S3 tests (imported inside execute_logic)."""
    with patch.dict("sys.modules", {"boto3": MagicMock()}) as _:
        import boto3

        mock_client = MagicMock()
        boto3.client.return_value = mock_client
        yield boto3, mock_client


@pytest.mark.asyncio
async def test_basic_upload(s3_context, mock_boto3):
    """Test basic S3 upload with string content."""
    _, mock_client = mock_boto3
    node = S3UploadNode()

    inputs = {
        "credential_id": "cred_s3",
        "file_content": "hello world",
        "bucket": "my-bucket",
        "key": "data/output.txt",
        "content_type": "text/plain",
    }
    result = await node.execute(inputs, s3_context)

    assert result.uploaded is True
    assert result.bucket == "my-bucket"
    assert result.key == "data/output.txt"
    assert result.uri == "s3://my-bucket/data/output.txt"
    assert result.size_bytes == len(b"hello world")

    mock_client.put_object.assert_called_once()
    call_kwargs = mock_client.put_object.call_args[1]
    assert call_kwargs["ContentType"] == "text/plain"


@pytest.mark.asyncio
async def test_content_type_respected(s3_context, mock_boto3):
    """Test that the input content_type is used, not a hardcoded default."""
    _, mock_client = mock_boto3
    node = S3UploadNode()

    inputs = {
        "credential_id": "cred_s3",
        "file_content": '{"key": "value"}',
        "bucket": "my-bucket",
        "key": "data.json",
        "content_type": "application/json",
    }
    await node.execute(inputs, s3_context)

    call_kwargs = mock_client.put_object.call_args[1]
    assert call_kwargs["ContentType"] == "application/json"


@pytest.mark.asyncio
async def test_empty_content_raises(s3_context):
    """Test that empty file content raises ValueError."""
    node = S3UploadNode()

    inputs = {
        "credential_id": "cred_s3",
        "file_content": "",
        "bucket": "my-bucket",
        "key": "empty.txt",
    }

    with pytest.raises(ValueError, match="File content is required"):
        await node.execute(inputs, s3_context)


@pytest.mark.asyncio
async def test_missing_credential_raises(node_execution_context):
    """Test that missing credential raises."""
    node = S3UploadNode()

    inputs = {
        "credential_id": "nonexistent",
        "file_content": "data",
        "bucket": "bucket",
        "key": "key",
    }

    with pytest.raises(ValueError, match="Credential nonexistent not found"):
        await node.execute(inputs, node_execution_context)


@pytest.mark.asyncio
async def test_client_error_raises(s3_context, mock_boto3):
    """Test that S3 client errors are wrapped in RuntimeError."""
    _, mock_client = mock_boto3
    from botocore.exceptions import ClientError

    mock_client.put_object.side_effect = ClientError(
        {"Error": {"Code": "AccessDenied", "Message": "Access Denied"}},
        "PutObject",
    )
    node = S3UploadNode()

    inputs = {
        "credential_id": "cred_s3",
        "file_content": "data",
        "bucket": "locked-bucket",
        "key": "file.txt",
    }

    with pytest.raises(RuntimeError, match="S3 upload failed.*AccessDenied"):
        await node.execute(inputs, s3_context)
