"""Handlers for different events and protocols."""

import json
from uuid import UUID

import stomp
import stomp.utils
from stomp.constants import HDR_DESTINATION
from waldur_api_client import AuthenticatedClient
from waldur_api_client.api.marketplace_slurm_periodic_usage_policies import (
    marketplace_slurm_periodic_usage_policies_report_command_result,
)
from waldur_api_client.errors import UnexpectedStatus
from waldur_api_client.models import ObservableObjectTypeEnum, OrderState
from waldur_api_client.models.agent_service import AgentService
from waldur_api_client.models.slurm_command_result_request import (
    SlurmCommandResultRequest,
)

from waldur_site_agent.backend import logger
from waldur_site_agent.common import agent_identity_management, structures
from waldur_site_agent.common import processors as common_processors
from waldur_site_agent.common import utils as common_utils
from waldur_site_agent.event_processing.structures import (
    AccountMessage,
    BackendResourceRequestMessage,
    OfferingUserMessage,
    OrderMessage,
    PeriodicLimitsMessage,
    ResourceMessage,
    UserRoleMessage,
)


def register_event_process_service(
    offering: structures.Offering,
    waldur_rest_client: AuthenticatedClient,
    observable_object: ObservableObjectTypeEnum,
) -> AgentService:
    """A shortcut for initialization of the event_process service.

    Args:
        offering (structures.Offering): Waldur offering
        waldur_rest_client (AuthenticatedClient): Waldur API client
        observable_object (ObservableObjectTypeEnum): Type of observable object

    Returns:
        AgentService: Registered agent service
    """
    agent_identity_manager = agent_identity_management.AgentIdentityManager(
        offering, waldur_rest_client
    )
    agent_identity_name = f"agent-{offering.uuid}"
    agent_identity = agent_identity_manager.get_identity(agent_identity_name)
    service_name = f"{structures.AgentMode.EVENT_PROCESS.value}-{observable_object}"
    return agent_identity_manager.register_service(
        agent_identity,
        service_name,
        structures.AgentMode.EVENT_PROCESS.value,
    )


def process_account_message(
    message: AccountMessage,
    offering: structures.Offering,
    account_type: structures.AccountType,
    observable_object: ObservableObjectTypeEnum,
    user_agent: str = "",
) -> None:
    """Process generic account message."""
    account_username = message["account_username"]
    service_account_uuid = message["account_uuid"]
    project_uuid = message["project_uuid"]
    action = message.get("action", "create")
    try:
        waldur_rest_client = common_utils.get_client(
            offering.api_url, offering.api_token, user_agent, offering.verify_ssl
        )

        agent_service = register_event_process_service(
            offering, waldur_rest_client, observable_object
        )

        processor = common_processors.OfferingMembershipProcessor(offering, waldur_rest_client)
        processor.register(agent_service)
        if action == "create":
            processor.process_account_creation(account_username, account_type)
        elif action == "delete":
            processor.process_account_removal(account_username, project_uuid)
        else:
            logger.error("Unknown action %s for course account %s", action, account_username)
    except Exception as e:
        logger.error(
            "Failed to process %s of course account %s (%s): %s",
            action,
            account_username,
            service_account_uuid,
            e,
        )


def on_order_message_stomp(
    frame: stomp.utils.Frame, offering: structures.Offering, user_agent: str
) -> None:
    """Order-processing handler for STOMP message event."""
    message: OrderMessage = json.loads(frame.body)
    logger.info("Processing message: %s", message)
    order_uuid = message["order_uuid"]
    order_state = message.get("order_state", "")

    # Skip done and erred orders to avoid duplicate processing
    if order_state in [OrderState.DONE, OrderState.ERRED]:
        logger.info("Skipping order %s with finished state %s", order_uuid, order_state)
        return

    if order_state == OrderState.PENDING_CONSUMER:
        logger.info("Skipping order %s with state %s", order_uuid, order_state)
        return

    try:
        waldur_rest_client = common_utils.get_client(
            offering.api_url, offering.api_token, user_agent, offering.verify_ssl
        )
        agent_service = register_event_process_service(
            offering, waldur_rest_client, ObservableObjectTypeEnum.ORDER
        )

        # Create backend instance for dependency injection
        resource_backend, resource_backend_version = common_utils.get_backend_for_offering(
            offering, "order_processing_backend"
        )

        processor = common_processors.OfferingOrderProcessor(
            offering,
            waldur_rest_client,
            resource_backend=resource_backend,
            resource_backend_version=resource_backend_version,
        )
        processor.register(agent_service)

        order = processor.get_order_info(order_uuid)
        if order is None:
            logger.error("Failed to process order %s", order_uuid)
            return
        processor.process_order_with_retries(order)
    except Exception as e:
        logger.error("Failed to process order %s: %s", order_uuid, e)


def on_user_role_message_stomp(
    frame: stomp.utils.Frame, offering: structures.Offering, user_agent: str
) -> None:
    """Membership sync handler for STOMP message event."""
    message: UserRoleMessage = json.loads(frame.body)
    logger.info("Received message: %s on topic %s", message, frame.headers.get("destination"))
    user_uuid = message.get("user_uuid")
    project_name = message["project_name"]
    project_uuid = message["project_uuid"]

    try:
        waldur_rest_client = common_utils.get_client(
            offering.api_url, offering.api_token, user_agent, offering.verify_ssl
        )
        agent_service = register_event_process_service(
            offering, waldur_rest_client, ObservableObjectTypeEnum.USER_ROLE
        )

        # Create backend instance for dependency injection
        resource_backend, resource_backend_version = common_utils.get_backend_for_offering(
            offering, "membership_sync_backend"
        )

        processor = common_processors.OfferingMembershipProcessor(
            offering,
            waldur_rest_client,
            resource_backend=resource_backend,
            resource_backend_version=resource_backend_version,
        )
        processor.register(agent_service)
        if user_uuid:
            user_username = message["user_username"]
            role_granted = message["granted"]
            if role_granted is None:
                logger.error("Missing required field 'granted' for user role change")
                return
            logger.info(
                "Processing %s (%s) user role changed event in project %s, granted: %s",
                user_username,
                user_uuid,
                project_name,
                role_granted,
            )
            processor.process_user_role_changed(user_uuid, project_uuid, role_granted)
        else:
            logger.info("Processing full project all users sync event for project %s", project_name)
            processor.process_project_user_sync(project_uuid)
    except Exception as e:
        if user_uuid:
            logger.error(
                "Failed to process user %s (%s) role change in project %s (%s) (granted: %s): %s",
                user_username,
                user_uuid,
                project_name,
                project_uuid,
                role_granted,
                e,
            )
        else:
            logger.error(
                "Failed to process full project all users sync event for project %s: %s",
                project_uuid,
                e,
            )


def on_resource_message_stomp(
    frame: stomp.utils.Frame, offering: structures.Offering, user_agent: str
) -> None:
    """Resource update handler for STOMP message event."""
    message: ResourceMessage = json.loads(frame.body)
    resource_uuid = message["resource_uuid"]

    try:
        waldur_rest_client = common_utils.get_client(
            offering.api_url, offering.api_token, user_agent, offering.verify_ssl
        )

        agent_service = register_event_process_service(
            offering, waldur_rest_client, ObservableObjectTypeEnum.RESOURCE
        )
        processor = common_processors.OfferingMembershipProcessor(offering, waldur_rest_client)
        processor.register(agent_service)

        processor.process_resource_by_uuid(resource_uuid)
    except Exception as e:
        logger.error("Failed to process resource %s: %s", resource_uuid, e)


def on_importable_resources_message_stomp(
    frame: stomp.utils.Frame, offering: structures.Offering, user_agent: str
) -> None:
    """Handler for importable resource list request for STOMP message event."""
    message: BackendResourceRequestMessage = json.loads(frame.body)
    request_uuid = message["backend_resource_request_uuid"]
    try:
        waldur_rest_client = common_utils.get_client(
            offering.api_url, offering.api_token, user_agent, offering.verify_ssl
        )

        agent_service = register_event_process_service(
            offering, waldur_rest_client, ObservableObjectTypeEnum.IMPORTABLE_RESOURCES
        )

        # Create backend instance for dependency injection
        resource_backend, resource_backend_version = common_utils.get_backend_for_offering(
            offering, "order_processing_backend"
        )

        processor = common_processors.OfferingImportableResourcesProcessor(
            offering,
            waldur_rest_client,
            resource_backend=resource_backend,
            resource_backend_version=resource_backend_version,
        )
        processor.register(agent_service)

        processor.process_request(request_uuid)
    except Exception as e:
        logger.error("Failed to process importable resource list request %s: %s", request_uuid, e)


def on_account_message_stomp(
    frame: stomp.utils.Frame, offering: structures.Offering, user_agent: str
) -> None:
    """Service account handler for STOMP."""
    message: AccountMessage = json.loads(frame.body)
    queue: str = frame.headers[HDR_DESTINATION]
    queue_parts = queue.split("_")
    account_type_raw = f"{queue_parts[-2]}_{queue_parts[-1]}"
    account_type = structures.AccountType.SERVICE_ACCOUNT
    observable_object = ObservableObjectTypeEnum.SERVICE_ACCOUNT
    if account_type_raw == structures.AccountType.COURSE_ACCOUNT.value:
        account_type = structures.AccountType.COURSE_ACCOUNT
        observable_object = ObservableObjectTypeEnum.COURSE_ACCOUNT
    process_account_message(message, offering, account_type, observable_object, user_agent)


def _report_command_result_to_waldur(
    offering: structures.Offering,
    message: PeriodicLimitsMessage,
    result: dict,
) -> None:
    """Report command execution result back to Waldur's report-command-result endpoint."""
    try:
        waldur_rest_client = common_utils.get_client(
            offering.api_url, offering.api_token, "site-agent", offering.verify_ssl
        )

        resource_uuid_str = message.get("resource_uuid", "")
        policy_uuid_str = message.get("policy_uuid", "")

        if not policy_uuid_str:
            logger.warning(
                "No policy_uuid in periodic limits message, cannot report result"
            )
            return

        body = SlurmCommandResultRequest(
            resource_uuid=UUID(resource_uuid_str),
            success=result.get("success", False),
            error_message=result.get("error", ""),
        )
        body["commands_executed"] = result.get("commands_executed", [])

        marketplace_slurm_periodic_usage_policies_report_command_result.sync_detailed(
            uuid=UUID(policy_uuid_str),
            client=waldur_rest_client,
            body=body,
        )

        logger.info(
            "Reported command result for resource %s to Waldur",
            message.get("backend_id", "unknown"),
        )
    except UnexpectedStatus as e:
        logger.warning(
            "Failed to report command result to Waldur: %s",
            e,
        )
    except Exception as e:
        logger.error("Error reporting command result to Waldur: %s", e)


def on_resource_periodic_limits_update_stomp(
    frame: stomp.utils.Frame,
    offering: structures.Offering,
    user_agent: str,  # noqa: ARG001
) -> None:
    """Periodic limits update handler for STOMP message event."""
    try:
        message: PeriodicLimitsMessage = json.loads(frame.body)
        logger.info(
            "Processing periodic limits update message for resource %s",
            message.get("backend_id", "unknown"),
        )
        logger.debug("Periodic limits message: %s", message)

        # Extract message data
        backend_id = message.get("backend_id")
        action = message.get("action")
        settings = message.get("settings", {})

        if not backend_id or action != "apply_periodic_settings":
            logger.error("Invalid periodic limits message: missing backend_id or invalid action")
            return

        # Get backend for the offering
        backend, _ = common_utils.get_backend_for_offering(offering, "order_processing_backend")

        if not hasattr(backend, "apply_periodic_settings"):
            logger.warning("Backend %s does not support periodic limits", type(backend).__name__)
            return

        # Apply periodic settings via backend
        result = backend.apply_periodic_settings(backend_id, settings)

        if result.get("success"):
            logger.info("Successfully applied periodic settings for resource %s", backend_id)
        else:
            logger.error(
                "Failed to apply periodic settings for resource %s: %s",
                backend_id,
                result.get("error", "unknown error"),
            )

        # Report command execution result back to Waldur
        _report_command_result_to_waldur(offering, message, result)

    except json.JSONDecodeError as e:
        logger.error("Failed to parse periodic limits STOMP message: %s", e)
    except Exception as e:
        logger.error("Error processing periodic limits update: %s", e)


def on_offering_user_message_stomp(
    frame: stomp.utils.Frame, offering: structures.Offering, user_agent: str
) -> None:
    """Offering user event handler for STOMP."""
    message: OfferingUserMessage = json.loads(frame.body)
    logger.info("Received offering user message: %s", message)
    _process_offering_user_message(message, offering, user_agent)



def _process_offering_user_message(
    message: OfferingUserMessage,
    offering: structures.Offering,
    user_agent: str,
) -> None:
    """Process an OFFERING_USER event message."""
    action = message.get("action", "")
    offering_user_uuid = message.get("offering_user_uuid", "")
    username = message.get("username", "")

    try:
        waldur_rest_client = common_utils.get_client(
            offering.api_url, offering.api_token, user_agent, offering.verify_ssl
        )
        register_event_process_service(
            offering, waldur_rest_client, ObservableObjectTypeEnum.OFFERING_USER
        )

        if action == "attribute_update":
            attributes = message.get("attributes", {})
            changed = message.get("changed_attributes", [])
            logger.info(
                "User %s attribute update: changed=%s",
                username,
                changed,
            )
            _forward_user_attributes_to_backend(offering, username, attributes, user_agent)
        elif action == "create":
            attributes = message.get("attributes", {})
            logger.info(
                "Offering user %s created with attributes: %s", username, list(attributes)
            )
            _forward_user_attributes_to_backend(offering, username, attributes, user_agent)
        elif action in ("update", "delete"):
            logger.info(
                "Offering user %s action: %s (no attribute forwarding)", username, action
            )
        else:
            logger.warning("Unknown offering user action: %s", action)
    except Exception:
        logger.exception(
            "Failed to process offering user event %s for %s (%s)",
            action,
            username,
            offering_user_uuid,
        )


def _forward_user_attributes_to_backend(
    offering: structures.Offering,
    username: str,
    attributes: dict,
    user_agent: str,  # noqa: ARG001
) -> None:
    """Forward user attributes to the membership sync backend if available."""
    if not offering.membership_sync_backend:
        logger.debug(
            "No membership_sync_backend for offering %s, skipping attribute forward",
            offering.name,
        )
        return

    if not attributes:
        logger.debug("No attributes to forward for user %s", username)
        return

    try:
        backend, _ = common_utils.get_backend_for_offering(offering, "membership_sync_backend")
        if hasattr(backend, "update_user_attributes"):
            backend.update_user_attributes(username, attributes)
            logger.info(
                "Forwarded %d attributes for user %s to backend", len(attributes), username
            )
        else:
            logger.debug(
                "Backend %s does not support update_user_attributes",
                type(backend).__name__,
            )
    except Exception:
        logger.exception("Failed to forward attributes for user %s", username)
