#!/usr/bin/env python3
"""Functional tests for build_service operations in BlackAntDockerClient."""

import unittest
import tempfile
import os
from unittest.mock import Mock, patch, MagicMock
from pathlib import Path

import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'src'))

from blackant.auth.blackant_auth import BlackAntAuth
from blackant.docker.client import BlackAntDockerClient
from blackant.exceptions import BlackAntDockerError


class TestBuildServiceOperations(unittest.TestCase):
    """Test complete build_service operations workflow."""

    def setUp(self):
        """Set up test environment."""
        # Mock auth
        self.mock_auth = Mock(spec=BlackAntAuth)
        self.mock_auth.token_store = Mock()
        self.mock_auth.get_token.return_value = "test_token"

        # Mock HTTP client
        self.mock_http_client = Mock()
        self.mock_http_response = Mock()
        self.mock_http_response.status_code = 200
        self.mock_http_response.json.return_value = {"uuid": "service-123-456"}

        # Mock Docker image builder
        self.mock_image_builder = Mock()

        # Patch HTTP client
        self.http_patch = patch('blackant.docker.client.HTTPClient')
        self.mock_http_client_class = self.http_patch.start()
        self.mock_http_client_class.return_value = self.mock_http_client

        # Patch image builder
        self.builder_patch = patch('blackant.docker.client.DockerImageBuilder')
        self.mock_builder_class = self.builder_patch.start()
        self.mock_builder_class.return_value = self.mock_image_builder

        # Create test environment
        self.test_dir = tempfile.mkdtemp()
        self.impl_path = os.path.join(self.test_dir, "impl")
        self.dockerfile_path = os.path.join(self.test_dir, "Dockerfile")

        os.makedirs(self.impl_path)

        # Create test implementation files
        with open(os.path.join(self.impl_path, "calculation.py"), "w") as f:
            f.write("""
import time
import json

def calculate(data):
    # Simulate calculation
    time.sleep(1)
    result = sum(data.get('numbers', []))
    return {'result': result, 'status': 'completed'}

if __name__ == '__main__':
    # Test calculation
    test_data = {'numbers': [1, 2, 3, 4, 5]}
    print(json.dumps(calculate(test_data)))
""")

        with open(os.path.join(self.impl_path, "requirements.txt"), "w") as f:
            f.write("requests==2.28.1\\n")

        # Create test Dockerfile
        with open(self.dockerfile_path, "w") as f:
            f.write("""FROM python:3.9-slim
WORKDIR /app
COPY impl/ /app/
RUN pip install -r requirements.txt
CMD ["python", "calculation.py"]
""")

    def tearDown(self):
        """Clean up patches and test files."""
        self.http_patch.stop()
        self.builder_patch.stop()

        # Clean up test directory
        import shutil
        shutil.rmtree(self.test_dir, ignore_errors=True)

    def test_build_service_complete_workflow(self):
        """Test complete build_service workflow with all steps."""
        # Setup mock responses
        build_result = {
            "image_name": "env.blackant.app/systemdevelopers/test-calc",
            "tag": "v1.0.0-20250105-123456",
            "full_image": "env.blackant.app/systemdevelopers/test-calc:v1.0.0-20250105-123456",
            "image_id": "sha256:abcdef123456789",
            "size": 125000000,
            "pushed": True,
            "registry_url": "env.blackant.app",
            "created": "2025-01-05T12:34:56Z",
            "labels": {"blackant.service.name": "test-calc"}
        }

        self.mock_image_builder.build_service.return_value = build_result
        self.mock_http_client.send_request.return_value = self.mock_http_response

        # Create client and execute build_service
        client = BlackAntDockerClient(auth=self.mock_auth)
        result = client.build_service(
            service_name="test-calc",
            impl_path=self.impl_path,
            dockerfile_path=self.dockerfile_path,
            tag="v1.0.0-20250105-123456",
            push=True,
            register_service=True
        )

        # Verify image builder was called correctly
        self.mock_image_builder.build_service.assert_called_once_with(
            service_name="test-calc",
            impl_path=self.impl_path,
            dockerfile_path=self.dockerfile_path,
            tag="v1.0.0-20250105-123456",
            push_to_registry=True
        )

        # Verify service registration was called
        self.mock_http_client.send_request.assert_called_once()
        call_args = self.mock_http_client.send_request.call_args

        self.assertEqual(call_args[1]["endpoint"], "/api/v1/services")
        self.assertEqual(call_args[1]["method"], "POST")

        # Verify service config structure
        service_config = call_args[1]["json"]
        self.assertEqual(service_config["name"], "test-calc")
        self.assertEqual(service_config["type"], "calculation")
        self.assertEqual(service_config["image"]["container"], "systemdevelopers")
        self.assertEqual(service_config["image"]["name"], "test-calc")
        self.assertEqual(service_config["image"]["tag"], "v1.0.0-20250105-123456")

        # Verify complete result
        self.assertEqual(result["service_id"], "service-123-456")
        self.assertTrue(result["service_registered"])
        self.assertTrue(result["build_success"])
        self.assertEqual(result["full_image"], build_result["full_image"])

    def test_build_service_without_registration(self):
        """Test build_service without service registration."""
        build_result = {
            "image_name": "env.blackant.app/systemdevelopers/no-reg-calc",
            "tag": "v1.0.0",
            "full_image": "env.blackant.app/systemdevelopers/no-reg-calc:v1.0.0",
            "image_id": "sha256:noreg123456",
            "size": 100000000,
            "pushed": True,
            "registry_url": "env.blackant.app",
            "created": "2025-01-05T12:00:00Z",
            "labels": {}
        }

        self.mock_image_builder.build_service.return_value = build_result

        client = BlackAntDockerClient(auth=self.mock_auth)
        result = client.build_service(
            service_name="no-reg-calc",
            impl_path=self.impl_path,
            dockerfile_path=self.dockerfile_path,
            register_service=False
        )

        # Verify no service registration call
        self.mock_http_client.send_request.assert_not_called()

        # Verify result
        self.assertIsNone(result["service_id"])
        self.assertFalse(result["service_registered"])
        self.assertTrue(result["build_success"])

    def test_build_service_without_push(self):
        """Test build_service without pushing to registry."""
        build_result = {
            "image_name": "env.blackant.app/systemdevelopers/local-calc",
            "tag": "v1.0.0",
            "full_image": "env.blackant.app/systemdevelopers/local-calc:v1.0.0",
            "image_id": "sha256:local123456",
            "size": 75000000,
            "pushed": False,  # Not pushed
            "registry_url": "env.blackant.app",
            "created": "2025-01-05T12:00:00Z",
            "labels": {}
        }

        self.mock_image_builder.build_service.return_value = build_result

        client = BlackAntDockerClient(auth=self.mock_auth)
        result = client.build_service(
            service_name="local-calc",
            impl_path=self.impl_path,
            dockerfile_path=self.dockerfile_path,
            push=False,
            register_service=True  # Should not register because push=False
        )

        # Verify no service registration (because image wasn't pushed)
        self.mock_http_client.send_request.assert_not_called()

        # Verify result
        self.assertIsNone(result["service_id"])
        self.assertFalse(result["service_registered"])
        self.assertFalse(result["pushed"])

    def test_build_service_registration_failure(self):
        """Test build_service with service registration failure."""
        build_result = {
            "image_name": "env.blackant.app/systemdevelopers/fail-reg-calc",
            "tag": "v1.0.0",
            "full_image": "env.blackant.app/systemdevelopers/fail-reg-calc:v1.0.0",
            "image_id": "sha256:failreg123456",
            "size": 90000000,
            "pushed": True,
            "registry_url": "env.blackant.app",
            "created": "2025-01-05T12:00:00Z",
            "labels": {}
        }

        self.mock_image_builder.build_service.return_value = build_result

        # Mock failed service registration
        self.mock_http_response.status_code = 400
        self.mock_http_response.text = "Service name already exists"

        client = BlackAntDockerClient(auth=self.mock_auth)
        result = client.build_service(
            service_name="fail-reg-calc",
            impl_path=self.impl_path,
            dockerfile_path=self.dockerfile_path,
            push=True,
            register_service=True
        )

        # Verify service registration was attempted
        self.mock_http_client.send_request.assert_called_once()

        # Build should still succeed even if registration fails
        self.assertIsNone(result["service_id"])
        self.assertFalse(result["service_registered"])
        self.assertTrue(result["build_success"])
        self.assertTrue(result["pushed"])

    def test_build_service_build_failure(self):
        """Test build_service with Docker build failure."""
        # Mock build failure
        self.mock_image_builder.build_service.side_effect = BlackAntDockerError("Build failed: Dockerfile syntax error")

        client = BlackAntDockerClient(auth=self.mock_auth)

        with self.assertRaises(BlackAntDockerError) as context:
            client.build_service(
                service_name="fail-build-calc",
                impl_path=self.impl_path,
                dockerfile_path=self.dockerfile_path
            )

        self.assertIn("Build service failed", str(context.exception))
        self.assertIn("fail-build-calc", str(context.exception))

        # Verify no service registration attempted
        self.mock_http_client.send_request.assert_not_called()

    def test_build_service_with_custom_environment(self):
        """Test build_service verifying custom environment variables are set."""
        build_result = {
            "image_name": "env.blackant.app/systemdevelopers/env-calc",
            "tag": "v2.0.0",
            "full_image": "env.blackant.app/systemdevelopers/env-calc:v2.0.0",
            "image_id": "sha256:env123456",
            "size": 110000000,
            "pushed": True,
            "registry_url": "env.blackant.app",
            "created": "2025-01-05T12:00:00Z",
            "labels": {}
        }

        self.mock_image_builder.build_service.return_value = build_result
        self.mock_http_client.send_request.return_value = self.mock_http_response

        client = BlackAntDockerClient(auth=self.mock_auth)
        result = client.build_service(
            service_name="env-calc",
            impl_path=self.impl_path,
            dockerfile_path=self.dockerfile_path,
            tag="v2.0.0",
            push=True,
            register_service=True
        )

        # Verify service registration call and check environment variables
        call_args = self.mock_http_client.send_request.call_args
        service_config = call_args[1]["json"]

        expected_env = {
            "BLACKANT_ENV": "production",
            "LOG_LEVEL": "INFO",
            "SERVICE_NAME": "env-calc"
        }

        self.assertEqual(service_config["environments"], expected_env)

    def test_build_service_with_auto_tag_generation(self):
        """Test build_service with automatic tag generation."""
        build_result = {
            "image_name": "env.blackant.app/systemdevelopers/auto-tag-calc",
            "tag": "v1.0.0-20250105-143022",  # Auto-generated timestamp tag
            "full_image": "env.blackant.app/systemdevelopers/auto-tag-calc:v1.0.0-20250105-143022",
            "image_id": "sha256:autotag123456",
            "size": 95000000,
            "pushed": True,
            "registry_url": "env.blackant.app",
            "created": "2025-01-05T14:30:22Z",
            "labels": {}
        }

        self.mock_image_builder.build_service.return_value = build_result
        self.mock_http_client.send_request.return_value = self.mock_http_response

        client = BlackAntDockerClient(auth=self.mock_auth)
        result = client.build_service(
            service_name="auto-tag-calc",
            impl_path=self.impl_path,
            dockerfile_path=self.dockerfile_path,
            tag=None,  # Auto-generate tag
            push=True,
            register_service=True
        )

        # Verify auto-generated tag was passed to image builder
        self.mock_image_builder.build_service.assert_called_once()
        call_args = self.mock_image_builder.build_service.call_args
        self.assertIsNone(call_args[1]["tag"])  # None was passed, builder generates tag

        # Verify result contains auto-generated tag
        self.assertTrue(result["tag"].startswith("v1.0.0-"))
        self.assertEqual(result["tag"], "v1.0.0-20250105-143022")

    def test_build_service_resource_configuration(self):
        """Test build_service resource configuration in service registration."""
        build_result = {
            "image_name": "env.blackant.app/systemdevelopers/resource-calc",
            "tag": "v1.0.0",
            "full_image": "env.blackant.app/systemdevelopers/resource-calc:v1.0.0",
            "image_id": "sha256:resource123456",
            "size": 105000000,
            "pushed": True,
            "registry_url": "env.blackant.app",
            "created": "2025-01-05T12:00:00Z",
            "labels": {}
        }

        self.mock_image_builder.build_service.return_value = build_result
        self.mock_http_client.send_request.return_value = self.mock_http_response

        client = BlackAntDockerClient(auth=self.mock_auth)
        result = client.build_service(
            service_name="resource-calc",
            impl_path=self.impl_path,
            dockerfile_path=self.dockerfile_path,
            push=True,
            register_service=True
        )

        # Verify resource configuration
        call_args = self.mock_http_client.send_request.call_args
        service_config = call_args[1]["json"]

        expected_resources = {
            "use_gpu": False,
            "cpu_limit": 1.0,
            "ram_limit": "512M",
            "disk_limit": "1G",
            "node_label": None
        }

        self.assertEqual(service_config["resource_config"], expected_resources)
        self.assertEqual(service_config["networks"], ["blackant_network"])
        self.assertIsNone(service_config["parent"])

    def test_build_service_http_client_integration(self):
        """Test build_service HTTPClient integration and error handling."""
        build_result = {
            "image_name": "env.blackant.app/systemdevelopers/http-test-calc",
            "tag": "v1.0.0",
            "full_image": "env.blackant.app/systemdevelopers/http-test-calc:v1.0.0",
            "image_id": "sha256:httptest123456",
            "size": 88000000,
            "pushed": True,
            "registry_url": "env.blackant.app",
            "created": "2025-01-05T12:00:00Z",
            "labels": {}
        }

        self.mock_image_builder.build_service.return_value = build_result

        # Test HTTP client exception handling
        self.mock_http_client.send_request.side_effect = Exception("Network error")

        client = BlackAntDockerClient(auth=self.mock_auth)
        result = client.build_service(
            service_name="http-test-calc",
            impl_path=self.impl_path,
            dockerfile_path=self.dockerfile_path,
            push=True,
            register_service=True
        )

        # Build should succeed, but registration should fail gracefully
        self.assertTrue(result["build_success"])
        self.assertTrue(result["pushed"])
        self.assertFalse(result["service_registered"])
        self.assertIsNone(result["service_id"])


if __name__ == '__main__':
    unittest.main(verbosity=2)