"""Unit tests for QueryOperations (Low-Level API)."""

from __future__ import annotations

from datetime import datetime
from uuid import UUID

import pytest

from questra_automation.models import (
    AutomationBuildStatus,
    AutomationExecutionStatus,
    AutomationInitiatorType,
    RepositoryAuthenticationMethod,
)
from questra_automation.operations import QueryOperations


@pytest.mark.unit
class TestQueryOperationsGetWorkspaces:
    """Tests für get_workspaces() Methode."""

    def test_get_workspaces_basic(self, mock_graphql_execute, workspaces_response):
        """Test get_workspaces ohne Filter."""
        mock_graphql_execute.return_value = workspaces_response

        queries = QueryOperations(mock_graphql_execute)
        workspaces = queries.get_workspaces(first=10)

        # Assertions
        assert workspaces.total_count == 3
        assert len(workspaces.nodes) == 3
        assert workspaces.page_info.has_next_page is False
        assert workspaces.page_info.has_previous_page is False

        # First workspace (succeeded)
        ws1 = workspaces.nodes[0]
        assert ws1.name == "dev-workspace"
        assert ws1.repository_name == "main-repo"
        assert ws1.build_status == AutomationBuildStatus.SUCCEEDED
        assert len(ws1.build_errors) == 0
        assert ws1.build_branch == "main"
        assert ws1.build_commit == "abc123def456"
        assert isinstance(ws1.created_at, datetime)
        assert isinstance(ws1.changed_at, datetime)

        # Second workspace (running)
        ws2 = workspaces.nodes[1]
        assert ws2.name == "staging-workspace"
        assert ws2.build_status == AutomationBuildStatus.RUNNING
        assert ws2.build_finished_at is None

        # Third workspace (failed with errors)
        ws3 = workspaces.nodes[2]
        assert ws3.name == "failed-workspace"
        assert ws3.build_status == AutomationBuildStatus.FAILED
        assert len(ws3.build_errors) == 1
        assert ws3.build_errors[0].code == "BUILD_ERROR_001"
        assert "FilePath" in ws3.build_errors[0].message_template
        assert len(ws3.build_errors[0].data) == 2

        # GraphQL Query wurde aufgerufen
        mock_graphql_execute.assert_called_once()
        call_args = mock_graphql_execute.call_args
        assert "workspaces" in call_args[0][0]
        assert call_args[0][1]["first"] == 10

    def test_get_workspaces_with_filter(self, mock_graphql_execute, workspaces_response):
        """Test get_workspaces mit Where-Filter."""
        mock_graphql_execute.return_value = workspaces_response

        queries = QueryOperations(mock_graphql_execute)
        where = {"name": {"eq": "dev-workspace"}}
        workspaces = queries.get_workspaces(first=10, where=where)

        assert len(workspaces.nodes) == 3

        # Prüfe dass where-Parameter übergeben wurde
        call_args = mock_graphql_execute.call_args
        assert call_args[0][1]["where"] == where

    def test_get_workspaces_with_order(self, mock_graphql_execute, workspaces_response):
        """Test get_workspaces mit Sortierung."""
        mock_graphql_execute.return_value = workspaces_response

        queries = QueryOperations(mock_graphql_execute)
        order = [{"name": "ASC"}]
        workspaces = queries.get_workspaces(first=10, order=order)

        assert len(workspaces.nodes) == 3

        # Prüfe dass order-Parameter übergeben wurde
        call_args = mock_graphql_execute.call_args
        assert call_args[0][1]["order"] == order


@pytest.mark.unit
class TestQueryOperationsGetRepositories:
    """Tests für get_repositories() Methode."""

    def test_get_repositories_basic(self, mock_graphql_execute, repositories_response):
        """Test get_repositories ohne Filter."""
        mock_graphql_execute.return_value = repositories_response

        queries = QueryOperations(mock_graphql_execute)
        repositories = queries.get_repositories(first=10)

        # Assertions
        assert repositories.total_count == 2
        assert len(repositories.nodes) == 2

        # First repository (username/password)
        repo1 = repositories.nodes[0]
        assert repo1.name == "main-repo"
        assert repo1.url == "https://github.com/example/main-repo.git"
        assert (
            repo1.authentication_method == RepositoryAuthenticationMethod.USERNAME_PASSWORD
        )
        assert repo1.username == "deploy-user"
        assert repo1.ssh_public_key is None
        assert isinstance(repo1.created_at, datetime)

        # Second repository (SSH key)
        repo2 = repositories.nodes[1]
        assert repo2.name == "test-repo"
        assert repo2.authentication_method == RepositoryAuthenticationMethod.SSH_KEY
        assert repo2.username is None
        assert repo2.ssh_public_key is not None
        assert repo2.ssh_public_key.startswith("ssh-rsa")

        # GraphQL Query wurde aufgerufen
        mock_graphql_execute.assert_called_once()
        call_args = mock_graphql_execute.call_args
        assert "repositories" in call_args[0][0]


@pytest.mark.unit
class TestQueryOperationsGetAutomations:
    """Tests für get_automations() Methode."""

    def test_get_automations_basic(self, mock_graphql_execute, automations_response):
        """Test get_automations ohne Filter."""
        mock_graphql_execute.return_value = automations_response

        queries = QueryOperations(mock_graphql_execute)
        automations = queries.get_automations(first=10)

        # Assertions
        assert automations.total_count == 2
        assert len(automations.nodes) == 2

        # First automation (mit Arguments)
        auto1 = automations.nodes[0]
        assert auto1.workspace_name == "dev-workspace"
        assert auto1.path == "scripts/daily_sync.py"
        assert auto1.description == "Daily synchronization automation"
        assert auto1.environment == "production"
        assert auto1.allow_parallel_execution is False
        assert len(auto1.argument_definitions) == 3

        # Argument definitions prüfen
        arg1 = auto1.argument_definitions[0]
        assert arg1.name == "source"
        assert arg1.description == "Source system identifier"
        assert arg1.mandatory is True

        arg3 = auto1.argument_definitions[2]
        assert arg3.name == "dry_run"
        assert arg3.mandatory is False

        # Second automation (ohne Arguments)
        auto2 = automations.nodes[1]
        assert auto2.path == "scripts/cleanup.py"
        assert auto2.allow_parallel_execution is True
        assert len(auto2.argument_definitions) == 0

        # GraphQL Query wurde aufgerufen
        mock_graphql_execute.assert_called_once()
        call_args = mock_graphql_execute.call_args
        assert "automations" in call_args[0][0]


@pytest.mark.unit
class TestQueryOperationsGetExecutions:
    """Tests für get_executions() Methode."""

    def test_get_executions_basic(self, mock_graphql_execute, executions_response):
        """Test get_executions ohne Filter."""
        mock_graphql_execute.return_value = executions_response

        queries = QueryOperations(mock_graphql_execute)
        executions = queries.get_executions(first=10)

        # Assertions
        assert executions.total_count == 3
        assert len(executions.nodes) == 3

        # First execution (succeeded, manual)
        exec1 = executions.nodes[0]
        assert isinstance(exec1.id, UUID)
        assert str(exec1.id) == "12345678-1234-1234-1234-123456789012"
        assert exec1.branch == "main"
        assert exec1.commit == "abc123def456"
        assert exec1.workspace_name == "dev-workspace"
        assert exec1.repository_name == "main-repo"
        assert exec1.automation_path == "scripts/daily_sync.py"
        assert exec1.environment == "production"
        assert exec1.initiator_type == AutomationInitiatorType.MANUAL
        assert exec1.initiator_reference == "user@example.com"
        assert len(exec1.argument_instances) == 2
        assert exec1.status == AutomationExecutionStatus.SUCCEEDED
        assert "Synchronization completed successfully" in exec1.output
        assert exec1.domain_status is not None
        assert isinstance(exec1.created_at, datetime)
        assert isinstance(exec1.started_at, datetime)
        assert isinstance(exec1.finished_at, datetime)
        assert exec1.duration == "PT15M25S"

        # Second execution (running, schedule)
        exec2 = executions.nodes[1]
        assert exec2.initiator_type == AutomationInitiatorType.SCHEDULE
        assert exec2.initiator_reference == "daily-sync-schedule"
        assert exec2.status == AutomationExecutionStatus.RUNNING
        assert exec2.finished_at is None
        assert exec2.duration is None

        # Third execution (failed, script)
        exec3 = executions.nodes[2]
        assert exec3.initiator_type == AutomationInitiatorType.SCRIPT
        assert exec3.status == AutomationExecutionStatus.FAILED
        assert "Database connection timeout" in exec3.output
        assert exec3.domain_status_message == "Database connection failed after 30s"

        # GraphQL Query wurde aufgerufen
        mock_graphql_execute.assert_called_once()
        call_args = mock_graphql_execute.call_args
        assert "executions" in call_args[0][0]


@pytest.mark.unit
class TestQueryOperationsGetSchedules:
    """Tests für get_schedules() Methode."""

    def test_get_schedules_basic(self, mock_graphql_execute, schedules_response):
        """Test get_schedules ohne Filter."""
        mock_graphql_execute.return_value = schedules_response

        queries = QueryOperations(mock_graphql_execute)
        schedules = queries.get_schedules(first=10)

        # Assertions
        assert schedules.total_count == 2
        assert len(schedules.nodes) == 2

        # First schedule (active)
        sched1 = schedules.nodes[0]
        assert sched1.automation_path == "scripts/daily_sync.py"
        assert sched1.name == "daily-sync-schedule"
        assert sched1.description == "Runs daily synchronization at 2 AM"
        assert sched1.cron == "0 2 * * *"
        assert sched1.timezone == "Europe/Berlin"
        assert sched1.active is True
        assert len(sched1.argument_instances) == 2
        assert sched1.version == 5

        # Second schedule (inactive)
        sched2 = schedules.nodes[1]
        assert sched2.name == "weekly-cleanup"
        assert sched2.active is False
        assert len(sched2.argument_instances) == 0

        # GraphQL Query wurde aufgerufen
        mock_graphql_execute.assert_called_once()
        call_args = mock_graphql_execute.call_args
        assert "schedules" in call_args[0][0]


@pytest.mark.unit
class TestQueryOperationsGetScheduledExecutions:
    """Tests für get_scheduled_executions() Methode."""

    def test_get_scheduled_executions_basic(
        self, mock_graphql_execute, scheduled_executions_response
    ):
        """Test get_scheduled_executions ohne Zeitbereich."""
        mock_graphql_execute.return_value = scheduled_executions_response

        queries = QueryOperations(mock_graphql_execute)
        scheduled_execs = queries.get_scheduled_executions(first=10)

        # Assertions
        assert len(scheduled_execs.nodes) == 3
        assert scheduled_execs.page_info.has_next_page is True

        # First scheduled execution
        sched_exec1 = scheduled_execs.nodes[0]
        assert sched_exec1.name == "daily-sync-schedule"
        assert sched_exec1.automation_path == "scripts/daily_sync.py"
        assert isinstance(sched_exec1.fire_time, datetime)
        assert len(sched_exec1.argument_instances) == 2

        # GraphQL Query wurde aufgerufen
        mock_graphql_execute.assert_called_once()
        call_args = mock_graphql_execute.call_args
        assert "scheduledExecutions" in call_args[0][0]

    def test_get_scheduled_executions_with_time_range(
        self, mock_graphql_execute, scheduled_executions_response
    ):
        """Test get_scheduled_executions mit Zeitbereich."""
        mock_graphql_execute.return_value = scheduled_executions_response

        queries = QueryOperations(mock_graphql_execute)
        from_time = datetime(2025, 1, 22, 0, 0, 0)
        to_time = datetime(2025, 1, 25, 0, 0, 0)
        scheduled_execs = queries.get_scheduled_executions(
            from_time=from_time, to_time=to_time, first=10
        )

        assert len(scheduled_execs.nodes) == 3

        # Prüfe dass Zeitparameter übergeben wurden
        call_args = mock_graphql_execute.call_args
        assert call_args[0][1]["from"] == from_time.isoformat()
        assert call_args[0][1]["to"] == to_time.isoformat()


@pytest.mark.unit
class TestQueryOperationsGetErrorCodes:
    """Tests für get_error_codes() Methode."""

    def test_get_error_codes_basic(self, mock_graphql_execute, error_codes_response):
        """Test get_error_codes ohne Filter."""
        mock_graphql_execute.return_value = error_codes_response

        queries = QueryOperations(mock_graphql_execute)
        error_codes = queries.get_error_codes(first=10)

        # Assertions
        assert error_codes.total_count == 10
        assert len(error_codes.nodes) == 10

        # First error code
        err1 = error_codes.nodes[0]
        assert err1.code == "AUTOMATION_ARGUMENT_DUPLICATE"
        assert "Duplicate argument" in err1.message_template
        assert "ArgumentName" in err1.parameters
        # Nur ein Parameter
        assert len(err1.parameters) == 1

        # Second error code
        err2 = error_codes.nodes[1]
        assert err2.code == "AUTOMATION_ARGUMENT_MISSING"
        assert len(err2.parameters) == 1

        # GraphQL Query wurde aufgerufen
        mock_graphql_execute.assert_called_once()
        call_args = mock_graphql_execute.call_args
        assert "errorCodes" in call_args[0][0]


@pytest.mark.unit
class TestQueryOperationsGetServiceInfo:
    """Tests für get_service_info() Methode."""

    def test_get_service_info_basic(self, mock_graphql_execute, service_info_response):
        """Test get_service_info."""
        mock_graphql_execute.return_value = service_info_response

        queries = QueryOperations(mock_graphql_execute)
        service_info = queries.get_service_info()

        # Assertions
        assert service_info.name == "Questra Automation Service"
        assert service_info.version == "1.2.3"
        assert service_info.informational_version == "1.2.3+build.20250120.0815"

        # GraphQL Query wurde aufgerufen
        mock_graphql_execute.assert_called_once()
        call_args = mock_graphql_execute.call_args
        assert "automationServiceInfo" in call_args[0][0]
