"""Slack Notification Module.

This module provides a decorator-based system for automatic Slack notifications
in JL Automation workflows. It monitors automation execution and sends notifications
for success, partial failure, and complete failure scenarios.

Key Features:
    - Automatic success/failure notifications
    - Support for both sync and async functions
    - Detailed execution statistics in notifications
    - Error tracking with file, line, and function information
    - Environment-specific webhook configuration
    - Configurable notification enabling/disabling
    - Support for both dict and ExecutionResult response formats

The decorator automatically wraps automation processing functions and handles
notification sending based on execution outcomes, providing comprehensive
monitoring and alerting capabilities.

Example:
    Basic usage:
        @slack_notification()
        async def start_processing(self, data):
            # Your automation logic here
            return ExecutionResult(
                execution_time="2024-01-01T12:00:00Z",
                read_count=10,
                create_count=5,
                update_count=2
            )

Alert Types:
    1: Success - All operations completed successfully
    2: Partial Failure - Some operations failed but automation continued
    3: Complete Failure - Automation failed with exception
"""

import functools
import logging
import asyncio
import aiohttp
from typing import Callable, Any, Union
from datetime import datetime, timezone
import os
from .azure_app_config import configurations
import traceback
import sys
from .common_handler import ExecutionResult


def slack_notification() -> Callable:
    """Decorator for automatic Slack notifications on automation execution.
    
    This decorator wraps automation processing functions to provide automatic
    Slack notifications for success, partial failure, and complete failure scenarios.
    It supports both synchronous and asynchronous functions and extracts execution
    statistics for detailed reporting.
    
    Returns:
        Callable: Decorator function that wraps the target automation function.
        
    Configuration:
        The decorator uses the following configuration values:
        - automation_success_slack_webhook: Webhook for success notifications
        - automation_failure_slack_webhook: Webhook for failure notifications  
        - automation_partial_failure_slack_webhook: Webhook for partial failures
        - slack_enabled: Boolean flag to enable/disable notifications
        
    Example:
        @slack_notification()
        async def start_processing(self, input_data):
            # Process data
            return ExecutionResult(execution_time="...", read_count=10)
    """
    def decorator_start_processing(func: Callable) -> Callable:
        @functools.wraps(func)
        def wrapper_start_processing(self, *args: Any, **kwargs: Any) -> Union[Any, asyncio.Future]:
            # Extract webhook URLs from configuration
            webhook_url: str = configurations.automation_success_slack_webhook
            failed_webhook_url: str = configurations.automation_failure_slack_webhook
            partial_failure_webhook_url: str = configurations.automation_partial_failure_slack_webhook

            # Extract automation context information
            automation_name = self.automation_name
            automation_code = self.code
            user_email = self.user_email if self.user_email else "abdula@joblogic.com"
            is_http_trigger = self.is_http_trigger if hasattr(self, "is_http_trigger") else False
            invocation_id = self.invocation_id if hasattr(self, "invocation_id") else "Missing Invocation ID"

            # Environment and configuration
            environment: str = os.getenv("APPLICATION_ENVIRONMENT", "Unknown")
            slack_enabled: bool = configurations.slack_enabled.lower() == "true"

            async def send_slack_notification(message: str, alert_type: int) -> None:
                """Send notification to appropriate Slack webhook based on alert type.
                
                Args:
                    message (str): Formatted message to send to Slack.
                    alert_type (int): Type of alert (1=success, 2=partial failure, 3=failure).
                """
                if not slack_enabled:
                    return
                
                # Select appropriate webhook based on alert type
                url = webhook_url if alert_type == 1 else partial_failure_webhook_url if alert_type == 2 else failed_webhook_url
                    
                payload = {"text": message, "mention": user_email, "mrkdwn": True}

                try:
                    async with aiohttp.ClientSession() as session:
                        async with session.post(url, json=payload) as response:
                            if response.status != 200:
                                logging.error(f"Failed to send Slack notification: {response.status}")
                
                except Exception as slack_exception:
                    logging.error(f"Failed to send Slack notification: {slack_exception}", exc_info=True)

            def format_success_message(execution_obj: dict) -> tuple[str, int]:
                """Format success message for dictionary-based execution results.
                
                Args:
                    execution_obj (Dict[str, Any]): Dictionary containing execution statistics.
                    
                Returns:
                    Tuple[str, int]: Formatted message and alert type (1 or 2).
                """
                timestamp_utc = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S %Z")
                message = (
                    f"Automation: {automation_name} \n\n"
                    f"Execution Time: {execution_obj.get('ExecutionTime')}\n"
                    f"Read Count: {execution_obj.get('ReadCount', 0)}\n"
                    f"Create Count: {execution_obj.get('CreateCount', 0)}\n"
                    f"Update Count: {execution_obj.get('UpdateCount', 0)}\n"
                    f"Delete Count: {execution_obj.get('DeleteCount', 0)}\n"
                    f"Failure Count: {execution_obj.get('FailureCount', 0)}\n"
                    f"No Operation Count: {execution_obj.get('NoOpCount', 0)}\n"
                    f"Automation Code: {automation_code}\n"
                    f"Timestamp (UTC): {timestamp_utc}\n"
                    f"Environment: {environment}\n\n"
                    f"Custom Message: {execution_obj.get('CustomMessage', '')}\n"
                    f"Invocation ID: {invocation_id}\n"
                    f"Azure Storage Link: {execution_obj.get('AzureBlobStorage')}\n"
                )

                return message, 2 if execution_obj.get('FailureCount') > 0 else 1
                
            def format_success_message_execution_result(execution_obj: ExecutionResult) -> tuple[str, int]:
                """Format success message for ExecutionResult-based execution results.
                
                Args:
                    execution_obj (ExecutionResult): ExecutionResult instance with typed fields.
                    
                Returns:
                    Tuple[str, int]: Formatted message and alert type (1 or 2).
                """
                timestamp_utc = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S %Z")
                message = (
                    f"Automation: {automation_name} \n\n"
                    f"Execution Time: {execution_obj.execution_time}\n"
                    f"Read Count: {execution_obj.read_count}\n"
                    f"Create Count: {execution_obj.create_count}\n"
                    f"Update Count: {execution_obj.update_count}\n"
                    f"Delete Count: {execution_obj.delete_count}\n"
                    f"Failure Count: {execution_obj.failure_count}\n"
                    f"No Operation Count: {execution_obj.no_op_count}\n"
                    f"Automation Code: {automation_code}\n"
                    f"Timestamp (UTC): {timestamp_utc}\n"
                    f"Environment: {environment}\n\n"
                    f"Custom Message: {execution_obj.custom_message}\n"
                    f"Invocation ID: {invocation_id}\n"
                    f"Azure Storage Link: {execution_obj.azure_blob_storage}\n"
                )

                return message, 2 if execution_obj.failure_count > 0 else 1

            def format_failure_message(exception: Exception) -> str:
                """Format failure message with exception details and stack trace information.
                
                Args:
                    exception (Exception): The exception that caused the failure.
                    
                Returns:
                    str: Formatted failure message with detailed error information.
                """
                timestamp_utc = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S %Z")
                
                # Extract stack trace information
                _, _, exc_traceback = sys.exc_info()
                if exc_traceback is not None:
                    last_frame = traceback.extract_tb(exc_traceback)[-1]
                    filename = os.path.basename(last_frame.filename)
                    lineno = last_frame.lineno
                    func_name = last_frame.name
                else:
                    filename = "Unknown"
                    lineno = -1
                    func_name = "Unknown"

                return (
                    f"Automation: {automation_name} \n\n"
                    f"Automation Code: {automation_code} \n"
                    f"Environment: {environment} \n"
                    f"Timestamp:  {timestamp_utc}\n"
                    f"FileName: {filename}\n"
                    f"Line No:  {lineno}\n"
                    f"Function Name:  {func_name}\n"
                    f"Invocation ID: {invocation_id}\n\n"
                    f"ERROR:  {str(exception)}"
                )

            async def execute_async_function() -> Any:
                """Execute async function with error handling and notification sending.
                
                Returns:
                    Any: Result from the wrapped function.
                    
                Raises:
                    Exception: Re-raises any exception after sending failure notification.
                """
                try:
                    logging.info("Starting the processing function.")
                    response = await func(self, *args, **kwargs)
                    
                    # Send success notification only for non-HTTP triggers with valid response
                    if response is not None and not is_http_trigger:
                        execution_obj = response
                        if isinstance(response, ExecutionResult):
                            message, alert_type = format_success_message_execution_result(execution_obj)
                        else:
                            message, alert_type = format_success_message(execution_obj)

                        await send_slack_notification(message, alert_type)
                    
                    return response
                except Exception as e:
                    message = format_failure_message(e)
                    await send_slack_notification(message, alert_type=3)
                    
                    logging.error("An error occurred during processing", exc_info=True)
                    raise

            def execute_sync_function() -> Any:
                """Execute sync function with error handling and notification sending.
                
                Returns:
                    Any: Result from the wrapped function.
                    
                Raises:
                    Exception: Re-raises any exception after sending failure notification.
                """
                try:
                    logging.info("Starting the processing function.")
                    response = func(self, *args, **kwargs)
                    
                    # Send success notification only for non-HTTP triggers with valid response
                    if response is not None and not is_http_trigger:
                        execution_obj = response
                        message, alert_type = format_success_message(execution_obj)
                        asyncio.run(send_slack_notification(message, alert_type))
                    
                    return response
                except Exception as e:
                    message = format_failure_message(e)
                    asyncio.run(send_slack_notification(message, alert_type=3))
                    
                    logging.error("An error occurred during processing", exc_info=True)
                    raise

            # Execute appropriate function based on whether the wrapped function is async
            if asyncio.iscoroutinefunction(func):
                return execute_async_function()
            else:
                return execute_sync_function()

        return wrapper_start_processing
    return decorator_start_processing