import asyncio
import functools
import json
import logging
import os
import sys
import traceback
from datetime import datetime, timezone
from typing import Any, Callable, Union

import aiohttp

from .azure_app_config import configurations
from .common_handler import ExecutionResult

TIME_FORMAT = "%Y-%m-%d %H:%M:%S %Z"


def teams_notification() -> Callable:
    def decorator_start_processing(func: Callable) -> Callable:
        @functools.wraps(func)
        def wrapper_start_processing(self, *args: Any, **kwargs: Any) -> Union[Any, asyncio.Future]:
            teams_config = json.loads(configurations.teams_decorator)
            workflow_url: str = teams_config.get("workflow_url", "")
            teams_enabled: int = teams_config.get("is_enabled", 0)
            default_email: str = teams_config.get("default_email")

            automation_name = self.automation_name
            automation_code = self.code
            tenant_id = self.tenant_id
            user_email = self.user_email if self.user_email else default_email
            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: str = os.getenv("APPLICATION_ENVIRONMENT", "Unknown")

            async def send_teams_notification(message: str, is_success: int) -> None:
                if not teams_enabled:
                    return

                payload = {
                    "text": message,
                    "mention": user_email,
                    "is_success": is_success,
                    "env": environment,
                    "mrkdwn": True,
                }

                try:
                    async with aiohttp.ClientSession() as session:
                        async with session.post(workflow_url, json=payload) as response:
                            if response.status not in (200, 202):
                                logging.exception(f"Failed to send Teams notification: {response.status}")

                except Exception as teams_exception:
                    logging.exception(f"Failed to send Teams notification: {teams_exception}")

            def format_success_message(execution_obj: dict) -> tuple[str, int]:
                timestamp_utc = datetime.now(timezone.utc).strftime(TIME_FORMAT)
                message = (
                    f"Automation: {automation_name}\n\n"
                    f"Tenant ID: {tenant_id}\n\n"
                    f"Execution Time: {execution_obj.get('ExecutionTime')}\n\n"
                    f"Read Count: {execution_obj.get('ReadCount')}\n\n"
                    f"Create Count: {execution_obj.get('CreateCount')}\n\n"
                    f"Update Count: {execution_obj.get('UpdateCount')}\n\n"
                    f"Delete Count: {execution_obj.get('DeleteCount')}\n\n"
                    f"Failure Count: {execution_obj.get('FailureCount')}\n\n"
                    f"No Operation Count: {execution_obj.get('NoOpCount', 0)}\n\n"
                    f"Automation Code: {automation_code}\n\n"
                    f"Timestamp (UTC): {timestamp_utc}\n\n"
                    f"Environment: {environment}\n\n"
                    f"Custom Message: {execution_obj.get('CustomMessage', '')}\n\n"
                    f"Invocation ID: {invocation_id}\n\n"
                    f"Azure Storage Link: {execution_obj.get('AzureBlobStorage')}\n\n"
                )

                return message, 0 if execution_obj.get('FailureCount') > 0 else 1

            def format_success_message_execution_result(execution_obj: ExecutionResult) -> tuple[str, int]:
                timestamp_utc = datetime.now(timezone.utc).strftime(TIME_FORMAT)
                message = (
                    f"Automation: {automation_name}\n\n"
                    f"Tenant ID: {tenant_id}\n\n"
                    f"Execution Time: {execution_obj.execution_time}\n\n"
                    f"Read Count: {execution_obj.read_count}\n\n"
                    f"Create Count: {execution_obj.create_count}\n\n"
                    f"Update Count: {execution_obj.update_count}\n\n"
                    f"Delete Count: {execution_obj.delete_count}\n\n"
                    f"Failure Count: {execution_obj.failure_count}\n\n"
                    f"No Operation Count: {execution_obj.no_op_count}\n\n"
                    f"Automation Code: {automation_code}\n\n"
                    f"Timestamp (UTC): {timestamp_utc}\n\n"
                    f"Environment: {environment}\n\n"
                    f"Custom Message: {execution_obj.custom_message}\n\n"
                    f"Invocation ID: {invocation_id}\n\n"
                    f"Azure Storage Link: {execution_obj.azure_blob_storage}\n\n"
                )

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

            def format_failure_message(exception: Exception) -> str:
                timestamp_utc = datetime.now(timezone.utc).strftime(TIME_FORMAT)

                _, _, 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"Tenant ID: {tenant_id}\n\n"
                    f"Automation Code: {automation_code} \n\n"
                    f"Environment: {environment} \n\n"
                    f"Timestamp:  {timestamp_utc}\n\n"
                    f"FileName: {filename}\n\n"
                    f"Line No:  {lineno}\n\n"
                    f"Function Name:  {func_name}\n\n"
                    f"Invocation ID: {invocation_id}\n\n"
                    f"ERROR:  {str(exception)}\n\n"
                )

            async def execute_async_function() -> Any:
                try:
                    logging.info("Starting the processing function.")
                    response = await func(self, *args, **kwargs)

                    if response is not None and not is_http_trigger:
                        execution_obj = response
                        if isinstance(response, ExecutionResult):
                            message, is_success = format_success_message_execution_result(execution_obj)
                        else:
                            message, is_success = format_success_message(execution_obj)

                        await send_teams_notification(message, is_success)

                    return response
                except Exception as e:
                    message = format_failure_message(e)
                    await send_teams_notification(message, is_success=0)

                    logging.exception("An error occurred during processing")
                    raise

            def execute_sync_function() -> Any:
                try:
                    logging.info("Starting the processing function.")
                    response = func(self, *args, **kwargs)

                    if response is not None and not is_http_trigger:
                        execution_obj = response
                        message, is_success = format_success_message(execution_obj)
                        asyncio.run(send_teams_notification(message, is_success))

                    return response
                except Exception as e:
                    message = format_failure_message(e)
                    asyncio.run(send_teams_notification(message, is_success=0))

                    logging.exception("An error occurred during processing")
                    raise

            if asyncio.iscoroutinefunction(func):
                return execute_async_function()
            else:
                return execute_sync_function()

        return wrapper_start_processing
    return decorator_start_processing
