import logging
import os

from dotenv import load_dotenv

from livekit.agents import (
    Agent,
    AgentSession,
    JobContext,
    MetricsCollectedEvent,
    cli,
    inference,
    metrics,
)
from livekit.agents.beta.workflows.dtmf_inputs import (
    GetDtmfTask,
)
from livekit.agents.llm.tool_context import ToolError, function_tool
from livekit.agents.voice.events import RunContext
from livekit.agents.worker import AgentServer

logger = logging.getLogger("dtmf-agent")

load_dotenv()


DTMF_AGENT_DISPATCH_NAME = os.getenv("DTMF_AGENT_DISPATCH_NAME", "my-telephony-agent")

server = AgentServer()


class DtmfAgent(Agent):
    def __init__(self) -> None:
        super().__init__(
            instructions=(
                "You are Horizon Wireless's automated support assistant speaking with a caller over the phone. "
                "Open with a brief, friendly greeting that mentions Horizon Wireless. "
                "Explain that you'll first confirm the caller's account phone number by using the `ask_for_phone_number` tool. "
                "After the phone number is confirmed, guide the caller through picking a support option with the `ask_for_service_options` tool. "
                "Keep the tone professional, concise, and smoothly transition between steps."
            ),
        )

        self.phone_number: str | None = None

    async def on_enter(self) -> None:
        self.session.generate_reply(
            instructions=(
                "Greet the caller as Horizon Wireless's virtual assistant, briefly describe your role, "
                "and let them know you'll collect their 10-digit phone number now."
            )
        )

    @function_tool
    async def ask_for_service_options(self, context: RunContext) -> str:
        """Ask user to which telephone service they want to use."""

        if self.phone_number is None:
            raise ToolError(
                "Phone number not provided, you should ask for it via `ask_for_phone_number` tool"
            )

        while True:
            try:
                result = await GetDtmfTask(
                    num_digits=1,
                    chat_ctx=self.chat_ctx.copy(
                        exclude_instructions=True,
                        exclude_function_call=True,
                        exclude_handoff=True,
                        exclude_config_update=True,
                    ),
                    extra_instructions=(
                        "Let the caller know they can choose one of three Horizon Wireless services: "
                        "press 1 to hear details about their current plan, press 2 to enable international data roaming, "
                        "or press 3 to explore upgrade options. Prompt them for a single digit and give them a moment to respond."
                    ),
                    repeat_instructions=2,
                )
            except ToolError as e:
                await self.session.generate_reply(instructions=e.message, allow_interruptions=False)
                continue

            if result.user_input == "1":
                return "Your current plan is $100 per month"
            elif result.user_input == "2":
                return "International data roaming is enabled"
            elif result.user_input == "3":
                return "Your new plan is $150 per month"

            await self.session.generate_reply(
                instructions=(
                    "Apologize that the selection wasn't recognized, then remind the caller to enter 1 for plan details, "
                    "2 for international roaming, or 3 for upgrades."
                ),
                allow_interruptions=False,
            )

    @function_tool
    async def ask_for_phone_number(self, context: RunContext) -> str:
        """Ask user to provide a phone number."""
        while True:
            try:
                result = await GetDtmfTask(
                    num_digits=10,
                    chat_ctx=self.chat_ctx.copy(
                        exclude_instructions=True,
                        exclude_function_call=True,
                        exclude_handoff=True,
                        exclude_config_update=True,
                    ),
                    ask_for_confirmation=True,
                    extra_instructions=(
                        "Let the caller know you'll record their 10-digit account number and that they can speak or dial it. "
                        "Provide an example such as 415 555 0199, remind them you're keeping their information secure, "
                        "then capture the digits. Read the number back in grouped segments for confirmation and invite them to confirm or re-enter."
                    ),
                    repeat_instructions=2,
                )
            except ToolError as e:
                await self.session.generate_reply(instructions=e.message, allow_interruptions=False)
                continue

            break

        self.phone_number = result.user_input
        return f"User's phone number is {result.user_input}"


@server.rtc_session(agent_name=DTMF_AGENT_DISPATCH_NAME)
async def entrypoint(ctx: JobContext) -> None:
    ctx.log_context_fields = {
        "room": ctx.room.name,
    }

    session: AgentSession = AgentSession(
        llm=inference.LLM("openai/gpt-4.1-mini"),
        stt=inference.STT("deepgram/nova-3"),
        tts=inference.TTS("inworld/inworld-tts-1"),
    )

    @session.on("metrics_collected")
    def _on_metrics_collected(ev: MetricsCollectedEvent) -> None:
        metrics.log_metrics(ev.metrics)

    async def log_usage() -> None:
        logger.info(f"Usage: {session.usage}")

    ctx.add_shutdown_callback(log_usage)

    await session.start(
        agent=DtmfAgent(),
        room=ctx.room,
    )


if __name__ == "__main__":
    cli.run_app(server)
