"""
Unit tests for Phase 6: API Layer components.

Tests cover:
- GraphQL space schema types
- CLI space commands
"""

import pytest
import tempfile
import json
from pathlib import Path
from unittest.mock import Mock, MagicMock, patch
from click.testing import CliRunner

from markitect.graphql.space_schema import (
    InformationSpaceType,
    SpaceDocumentType,
    SpaceVariableType,
    SpaceConfigType,
    SpaceMetadataType,
    RenderResultType,
    SyncResultType,
    SpaceQuery,
    SpaceMutation,
    CreateSpaceInput,
    UpdateSpaceInput,
    AddDocumentInput,
    CreateSpacePayload,
    UpdateSpacePayload,
    DeleteSpacePayload,
    AddDocumentPayload,
    SpaceStatusEnum,
    SyncDirectionEnum,
    ConflictResolutionEnum,
)
from markitect.spaces.cli import (
    space,
    create_space,
    list_spaces,
    show_space,
    delete_space,
    add_document,
    list_documents,
    set_variable,
    list_variables,
    export_space,
    sync_space,
)
from markitect.spaces.models import (
    InformationSpace,
    SpaceDocument,
    SpaceVariable,
    SpaceConfig,
    SpaceMetadata,
    SpaceStatus,
)


class TestSpaceStatusEnum:
    """Tests for SpaceStatusEnum."""

    def test_status_values(self):
        """Test all status values are defined."""
        assert SpaceStatusEnum.DRAFT
        assert SpaceStatusEnum.ACTIVE
        assert SpaceStatusEnum.ARCHIVED
        assert SpaceStatusEnum.DELETED


class TestSyncDirectionEnum:
    """Tests for SyncDirectionEnum."""

    def test_direction_values(self):
        """Test all direction values are defined."""
        assert SyncDirectionEnum.SPACE_TO_DIRECTORY
        assert SyncDirectionEnum.DIRECTORY_TO_SPACE
        assert SyncDirectionEnum.BIDIRECTIONAL


class TestConflictResolutionEnum:
    """Tests for ConflictResolutionEnum."""

    def test_resolution_values(self):
        """Test all resolution values are defined."""
        assert ConflictResolutionEnum.SPACE_WINS
        assert ConflictResolutionEnum.DIRECTORY_WINS
        assert ConflictResolutionEnum.NEWER_WINS
        assert ConflictResolutionEnum.SKIP


class TestInformationSpaceType:
    """Tests for InformationSpaceType GraphQL type."""

    def test_type_has_required_fields(self):
        """Test that type has required fields."""
        # Check field definitions exist on the type
        assert hasattr(InformationSpaceType, 'id')
        assert hasattr(InformationSpaceType, 'name')
        assert hasattr(InformationSpaceType, 'description')
        assert hasattr(InformationSpaceType, 'status')
        assert hasattr(InformationSpaceType, 'config')
        assert hasattr(InformationSpaceType, 'metadata')


class TestSpaceDocumentType:
    """Tests for SpaceDocumentType GraphQL type."""

    def test_type_has_required_fields(self):
        """Test that type has required fields."""
        assert hasattr(SpaceDocumentType, 'id')
        assert hasattr(SpaceDocumentType, 'space_id')
        assert hasattr(SpaceDocumentType, 'document_id')
        assert hasattr(SpaceDocumentType, 'space_path')


class TestSpaceVariableType:
    """Tests for SpaceVariableType GraphQL type."""

    def test_type_has_required_fields(self):
        """Test that type has required fields."""
        assert hasattr(SpaceVariableType, 'space_id')
        assert hasattr(SpaceVariableType, 'name')
        assert hasattr(SpaceVariableType, 'value')
        assert hasattr(SpaceVariableType, 'scope')


class TestSpaceConfigType:
    """Tests for SpaceConfigType GraphQL type."""

    def test_type_has_required_fields(self):
        """Test that type has required fields."""
        assert hasattr(SpaceConfigType, 'default_variant')
        assert hasattr(SpaceConfigType, 'enable_caching')
        assert hasattr(SpaceConfigType, 'theme')


class TestRenderResultType:
    """Tests for RenderResultType GraphQL type."""

    def test_type_has_required_fields(self):
        """Test that type has required fields."""
        assert hasattr(RenderResultType, 'content')
        assert hasattr(RenderResultType, 'format')
        assert hasattr(RenderResultType, 'content_hash')


class TestSyncResultType:
    """Tests for SyncResultType GraphQL type."""

    def test_type_has_required_fields(self):
        """Test that type has required fields."""
        assert hasattr(SyncResultType, 'success')
        assert hasattr(SyncResultType, 'created_count')
        assert hasattr(SyncResultType, 'updated_count')
        assert hasattr(SyncResultType, 'conflict_count')


class TestSpaceQuery:
    """Tests for SpaceQuery GraphQL type."""

    def test_query_has_space_field(self):
        """Test that query has space field."""
        assert hasattr(SpaceQuery, 'space')

    def test_query_has_spaces_field(self):
        """Test that query has spaces field."""
        assert hasattr(SpaceQuery, 'spaces')

    def test_query_has_document_fields(self):
        """Test that query has document fields."""
        assert hasattr(SpaceQuery, 'space_documents')
        assert hasattr(SpaceQuery, 'space_document')

    def test_query_has_variable_fields(self):
        """Test that query has variable fields."""
        assert hasattr(SpaceQuery, 'space_variables')
        assert hasattr(SpaceQuery, 'space_variable')


class TestSpaceMutation:
    """Tests for SpaceMutation GraphQL type."""

    def test_mutation_has_space_lifecycle(self):
        """Test mutation has space lifecycle operations."""
        assert hasattr(SpaceMutation, 'create_space')
        assert hasattr(SpaceMutation, 'update_space')
        assert hasattr(SpaceMutation, 'delete_space')
        assert hasattr(SpaceMutation, 'activate_space')
        assert hasattr(SpaceMutation, 'archive_space')

    def test_mutation_has_document_operations(self):
        """Test mutation has document operations."""
        assert hasattr(SpaceMutation, 'add_document')
        assert hasattr(SpaceMutation, 'remove_document')

    def test_mutation_has_variable_operations(self):
        """Test mutation has variable operations."""
        assert hasattr(SpaceMutation, 'set_variable')
        assert hasattr(SpaceMutation, 'delete_variable')

    def test_mutation_has_render_operations(self):
        """Test mutation has render operations."""
        assert hasattr(SpaceMutation, 'render_document')
        assert hasattr(SpaceMutation, 'render_space')

    def test_mutation_has_sync_operations(self):
        """Test mutation has sync operations."""
        assert hasattr(SpaceMutation, 'sync_space')
        assert hasattr(SpaceMutation, 'export_space')
        assert hasattr(SpaceMutation, 'import_directory')


class TestPayloadTypes:
    """Tests for payload types."""

    def test_create_space_payload(self):
        """Test CreateSpacePayload type."""
        assert hasattr(CreateSpacePayload, 'space')
        assert hasattr(CreateSpacePayload, 'success')
        assert hasattr(CreateSpacePayload, 'errors')

    def test_update_space_payload(self):
        """Test UpdateSpacePayload type."""
        assert hasattr(UpdateSpacePayload, 'space')
        assert hasattr(UpdateSpacePayload, 'success')
        assert hasattr(UpdateSpacePayload, 'errors')

    def test_delete_space_payload(self):
        """Test DeleteSpacePayload type."""
        assert hasattr(DeleteSpacePayload, 'success')
        assert hasattr(DeleteSpacePayload, 'deleted_id')
        assert hasattr(DeleteSpacePayload, 'errors')

    def test_add_document_payload(self):
        """Test AddDocumentPayload type."""
        assert hasattr(AddDocumentPayload, 'document')
        assert hasattr(AddDocumentPayload, 'success')
        assert hasattr(AddDocumentPayload, 'errors')


# CLI Tests
class TestCLISpaceGroup:
    """Tests for CLI space command group."""

    def test_space_group_exists(self):
        """Test space command group exists."""
        assert space is not None
        assert hasattr(space, 'commands')

    def test_space_commands_registered(self):
        """Test that expected commands are registered."""
        commands = space.commands
        expected = [
            'create', 'list', 'show', 'delete',
            'activate', 'archive',
            'add-doc', 'remove-doc', 'list-docs',
            'set-var', 'get-var', 'list-vars',
            'render', 'export', 'import', 'sync',
            'invalidate-cache',
        ]
        for cmd in expected:
            assert cmd in commands, f"Command '{cmd}' not found in space group"


class TestCLICreateSpace:
    """Tests for CLI create space command."""

    @patch('markitect.spaces.cli.get_space_service')
    def test_create_space_basic(self, mock_get_service):
        """Test basic space creation."""
        mock_service = Mock()
        mock_space = InformationSpace(
            id="test-id",
            name="Test Space",
            status=SpaceStatus.DRAFT,
        )
        mock_service.create_space.return_value = mock_space
        mock_get_service.return_value = mock_service

        runner = CliRunner()
        result = runner.invoke(space, ['create', 'Test Space'])

        assert result.exit_code == 0
        assert 'Created space' in result.output

    @patch('markitect.spaces.cli.get_space_service')
    def test_create_space_with_options(self, mock_get_service):
        """Test space creation with options."""
        mock_service = Mock()
        mock_space = InformationSpace(
            id="test-id",
            name="Test Space",
            description="A test",
            status=SpaceStatus.DRAFT,
        )
        mock_service.create_space.return_value = mock_space
        mock_get_service.return_value = mock_service

        runner = CliRunner()
        result = runner.invoke(space, [
            'create', 'Test Space',
            '--description', 'A test',
            '--theme', 'github',
        ])

        assert result.exit_code == 0
        mock_service.create_space.assert_called_once()

    @patch('markitect.spaces.cli.get_space_service')
    def test_create_space_json_output(self, mock_get_service):
        """Test space creation with JSON output."""
        from datetime import datetime
        mock_service = Mock()
        mock_space = InformationSpace(
            id="test-id",
            name="Test Space",
            status=SpaceStatus.DRAFT,
            created_at=datetime.now(),
        )
        mock_service.create_space.return_value = mock_space
        mock_get_service.return_value = mock_service

        runner = CliRunner()
        result = runner.invoke(space, ['create', 'Test Space', '-j'])

        assert result.exit_code == 0
        data = json.loads(result.output)
        assert data['id'] == 'test-id'
        assert data['name'] == 'Test Space'


class TestCLIListSpaces:
    """Tests for CLI list spaces command."""

    @patch('markitect.spaces.cli.get_space_service')
    def test_list_spaces_empty(self, mock_get_service):
        """Test listing empty spaces."""
        mock_service = Mock()
        mock_service.list_spaces.return_value = []
        mock_get_service.return_value = mock_service

        runner = CliRunner()
        result = runner.invoke(space, ['list'])

        assert result.exit_code == 0
        assert 'No spaces found' in result.output

    @patch('markitect.spaces.cli.get_space_service')
    def test_list_spaces_with_results(self, mock_get_service):
        """Test listing spaces with results."""
        mock_service = Mock()
        mock_service.list_spaces.return_value = [
            InformationSpace(id="id-1", name="Space 1", status=SpaceStatus.ACTIVE),
            InformationSpace(id="id-2", name="Space 2", status=SpaceStatus.DRAFT),
        ]
        mock_get_service.return_value = mock_service

        runner = CliRunner()
        result = runner.invoke(space, ['list'])

        assert result.exit_code == 0
        assert 'Space 1' in result.output
        assert 'Space 2' in result.output


class TestCLIShowSpace:
    """Tests for CLI show space command."""

    @patch('markitect.spaces.cli.get_space_service')
    def test_show_space_not_found(self, mock_get_service):
        """Test showing non-existent space."""
        mock_service = Mock()
        mock_service.get_space.return_value = None
        mock_service.get_space_by_name.return_value = None
        mock_get_service.return_value = mock_service

        runner = CliRunner()
        result = runner.invoke(space, ['show', 'nonexistent'])

        assert result.exit_code == 1
        assert 'not found' in result.output

    @patch('markitect.spaces.cli.get_space_service')
    def test_show_space_found(self, mock_get_service):
        """Test showing existing space."""
        mock_service = Mock()
        mock_space = InformationSpace(
            id="test-id",
            name="Test Space",
            description="A test space",
            status=SpaceStatus.ACTIVE,
            config=SpaceConfig(),
        )
        mock_service.get_space.return_value = mock_space
        mock_service.list_documents.return_value = []
        mock_service.list_variables.return_value = []
        mock_get_service.return_value = mock_service

        runner = CliRunner()
        result = runner.invoke(space, ['show', 'test-id'])

        assert result.exit_code == 0
        assert 'Test Space' in result.output
        assert 'active' in result.output


class TestCLIDeleteSpace:
    """Tests for CLI delete space command."""

    @patch('markitect.spaces.cli.get_space_service')
    def test_delete_space_with_force(self, mock_get_service):
        """Test deleting space with force flag."""
        mock_service = Mock()
        mock_service.get_space.return_value = InformationSpace(
            id="test-id",
            name="Test Space",
            status=SpaceStatus.ACTIVE,
        )
        mock_get_service.return_value = mock_service

        runner = CliRunner()
        result = runner.invoke(space, ['delete', 'test-id', '-f'])

        assert result.exit_code == 0
        assert 'Deleted space' in result.output
        mock_service.delete_space.assert_called_once_with('test-id')


class TestCLIDocumentCommands:
    """Tests for CLI document commands."""

    @patch('markitect.spaces.cli.get_space_service')
    def test_add_document(self, mock_get_service):
        """Test adding a document."""
        mock_service = Mock()
        mock_doc = SpaceDocument(
            id="doc-id",
            space_id="space-id",
            document_id="doc-id",
            space_path="/test.md",
        )
        mock_service.add_document.return_value = mock_doc
        mock_get_service.return_value = mock_service

        runner = CliRunner()
        result = runner.invoke(space, [
            'add-doc', 'space-id',
            '--path', '/test.md',
            '--document-id', 'doc-id',
        ])

        assert result.exit_code == 0
        assert 'Added document' in result.output

    @patch('markitect.spaces.cli.get_space_service')
    def test_list_documents_empty(self, mock_get_service):
        """Test listing empty documents."""
        mock_service = Mock()
        mock_service.list_documents.return_value = []
        mock_get_service.return_value = mock_service

        runner = CliRunner()
        result = runner.invoke(space, ['list-docs', 'space-id'])

        assert result.exit_code == 0
        assert 'No documents' in result.output


class TestCLIVariableCommands:
    """Tests for CLI variable commands."""

    @patch('markitect.spaces.cli.get_space_service')
    def test_set_variable(self, mock_get_service):
        """Test setting a variable."""
        mock_service = Mock()
        mock_var = SpaceVariable(
            space_id="space-id",
            name="test_var",
            value="test_value",
            scope="space",
        )
        mock_service.set_variable.return_value = mock_var
        mock_get_service.return_value = mock_service

        runner = CliRunner()
        result = runner.invoke(space, [
            'set-var', 'space-id', 'test_var', 'test_value',
        ])

        assert result.exit_code == 0
        assert 'Set variable' in result.output

    @patch('markitect.spaces.cli.get_space_service')
    def test_list_variables_empty(self, mock_get_service):
        """Test listing empty variables."""
        mock_service = Mock()
        mock_service.list_variables.return_value = []
        mock_get_service.return_value = mock_service

        runner = CliRunner()
        result = runner.invoke(space, ['list-vars', 'space-id'])

        assert result.exit_code == 0
        assert 'No variables' in result.output


class TestCLISyncCommands:
    """Tests for CLI sync commands."""

    @patch('markitect.spaces.cli.get_space_service')
    def test_export_space_not_found(self, mock_get_service):
        """Test exporting non-existent space."""
        mock_service = Mock()
        mock_service.get_space.return_value = None
        mock_get_service.return_value = mock_service

        runner = CliRunner()
        with tempfile.TemporaryDirectory() as tmpdir:
            result = runner.invoke(space, ['export', 'nonexistent', tmpdir])

        assert result.exit_code == 1
        assert 'not found' in result.output

    @patch('markitect.spaces.cli.get_space_service')
    def test_sync_space_not_found(self, mock_get_service):
        """Test syncing non-existent space."""
        mock_service = Mock()
        mock_service.get_space.return_value = None
        mock_get_service.return_value = mock_service

        runner = CliRunner()
        with tempfile.TemporaryDirectory() as tmpdir:
            result = runner.invoke(space, ['sync', 'nonexistent', tmpdir])

        assert result.exit_code == 1
        assert 'not found' in result.output
