# Copyright (c) Microsoft. All rights reserved.

import asyncio
from enum import Enum
from typing import ClassVar

from azure.identity import AzureCliCredential
from pydantic import Field

from semantic_kernel.agents import ChatCompletionAgent, ChatHistoryAgentThread
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from semantic_kernel.functions import kernel_function
from semantic_kernel.kernel_pydantic import KernelBaseModel
from semantic_kernel.processes.kernel_process import (
    KernelProcessStep,
    KernelProcessStepContext,
    KernelProcessStepState,
)


class CommonEvents(Enum):
    """Common events for the sample process."""

    UserInputReceived = "UserInputReceived"
    CompletionResponseGenerated = "CompletionResponseGenerated"
    WelcomeDone = "WelcomeDone"
    AStepDone = "AStepDone"
    BStepDone = "BStepDone"
    CStepDone = "CStepDone"
    StartARequested = "StartARequested"
    StartBRequested = "StartBRequested"
    ExitRequested = "ExitRequested"
    StartProcess = "StartProcess"


# Define a sample step that once the `on_input_event` is received,
# it will emit two events to start the A and B steps.
class KickOffStep(KernelProcessStep):
    KICK_OFF_FUNCTION: ClassVar[str] = "kick_off"

    @kernel_function(name=KICK_OFF_FUNCTION)
    async def print_welcome_message(self, context: KernelProcessStepContext):
        print("##### Kickoff ran.")
        await context.emit_event(process_event=CommonEvents.StartARequested, data="Get Going A")
        await context.emit_event(process_event=CommonEvents.StartBRequested, data="Get Going B")


# Define a sample `AStep` step that will emit an event after 1 second.
# The event will be sent to the `CStep` step with the data `I did A`.
class AStep(KernelProcessStep):
    @kernel_function()
    async def do_it(self, context: KernelProcessStepContext):
        print("##### AStep ran.")
        await asyncio.sleep(1)
        await context.emit_event(process_event=CommonEvents.AStepDone, data="I did A")


# Define a simple factory for the BStep that can create the dependency that the BStep requires
# As an example, this factory creates a kernel and adds an `AzureChatCompletion` service to it.
async def bstep_factory():
    """Creates a BStep instance with ephemeral references like ChatCompletionAgent."""
    agent = ChatCompletionAgent(
        service=AzureChatCompletion(credential=AzureCliCredential()), name="echo", instructions="repeat the input back"
    )
    step_instance = BStep()
    step_instance.agent = agent
    step_instance.thread = ChatHistoryAgentThread()

    return step_instance


class BStep(KernelProcessStep):
    """A sample BStep that optionally holds a ChatCompletionAgent.

    By design, the agent is ephemeral (not stored in state).
    """

    # Ephemeral references won't be persisted to Dapr
    # because we do not place them in a step state model.
    # We'll set this in the factory function:
    agent: ChatCompletionAgent | None = None
    thread: ChatHistoryAgentThread | None = None

    @kernel_function(name="do_it")
    async def do_it(self, context: KernelProcessStepContext):
        print("##### BStep ran (do_it).")
        await asyncio.sleep(2)

        if self.agent:
            response = await self.agent.get_response(messages="Hello from BStep!")
            print(f"BStep got agent response: {response.content}")

        await context.emit_event(process_event="BStepDone", data="I did B")


# Define a sample `CStepState` that will keep track of the current cycle.
class CStepState(KernelBaseModel):
    current_cycle: int = 1


# Define a sample `CStep` step that will emit an `ExitRequested` event after 3 cycles.
class CStep(KernelProcessStep[CStepState]):
    state: CStepState = Field(default_factory=CStepState)

    # The activate method overrides the base class method to set the state in the step.
    async def activate(self, state: KernelProcessStepState[CStepState]):
        """Activates the step and sets the state."""
        self.state = state.state
        print(f"##### CStep activated with Cycle = '{self.state.current_cycle}'.")

    @kernel_function()
    async def do_it(self, context: KernelProcessStepContext, astepdata: str, bstepdata: str):
        self.state.current_cycle += 1
        if self.state.current_cycle >= 3:
            print("##### CStep run cycle 3 - exiting.")
            await context.emit_event(process_event=CommonEvents.ExitRequested)
            return
        print(f"##### CStep run cycle {self.state.current_cycle}")
        await context.emit_event(process_event=CommonEvents.CStepDone)
