"""Tests for the AWS skill."""

from datetime import datetime
from unittest.mock import Mock, patch

import pytest
from oclawma.skills import SkillMetadata

from oclawma_skill_aws import AwsSkill


@pytest.fixture
def metadata():
    """Create test metadata."""
    return SkillMetadata(
        name="aws",
        version="1.0.0",
        description="Test skill",
        author="Test",
        entry_point="test"
    )


@pytest.fixture
def skill(metadata):
    """Create a test skill instance."""
    return AwsSkill(metadata)


class TestAwsSkill:
    """Test suite for AwsSkill."""

    def test_initialization(self, skill, metadata):
        """Test skill initialization."""
        assert skill.metadata == metadata
        assert skill._clients == {}
        assert skill._default_region == "us-east-1"

    @patch("boto3.client")
    def test_get_client_caches_result(self, mock_boto_client, skill):
        """Test client is cached."""
        mock_client = Mock()
        mock_boto_client.return_value = mock_client

        # First call
        client1 = skill._get_client("ec2", "us-east-1")
        assert client1 == mock_client

        # Second call should return cached client
        client2 = skill._get_client("ec2", "us-east-1")
        assert client2 == mock_client

        # boto3.client should only be called once
        assert mock_boto_client.call_count == 1

    def test_handle_error(self, skill):
        """Test error handling."""
        from botocore.exceptions import ClientError

        error_response = {
            "Error": {
                "Code": "NoSuchBucket",
                "Message": "The specified bucket does not exist"
            }
        }
        error = ClientError(error_response, "ListObjects")

        result = skill._handle_error(error)

        assert result["success"] is False
        assert "NoSuchBucket" in result["error"]
        assert "does not exist" in result["error"]

    def test_serialize_datetime(self, skill):
        """Test datetime serialization."""
        now = datetime(2024, 1, 15, 10, 30, 0)
        result = skill._serialize_datetime(now)
        assert result == "2024-01-15T10:30:00"

    @pytest.mark.asyncio
    async def test_ec2_list_instances(self, skill):
        """Test ec2_list_instances tool."""
        mock_ec2 = Mock()
        mock_ec2.describe_instances.return_value = {
            "Reservations": [
                {
                    "Instances": [
                        {
                            "InstanceId": "i-1234567890",
                            "InstanceType": "t2.micro",
                            "State": {"Name": "running"},
                            "PublicIpAddress": "1.2.3.4",
                            "Tags": [{"Key": "Name", "Value": "test-instance"}]
                        }
                    ]
                }
            ]
        }

        with patch.object(skill, "_get_client", return_value=mock_ec2):
            result = await skill._ec2_list_instances(region="us-east-1")

        assert result["success"] is True
        assert result["count"] == 1
        assert result["data"]["instances"][0]["InstanceId"] == "i-1234567890"

    @pytest.mark.asyncio
    async def test_ec2_start_instance(self, skill):
        """Test ec2_start_instance tool."""
        mock_ec2 = Mock()
        mock_ec2.start_instances.return_value = {
            "StartingInstances": [{"InstanceId": "i-1234567890"}]
        }

        with patch.object(skill, "_get_client", return_value=mock_ec2):
            result = await skill._ec2_start_instance(
                instance_id="i-1234567890",
                region="us-east-1"
            )

        assert result["success"] is True
        assert "Starting instance" in result["output"]

    @pytest.mark.asyncio
    async def test_ec2_stop_instance(self, skill):
        """Test ec2_stop_instance tool."""
        mock_ec2 = Mock()
        mock_ec2.stop_instances.return_value = {
            "StoppingInstances": [{"InstanceId": "i-1234567890"}]
        }

        with patch.object(skill, "_get_client", return_value=mock_ec2):
            result = await skill._ec2_stop_instance(
                instance_id="i-1234567890",
                region="us-east-1",
                force=False
            )

        assert result["success"] is True
        assert "Stopping instance" in result["output"]

    @pytest.mark.asyncio
    async def test_s3_list_buckets(self, skill):
        """Test s3_list_buckets tool."""
        mock_s3 = Mock()
        mock_s3.list_buckets.return_value = {
            "Buckets": [
                {"Name": "bucket1", "CreationDate": datetime(2024, 1, 1)},
                {"Name": "bucket2", "CreationDate": datetime(2024, 1, 2)}
            ]
        }

        with patch.object(skill, "_get_client", return_value=mock_s3):
            result = await skill._s3_list_buckets()

        assert result["success"] is True
        assert result["count"] == 2
        assert result["data"]["buckets"][0]["Name"] == "bucket1"

    @pytest.mark.asyncio
    async def test_s3_list_objects(self, skill):
        """Test s3_list_objects tool."""
        mock_s3 = Mock()
        mock_s3.list_objects_v2.return_value = {
            "Contents": [
                {
                    "Key": "test/file1.txt",
                    "Size": 100,
                    "LastModified": datetime(2024, 1, 1),
                    "StorageClass": "STANDARD"
                }
            ]
        }

        with patch.object(skill, "_get_client", return_value=mock_s3):
            result = await skill._s3_list_objects(bucket="my-bucket", prefix="test/")

        assert result["success"] is True
        assert result["count"] == 1
        assert result["data"]["objects"][0]["Key"] == "test/file1.txt"

    @pytest.mark.asyncio
    async def test_s3_upload(self, skill):
        """Test s3_upload tool."""
        mock_s3 = Mock()

        with patch.object(skill, "_get_client", return_value=mock_s3):
            result = await skill._s3_upload(
                bucket="my-bucket",
                key="uploads/file.txt",
                file_path="/local/file.txt",
                metadata={"author": "test"}
            )

        assert result["success"] is True
        assert "Uploaded" in result["output"]
        mock_s3.upload_file.assert_called_once()

    @pytest.mark.asyncio
    async def test_s3_download(self, skill):
        """Test s3_download tool."""
        mock_s3 = Mock()

        with patch.object(skill, "_get_client", return_value=mock_s3):
            result = await skill._s3_download(
                bucket="my-bucket",
                key="file.txt",
                file_path="/local/download.txt"
            )

        assert result["success"] is True
        assert "Downloaded" in result["output"]
        mock_s3.download_file.assert_called_once()

    @pytest.mark.asyncio
    async def test_s3_delete_object(self, skill):
        """Test s3_delete_object tool."""
        mock_s3 = Mock()

        with patch.object(skill, "_get_client", return_value=mock_s3):
            result = await skill._s3_delete_object(bucket="my-bucket", key="file.txt")

        assert result["success"] is True
        assert "Deleted" in result["output"]
        mock_s3.delete_object.assert_called_once()

    @pytest.mark.asyncio
    async def test_lambda_list_functions(self, skill):
        """Test lambda_list_functions tool."""
        mock_lambda = Mock()
        mock_lambda.list_functions.return_value = {
            "Functions": [
                {
                    "FunctionName": "test-function",
                    "Runtime": "python3.11",
                    "State": "Active",
                    "MemorySize": 128,
                    "Timeout": 30
                }
            ]
        }

        with patch.object(skill, "_get_client", return_value=mock_lambda):
            result = await skill._lambda_list_functions(region="us-east-1")

        assert result["success"] is True
        assert result["count"] == 1
        assert result["data"]["functions"][0]["FunctionName"] == "test-function"

    @pytest.mark.asyncio
    async def test_lambda_invoke(self, skill):
        """Test lambda_invoke tool."""
        mock_lambda = Mock()
        mock_response = Mock()
        mock_response.read.return_value = b'{"statusCode": 200}'
        mock_lambda.invoke.return_value = {
            "StatusCode": 200,
            "Payload": mock_response
        }

        with patch.object(skill, "_get_client", return_value=mock_lambda):
            result = await skill._lambda_invoke(
                function_name="test-function",
                payload={"key": "value"},
                region="us-east-1"
            )

        assert result["success"] is True
        assert result["data"]["status_code"] == 200

    @pytest.mark.asyncio
    async def test_iam_list_users(self, skill):
        """Test iam_list_users tool."""
        mock_iam = Mock()
        mock_iam.list_users.return_value = {
            "Users": [
                {
                    "UserName": "test-user",
                    "UserId": "AID123",
                    "Arn": "arn:aws:iam::123:user/test-user",
                    "CreateDate": datetime(2024, 1, 1)
                }
            ]
        }

        with patch.object(skill, "_get_client", return_value=mock_iam):
            result = await skill._iam_list_users()

        assert result["success"] is True
        assert result["count"] == 1
        assert result["data"]["users"][0]["UserName"] == "test-user"

    @pytest.mark.asyncio
    async def test_iam_list_roles(self, skill):
        """Test iam_list_roles tool."""
        mock_iam = Mock()
        mock_iam.list_roles.return_value = {
            "Roles": [
                {
                    "RoleName": "test-role",
                    "RoleId": "AROA123",
                    "Arn": "arn:aws:iam::123:role/test-role",
                    "CreateDate": datetime(2024, 1, 1)
                }
            ]
        }

        with patch.object(skill, "_get_client", return_value=mock_iam):
            result = await skill._iam_list_roles()

        assert result["success"] is True
        assert result["count"] == 1
        assert result["data"]["roles"][0]["RoleName"] == "test-role"

    @pytest.mark.asyncio
    async def test_sts_get_caller_identity(self, skill):
        """Test sts_get_caller_identity tool."""
        mock_sts = Mock()
        mock_sts.get_caller_identity.return_value = {
            "Account": "123456789",
            "Arn": "arn:aws:sts::123456789:assumed-role/admin",
            "UserId": "AROA123:user"
        }

        with patch.object(skill, "_get_client", return_value=mock_sts):
            result = await skill._sts_get_caller_identity()

        assert result["success"] is True
        assert result["data"]["Account"] == "123456789"

    def test_load_registers_tools(self, skill):
        """Test that _load registers all expected tools."""
        skill._load()

        expected_tools = [
            "ec2_list_instances",
            "ec2_start_instance",
            "ec2_stop_instance",
            "ec2_describe_instance",
            "s3_list_buckets",
            "s3_list_objects",
            "s3_upload",
            "s3_download",
            "s3_delete_object",
            "lambda_list_functions",
            "lambda_invoke",
            "lambda_get_logs",
            "cloudwatch_get_metrics",
            "iam_list_users",
            "iam_list_roles",
            "rds_describe_instances",
            "route53_list_hosted_zones",
            "sts_get_caller_identity",
        ]

        for tool in expected_tools:
            assert tool in skill._tools, f"Tool {tool} not registered"
