"""Agent builder module for generating files from Agentfile configuration."""

import json
import subprocess
from pathlib import Path

import yaml

from agentman.agentfile_parser import AgentfileConfig, AgentfileParser


class AgentBuilder:
    """Builds agent files from Agentfile configuration."""

    def __init__(self, config: AgentfileConfig, output_dir: str = "output", source_dir: str = "."):
        self.config = config
        self.output_dir = Path(output_dir)
        self.source_dir = Path(source_dir)
        # Check if prompt.txt exists in the source directory
        self.prompt_file_path = self.source_dir / "prompt.txt"
        self.has_prompt_file = self.prompt_file_path.exists()

    def build_all(self):
        """Build all generated files."""
        self._ensure_output_dir()
        self._copy_prompt_file()
        self._generate_python_agent()
        self._generate_config_yaml()
        self._generate_secrets_yaml()
        self._generate_dockerfile()
        self._generate_requirements_txt()
        self._generate_dockerignore()
        self._validate_output()

    def _ensure_output_dir(self):
        """Ensure output directory exists."""
        self.output_dir.mkdir(exist_ok=True)

    def _copy_prompt_file(self):
        """Copy prompt.txt to output directory if it exists."""
        if self.has_prompt_file:
            import shutil

            dest_path = self.output_dir / "prompt.txt"
            shutil.copy2(self.prompt_file_path, dest_path)

    def _generate_python_agent(self):
        """Generate the main Python agent file."""
        content = self._build_python_content()

        agent_file = self.output_dir / "agent.py"
        with open(agent_file, 'w', encoding='utf-8') as f:
            f.write(content)

    def _build_python_content(self) -> str:
        """Build the Python agent file content."""
        if self.config.framework == "agno":
            return self._build_agno_content()
        else:
            return self._build_fast_agent_content()

    def _build_fast_agent_content(self) -> str:
        """Build the Python agent file content for Fast-Agent framework."""
        lines = []

        # Imports
        lines.extend(
            [
                "import asyncio",
                "from mcp_agent.core.fastagent import FastAgent",
                "",
                "# Create the application",
                'fast = FastAgent("Generated by Agentman")',
                "",
            ]
        )

        # Agent definitions
        for agent in self.config.agents.values():
            lines.append(agent.to_decorator_string(self.config.default_model))

        # Router definitions
        for router in self.config.routers.values():
            lines.append(router.to_decorator_string(self.config.default_model))

        # Chain definitions
        for chain in self.config.chains.values():
            lines.append(chain.to_decorator_string())

        # Orchestrator definitions
        for orchestrator in self.config.orchestrators.values():
            lines.append(orchestrator.to_decorator_string(self.config.default_model))

        # Main function
        lines.extend(
            [
                "async def main() -> None:",
                "    async with fast.run() as agent:",
            ]
        )

        # Check if prompt.txt exists and add prompt loading
        if self.has_prompt_file:
            lines.extend(
                [
                    "        # Check if prompt.txt exists and load its content",
                    "        import os",
                    "        prompt_file = 'prompt.txt'",
                    "        if os.path.exists(prompt_file):",
                    "            with open(prompt_file, 'r', encoding='utf-8') as f:",
                    "                prompt_content = f.read().strip()",
                    "            if prompt_content:",
                    "                await agent(prompt_content)",
                    "            else:",
                    "                await agent()",
                    "        else:",
                    "            await agent()",
                ]
            )
        else:
            lines.extend(["        await agent()"])

        lines.extend(
            [
                "",
                "",
                'if __name__ == "__main__":',
                "    asyncio.run(main())",
            ]
        )

        return "\n".join(lines)

    def _build_agno_content(self) -> str:
        """Build the Python agent file content for Agno framework."""
        lines = []

        # Determine if we need advanced features
        has_multiple_agents = len(self.config.agents) > 1
        has_servers = bool(self.config.servers)

        # Enhanced imports based on features needed
        imports = [
            "import asyncio",
            "import os",
            "from agno.agent import Agent",
        ]

        # Add dotenv import for loading .env files
        imports.append("from dotenv import load_dotenv")
        imports.append("")
        imports.append("# Load environment variables from .env file")
        imports.append("load_dotenv()")
        imports.append("")

        # Model imports
        default_model = self.config.default_model or ""
        if "anthropic" in default_model.lower() or "claude" in default_model.lower():
            imports.append("from agno.models.anthropic import Claude")
        elif "openai" in default_model.lower() or "gpt" in default_model.lower():
            imports.append("from agno.models.openai import OpenAILike")
        elif "/" in default_model:
            # Custom model with provider prefix (e.g., "ollama/llama3", "groq/mixtral")
            imports.append("from agno.models.openai import OpenAILike")

        # Check agent models to determine what imports we need
        for agent in self.config.agents.values():
            agent_model = agent.model or default_model
            if agent_model:
                if "anthropic" in agent_model.lower() or "claude" in agent_model.lower():
                    if "from agno.models.anthropic import Claude" not in imports:
                        imports.append("from agno.models.anthropic import Claude")
                elif "openai" in agent_model.lower() or "gpt" in agent_model.lower():
                    if "from agno.models.openai import OpenAILike" not in imports:
                        imports.append("from agno.models.openai import OpenAILike")
                elif "/" in agent_model:
                    # Custom model with provider prefix
                    if "from agno.models.openai import OpenAILike" not in imports:
                        imports.append("from agno.models.openai import OpenAILike")

        if not any("anthropic" in imp or "openai" in imp for imp in imports):
            # Default to both if model is not specified or unclear
            imports.extend(
                [
                    "from agno.models.openai import OpenAILike",
                    "from agno.models.anthropic import Claude",
                ]
            )

        # Tool imports based on servers
        tool_imports = []
        if has_servers:
            # Map server types to appropriate tools
            for server_name, server in self.config.servers.items():
                if server_name in ["web_search", "search", "browser"]:
                    tool_imports.append("from agno.tools.duckduckgo import DuckDuckGoTools")
                elif server_name in ["finance", "yfinance", "stock"]:
                    tool_imports.append("from agno.tools.yfinance import YFinanceTools")
                elif server_name in ["file", "filesystem"]:
                    tool_imports.append("from agno.tools.file import FileTools")
                elif server_name in ["shell", "terminal"]:
                    tool_imports.append("from agno.tools.shell import ShellTools")
                elif server_name in ["python", "code"]:
                    tool_imports.append("from agno.tools.python import PythonTools")

        # Remove duplicates and add to imports
        for tool_import in sorted(set(tool_imports)):
            imports.append(tool_import)

        # Team imports if multiple agents
        if has_multiple_agents:
            imports.append("from agno.team.team import Team")

        # Advanced feature imports (always include for better examples)
        imports.extend(
            [
                "from agno.tools.reasoning import ReasoningTools",
                "# Optional: Uncomment for advanced features",
                "# from agno.storage.sqlite import SqliteStorage",
                "# from agno.memory.v2.db.sqlite import SqliteMemoryDb",
                "# from agno.memory.v2.memory import Memory",
                "# from agno.knowledge.url import UrlKnowledge",
                "# from agno.vectordb.lancedb import LanceDb",
            ]
        )

        lines.extend(imports + [""])

        # Generate agents with enhanced capabilities
        agent_vars = []
        for agent in self.config.agents.values():
            agent_var = f"{agent.name.lower().replace('-', '_')}_agent"
            agent_vars.append((agent_var, agent))

            lines.extend(
                [
                    f"# Agent: {agent.name}",
                    f"{agent_var} = Agent(",
                    f'    name="{agent.name}",',
                    f'    instructions="""{agent.instruction}""",',
                ]
            )

            # Add role if we have multiple agents
            if has_multiple_agents:
                role = f"Handle {agent.name.lower().replace('-', ' ')} requests"
                lines.append(f'    role="{role}",')

            # Add model
            model = agent.model or self.config.default_model
            if model:
                model_code = self._generate_agno_model_code(model)
                lines.append(f'    {model_code}')

            # Enhanced tools based on servers
            tools = []
            if agent.servers:
                for server_name in agent.servers:
                    if server_name in ["web_search", "search", "browser"]:
                        tools.append("DuckDuckGoTools()")
                    elif server_name in ["finance", "yfinance", "stock"]:
                        tools.append("YFinanceTools(stock_price=True, analyst_recommendations=True)")
                    elif server_name in ["file", "filesystem"]:
                        tools.append("FileTools()")
                    elif server_name in ["shell", "terminal"]:
                        tools.append("ShellTools()")
                    elif server_name in ["python", "code"]:
                        tools.append("PythonTools()")

            # Always add reasoning tools for better performance
            tools.append("ReasoningTools(add_instructions=True)")

            if tools:
                tools_str = ", ".join(tools)
                lines.append(f'    tools=[{tools_str}],')

            # Add other properties
            if not agent.use_history:
                lines.append("    add_history_to_messages=False,")
            else:
                lines.append("    add_history_to_messages=True,")

            if agent.human_input:
                lines.append("    human_input=True,")

            # Enhanced agent properties
            lines.extend(
                [
                    "    markdown=True,",
                    "    add_datetime_to_instructions=True,",
                    "    # Optional: Enable advanced features",
                    "    # storage=SqliteStorage(table_name='agent_sessions', db_file='tmp/agent.db'),",
                    "    # memory=Memory(model=Claude(id='claude-sonnet-4-20250514'), db=SqliteMemoryDb()),",
                    "    # enable_agentic_memory=True,",
                    ")",
                    "",
                ]
            )

        # Team creation for multi-agent scenarios
        if has_multiple_agents:
            team_name = "AgentTeam"
            lines.extend(
                [
                    "# Multi-Agent Team",
                    f"{team_name.lower()} = Team(",
                    f'    name="{team_name}",',
                    "    mode='coordinate',  # or 'sequential' for ordered execution",
                ]
            )

            # Use the first agent's model for team coordination
            if agent_vars:
                first_model = agent_vars[0][1].model or self.config.default_model
                model_code = self._generate_agno_model_code(first_model)
                lines.append(f'    {model_code}')

            # Add all agents as team members
            member_vars = [var for var, _ in agent_vars]
            members_str = ", ".join(member_vars)
            lines.append(f'    members=[{members_str}],')

            lines.extend(
                [
                    "    tools=[ReasoningTools(add_instructions=True)],",
                    "    instructions=[",
                    "        'Collaborate to provide comprehensive responses',",
                    "        'Consider multiple perspectives and expertise areas',",
                    "        'Present findings in a structured, easy-to-follow format',",
                    "        'Only output the final consolidated response',",
                    "    ],",
                    "    markdown=True,",
                    "    show_members_responses=True,",
                    "    enable_agentic_context=True,",
                    "    add_datetime_to_instructions=True,",
                    "    success_criteria='The team has provided a complete and accurate response.',",
                    ")",
                    "",
                ]
            )

        # Main function
        lines.extend(
            [
                "async def main() -> None:",
            ]
        )

        # Handle prompt file loading
        if self.has_prompt_file:
            lines.extend(
                [
                    "    # Check if prompt.txt exists and load its content",
                    "    import os",
                    "    prompt_file = 'prompt.txt'",
                    "    if os.path.exists(prompt_file):",
                    "        with open(prompt_file, 'r', encoding='utf-8') as f:",
                    "            prompt_content = f.read().strip()",
                ]
            )

        # Enhanced execution logic
        if has_multiple_agents:
            # Use team for multi-agent scenarios
            if self.has_prompt_file:
                lines.extend(
                    [
                        "        if prompt_content:",
                        f"            response = await {team_name.lower()}.arun(",
                        "                prompt_content,",
                        "                stream=True,",
                        "                show_full_reasoning=True,",
                        "                stream_intermediate_steps=True,",
                        "            )",
                        "            # Handle streaming response",
                        "            async for chunk in response:",
                        "                if hasattr(chunk, 'content') and chunk.content:",
                        "                    print(chunk.content, end='', flush=True)",
                        "            print()  # Add final newline",
                        "        else:",
                        f"            response = await {team_name.lower()}.arun(",
                        "                'Hello! How can our team help you today?',",
                        "                stream=True,",
                        "                show_full_reasoning=True,",
                        "                stream_intermediate_steps=True,",
                        "            )",
                        "            # Handle streaming response",
                        "            async for chunk in response:",
                        "                if hasattr(chunk, 'content') and chunk.content:",
                        "                    print(chunk.content, end='', flush=True)",
                        "            print()  # Add final newline",
                        "    else:",
                        f"        response = await {team_name.lower()}.arun(",
                        "            'Hello! How can our team help you today?',",
                        "            stream=True,",
                        "            show_full_reasoning=True,",
                        "            stream_intermediate_steps=True,",
                        "        )",
                        "        # Handle streaming response",
                        "        async for chunk in response:",
                        "            if hasattr(chunk, 'content') and chunk.content:",
                        "                print(chunk.content, end='', flush=True)",
                        "        print()  # Add final newline",
                    ]
                )
            else:
                lines.extend(
                    [
                        f"    response = await {team_name.lower()}.arun(",
                        "        'Hello! How can our team help you today?',",
                        "        stream=True,",
                        "        show_full_reasoning=True,",
                        "        stream_intermediate_steps=True,",
                        "    )",
                        "    # Handle streaming response",
                        "    async for chunk in response:",
                        "        if hasattr(chunk, 'content') and chunk.content:",
                        "            print(chunk.content, end='', flush=True)",
                        "    print()  # Add final newline",
                    ]
                )

        elif agent_vars:
            # Single agent scenario with enhanced features
            primary_agent_var, primary_agent = agent_vars[0]
            if self.has_prompt_file:
                lines.extend(
                    [
                        "        if prompt_content:",
                        f"            response = await {primary_agent_var}.arun(",
                        "                prompt_content,",
                        "                stream=True,",
                        "                show_full_reasoning=True,",
                        "                stream_intermediate_steps=True,",
                        "            )",
                        "            # Handle streaming response",
                        "            async for chunk in response:",
                        "                if hasattr(chunk, 'content') and chunk.content:",
                        "                    print(chunk.content, end='', flush=True)",
                        "            print()  # Add final newline",
                        "        else:",
                        f"            response = await {primary_agent_var}.arun(",
                        "                'Hello! How can I help you today?',",
                        "                stream=True,",
                        "                show_full_reasoning=True,",
                        "                stream_intermediate_steps=True,",
                        "            )",
                        "            # Handle streaming response",
                        "            async for chunk in response:",
                        "                if hasattr(chunk, 'content') and chunk.content:",
                        "                    print(chunk.content, end='', flush=True)",
                        "            print()  # Add final newline",
                        "    else:",
                        f"        response = await {primary_agent_var}.arun(",
                        "            'Hello! How can I help you today?',",
                        "            stream=True,",
                        "            show_full_reasoning=True,",
                        "            stream_intermediate_steps=True,",
                        "        )",
                        "        # Handle streaming response",
                        "        async for chunk in response:",
                        "            if hasattr(chunk, 'content') and chunk.content:",
                        "                print(chunk.content, end='', flush=True)",
                        "        print()  # Add final newline",
                    ]
                )
            else:
                lines.extend(
                    [
                        f"    response = await {primary_agent_var}.arun(",
                        "        'Hello! How can I help you today?',",
                        "        stream=True,",
                        "        show_full_reasoning=True,",
                        "        stream_intermediate_steps=True,",
                        "    )",
                        "    # Handle streaming response",
                        "    async for chunk in response:",
                        "        if hasattr(chunk, 'content') and chunk.content:",
                        "            print(chunk.content, end='', flush=True)",
                        "    print()  # Add final newline",
                    ]
                )

        else:
            lines.extend(
                [
                    "    print('No agents defined')",
                ]
            )

        lines.extend(
            [
                "",
                'if __name__ == "__main__":',
                "    asyncio.run(main())",
            ]
        )

        return "\n".join(lines)

    def _generate_config_yaml(self):
        """Generate the configuration file based on framework."""
        if self.config.framework == "agno":
            self._generate_agno_config()
        else:
            self._generate_fast_agent_config()

    def _generate_fast_agent_config(self):
        """Generate the fastagent.config.yaml file."""
        config_data = {
            "default_model": self.config.default_model or "haiku",
            "logger": {
                "level": "info",
                "progress_display": True,
                "show_chat": True,
                "show_tools": True,
                "truncate_tools": True,
            },
        }

        if self.config.servers:
            config_data["mcp"] = {
                "servers": {name: server.to_config_dict() for name, server in self.config.servers.items()}
            }

        config_file = self.output_dir / "fastagent.config.yaml"
        with open(config_file, 'w', encoding='utf-8') as f:
            yaml.dump(config_data, f, default_flow_style=False, sort_keys=False)

    def _generate_agno_config(self):
        """Generate configuration for Agno framework (if needed)."""
        # Agno typically doesn't need a separate config file
        # Configuration is done through environment variables and code
        pass

    def _generate_secrets_yaml(self):
        """Generate the secrets file based on framework."""
        if self.config.framework == "agno":
            self._generate_agno_env_file()
        else:
            self._generate_fast_agent_secrets()

    def _generate_fast_agent_secrets(self):
        """Generate the fastagent.secrets.yaml template file."""
        secrets_data = {}
        mcp_servers_env = {}

        # Process secrets based on their type
        for secret in self.config.secrets:
            if isinstance(secret, str):
                # Simple secret reference
                self._process_simple_secret(secret, secrets_data, mcp_servers_env)
            elif hasattr(secret, 'value'):
                # SecretValue with inline value
                self._process_secret_value(secret, secrets_data, mcp_servers_env)
            elif hasattr(secret, 'values'):
                # SecretContext with multiple key-value pairs
                self._process_secret_context(secret, secrets_data)

        # Add MCP servers environment if any
        if mcp_servers_env:
            secrets_data["mcp"] = {"servers": mcp_servers_env}

        secrets_file = self.output_dir / "fastagent.secrets.yaml"
        with open(secrets_file, 'w', encoding='utf-8') as f:
            f.write("# FastAgent Secrets Configuration\n")
            f.write("# WARNING: Keep this file secure and never commit to version control\n\n")
            f.write(
                "# Alternatively set OPENAI_API_KEY and ANTHROPIC_API_KEY "
                "environment variables. Config file takes precedence.\n\n"
            )
            yaml.dump(secrets_data, f, default_flow_style=False, sort_keys=False)

    def _generate_agno_env_file(self):
        """Generate .env template file for Agno framework."""
        env_lines = [
            "# Agno Environment Configuration",
            "# WARNING: Keep this file secure and never commit to version control",
            "",
            "# API Keys - uncomment and add your keys",
        ]

        # Add environment variables for custom model providers
        custom_providers = self._get_custom_model_providers()
        if custom_providers:
            env_lines.extend(["", "# Custom Model Provider Configuration"])
            for provider in sorted(custom_providers):
                provider_upper = provider.upper()
                env_lines.extend(
                    [
                        f"# {provider_upper}_API_KEY=your-{provider}-api-key",
                        f"# {provider_upper}_BASE_URL=your-{provider}-base-url",
                    ]
                )

        # Process secrets to generate environment variables
        for secret in self.config.secrets:
            if isinstance(secret, str):
                if secret in ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "GROQ_API_KEY", "GOOGLE_API_KEY"]:
                    env_lines.append(f"# {secret}=your-key-here")
                else:
                    env_lines.append(f"# {secret}=your-value-here")
            elif hasattr(secret, 'value'):
                # SecretValue with inline value
                env_lines.append(f"{secret.name}={secret.value}")
            elif hasattr(secret, 'values'):
                # SecretContext with multiple key-value pairs
                env_lines.append(f"# {secret.name.upper()} configuration")
                for key, value in secret.values.items():
                    env_lines.append(f"{secret.name.upper()}_{key}={value}")

        env_file = self.output_dir / ".env"
        with open(env_file, 'w', encoding='utf-8') as f:
            f.write("\n".join(env_lines) + "\n")

    def _process_simple_secret(self, secret: str, secrets_data: dict, mcp_servers_env: dict):
        """Process a simple secret reference."""
        if secret == "OPENAI_API_KEY":
            if "openai" not in secrets_data:
                secrets_data["openai"] = {}
            secrets_data["openai"]["api_key"] = "<your-api-key-here>"
        elif secret == "ANTHROPIC_API_KEY":
            if "anthropic" not in secrets_data:
                secrets_data["anthropic"] = {}
            secrets_data["anthropic"]["api_key"] = "<your-api-key-here>"
        elif secret == "AZURE_OPENAI_API_KEY":
            if "azure" not in secrets_data:
                secrets_data["azure"] = {}
            secrets_data["azure"]["api_key"] = "<your-azure-api-key-here>"
        elif secret == "ALIYUN_API_KEY":
            if "aliyun" not in secrets_data:
                secrets_data["aliyun"] = {}
            secrets_data["aliyun"]["api_key"] = "<your-aliyun-api-key-here>"
        else:
            # Handle server-specific environment variables
            server_found = False
            for server_name, server in self.config.servers.items():
                if hasattr(server, 'env') and server.env and secret in server.env:
                    if server_name not in mcp_servers_env:
                        mcp_servers_env[server_name] = {"env": {}}
                    mcp_servers_env[server_name]["env"][secret] = f"<your_{secret.lower()}_here>"
                    server_found = True
                    break

            if not server_found:
                # Generic environment variable
                if "environment" not in mcp_servers_env:
                    mcp_servers_env["environment"] = {"env": {}}
                mcp_servers_env["environment"]["env"][secret] = f"<your_{secret.lower()}_here>"

    def _process_secret_value(self, secret, secrets_data: dict, mcp_servers_env: dict):
        """Process a secret with an inline value."""
        secret_name = secret.name
        secret_value = secret.value

        if secret_name == "OPENAI_API_KEY":
            if "openai" not in secrets_data:
                secrets_data["openai"] = {}
            secrets_data["openai"]["api_key"] = secret_value
        elif secret_name == "ANTHROPIC_API_KEY":
            if "anthropic" not in secrets_data:
                secrets_data["anthropic"] = {}
            secrets_data["anthropic"]["api_key"] = secret_value
        elif secret_name == "AZURE_OPENAI_API_KEY":
            if "azure" not in secrets_data:
                secrets_data["azure"] = {}
            secrets_data["azure"]["api_key"] = secret_value
        elif secret_name == "ALIYUN_API_KEY":
            if "aliyun" not in secrets_data:
                secrets_data["aliyun"] = {}
            secrets_data["aliyun"]["api_key"] = secret_value
        else:
            # Handle server-specific environment variables
            server_found = False
            for server_name, server in self.config.servers.items():
                if hasattr(server, 'env') and server.env and secret_name in server.env:
                    if server_name not in mcp_servers_env:
                        mcp_servers_env[server_name] = {"env": {}}
                    mcp_servers_env[server_name]["env"][secret_name] = secret_value
                    server_found = True
                    break

            if not server_found:
                # Generic environment variable
                if "environment" not in mcp_servers_env:
                    mcp_servers_env["environment"] = {"env": {}}
                mcp_servers_env["environment"]["env"][secret_name] = secret_value

    def _process_secret_context(self, secret, secrets_data: dict):
        """Process a secret context with multiple key-value pairs."""
        context_name = secret.name.lower()

        if context_name not in secrets_data:
            secrets_data[context_name] = {}

        for key, value in secret.values.items():
            secrets_data[context_name][key.lower()] = value

    def _generate_dockerfile(self):
        """Generate the Dockerfile."""
        lines = []

        # Start with FROM instruction
        lines.extend([f"FROM {self.config.base_image}", ""])

        # Copy requirements and install Python dependencies
        lines.extend(
            [
                "# Copy requirements and install Python dependencies",
                "COPY requirements.txt .",
                "RUN pip install --no-cache-dir -r requirements.txt",
                "",
            ]
        )

        # Add all other Dockerfile instructions in order (except FROM)
        # We'll handle EXPOSE and CMD at the end in their proper positions
        for instruction in self.config.dockerfile_instructions:
            if instruction.instruction not in ["FROM", "EXPOSE", "CMD"]:
                lines.append(instruction.to_dockerfile_line())

        # Add a blank line if we have custom instructions
        custom_instructions = [
            inst for inst in self.config.dockerfile_instructions if inst.instruction not in ["FROM", "EXPOSE", "CMD"]
        ]
        if custom_instructions:
            lines.append("")

        # Set working directory if not already set by custom instructions
        workdir_set = any(inst.instruction == "WORKDIR" for inst in self.config.dockerfile_instructions)
        if not workdir_set:
            lines.extend(["WORKDIR /app", ""])

        # Copy application files
        copy_lines = [
            "# Copy application files",
            "COPY agent.py .",
        ]

        # Add framework-specific configuration files
        if self.config.framework == "agno":
            copy_lines.append("COPY .env .")
        else:
            copy_lines.extend(
                [
                    "COPY fastagent.config.yaml .",
                    "COPY fastagent.secrets.yaml .",
                ]
            )

        # Add prompt.txt copy if it exists
        if self.has_prompt_file:
            copy_lines.append("COPY prompt.txt .")

        copy_lines.append("")
        lines.extend(copy_lines)

        # Add EXPOSE instructions from custom dockerfile instructions first
        expose_instructions = [inst for inst in self.config.dockerfile_instructions if inst.instruction == "EXPOSE"]
        if expose_instructions:
            for instruction in expose_instructions:
                lines.append(instruction.to_dockerfile_line())
            lines.append("")

        # Add EXPOSE from config.expose_ports if not already handled
        if self.config.expose_ports and not expose_instructions:
            expose_lines = [f"EXPOSE {port}" for port in self.config.expose_ports]
            lines.extend(expose_lines)
            lines.append("")

        # Add CMD instructions from custom dockerfile instructions first
        cmd_instructions = [inst for inst in self.config.dockerfile_instructions if inst.instruction == "CMD"]
        if cmd_instructions:
            for instruction in cmd_instructions:
                lines.append(instruction.to_dockerfile_line())
        elif self.config.cmd:
            # Default command from config
            cmd_str = json.dumps(self.config.cmd)
            lines.append(f"CMD {cmd_str}")

        dockerfile = self.output_dir / "Dockerfile"
        with open(dockerfile, 'w', encoding='utf-8') as f:
            f.write("\n".join(lines))

    def _generate_requirements_txt(self):
        """Generate the requirements.txt file based on framework."""
        if self.config.framework == "agno":
            requirements = self._get_agno_requirements()
        else:
            requirements = self._get_fast_agent_requirements()

        # Remove duplicates and sort
        requirements = sorted(list(set(requirements)))

        req_file = self.output_dir / "requirements.txt"
        with open(req_file, 'w', encoding='utf-8') as f:
            f.write("\n".join(requirements) + "\n")

    def _get_fast_agent_requirements(self):
        """Get requirements for Fast-Agent framework."""
        requirements = [
            "fast-agent-mcp>=0.2.33",
            "deprecated>=1.2.18",
        ]

        # Add additional requirements based on servers used
        server_requirements = {
            "fetch": [],
            "filesystem": [],
            "brave": [],
            "postgres": [],
            "sqlite": [],
        }

        for server_name in self.config.servers.keys():
            if server_name in server_requirements:
                requirements.extend(server_requirements[server_name])

        return requirements

    def _get_agno_requirements(self):
        """Get requirements for Agno framework with enhanced tool support."""
        requirements = [
            "agno>=1.6.0",
        ]

        # Add model-specific requirements
        all_models = set()
        if self.config.default_model:
            all_models.add(self.config.default_model)

        # Collect all agent models
        for agent in self.config.agents.values():
            if agent.model:
                all_models.add(agent.model)

        # Add requirements based on model types
        for model in all_models:
            model_lower = model.lower()
            if "anthropic" in model_lower or "claude" in model_lower:
                requirements.append("anthropic")
            elif "openai" in model_lower or "gpt" in model_lower:
                requirements.append("openai")
            elif "groq" in model_lower:
                requirements.append("groq")
            elif "/" in model:
                # Custom OpenAI-like model - add openai for OpenAILike class
                requirements.append("openai")
                # Add specific provider requirements if known
                provider = model.split("/")[0].lower()
                if provider == "groq":
                    requirements.append("groq")
                elif provider == "together":
                    requirements.append("together")
                elif provider == "anthropic":
                    requirements.append("anthropic")
            else:
                # Default case - use OpenAILike (matches _generate_agno_model_code logic)
                requirements.append("openai")
                requirements.append("anthropic")

        # Add tool-specific requirements based on servers
        tool_requirements = {
            # Search and web tools
            "web_search": ["duckduckgo-search"],
            "search": ["duckduckgo-search"],
            "browser": ["duckduckgo-search"],
            "google": ["google-search-results"],
            # Finance tools
            "finance": ["yfinance"],
            "yfinance": ["yfinance"],
            "stock": ["yfinance"],
            # File and system tools
            "file": [],  # Built into agno
            "filesystem": [],  # Built into agno
            "shell": [],  # Built into agno
            "python": [],  # Built into agno
            # Database tools
            "postgres": ["psycopg2-binary", "sqlalchemy"],
            "sqlite": ["sqlalchemy"],
            "database": ["sqlalchemy"],
            # Communication tools
            "email": ["smtplib"],  # Usually built-in
            "slack": ["slack-sdk"],
            "discord": ["discord.py"],
            # Advanced features
            "knowledge": ["lancedb", "tantivy"],
            "vector": ["lancedb", "tantivy"],
            "storage": ["sqlalchemy"],
            "memory": ["sqlalchemy"],
        }

        # Check global MCP servers
        for server_name in self.config.servers.keys():
            server_reqs = tool_requirements.get(server_name, [])
            requirements.extend(server_reqs)

        # Check individual agent servers
        for agent in self.config.agents.values():
            for server_name in agent.servers:
                server_reqs = tool_requirements.get(server_name, [])
                requirements.extend(server_reqs)

        # Always include core advanced features
        requirements.extend(
            [
                # Core MCP support
                "mcp",
                # Environment file support
                "python-dotenv",
                # Optional but commonly used packages
                "sqlalchemy",  # For storage and memory
                "lancedb",  # For knowledge and vector databases
                "tantivy",  # For hybrid search
            ]
        )

        # Multi-agent scenarios get additional dependencies
        if len(self.config.agents) > 1:
            requirements.extend(
                [
                    "asyncio",  # Usually built-in but explicit for clarity
                ]
            )

        return requirements

    def _generate_dockerignore(self):
        """Generate the .dockerignore file."""
        ignore_patterns = [
            "# Python",
            "__pycache__/",
            "*.py[cod]",
            "*$py.class",
            "*.so",
            ".Python",
            "build/",
            "develop-eggs/",
            "dist/",
            "downloads/",
            "eggs/",
            ".eggs/",
            "lib/",
            "lib64/",
            "parts/",
            "sdist/",
            "var/",
            "wheels/",
            "*.egg-info/",
            ".installed.cfg",
            "*.egg",
            "",
            "# Virtual Environment",
            ".venv",
            "env/",
            "venv/",
            "ENV/",
            "",
            "# IDE",
            ".idea/",
            ".vscode/",
            "*.swp",
            "*.swo",
            "",
            "# Git",
            ".git/",
            ".gitignore",
            "",
            "# Logs",
            "*.log",
            "logs/",
            "",
            "# OS",
            ".DS_Store",
            "Thumbs.db",
        ]

        dockerignore = self.output_dir / ".dockerignore"
        with open(dockerignore, 'w', encoding='utf-8') as f:
            f.write("\n".join(ignore_patterns))

    def _validate_output(self):
        """Validate that all required files were generated."""
        # Skip validation in test environments or when fast-agent is not available
        try:
            subprocess.run(["fast-agent", "check"], check=True, cwd=self.output_dir, capture_output=True)
        except (subprocess.CalledProcessError, FileNotFoundError) as e:
            # If fast-agent is not available or validation fails, just warn but don't fail
            print(f"⚠️  Validation skipped: {e}")
            pass

    def _generate_agno_model_code(self, model: str) -> str:
        """Generate the appropriate model instantiation code for Agno framework."""
        if not model:
            return 'model=Claude(id="anthropic/claude-3-sonnet-20241022"),'

        model_lower = model.lower()

        # Anthropic models
        if "anthropic" in model_lower or "claude" in model_lower:
            return f'model=Claude(id="{model}"),'

        # OpenAI models
        elif "openai" in model_lower or "gpt" in model_lower:
            model_code = 'model=OpenAILike(\n'
            model_code += f'        id="{model}",\n'
            model_code += '        api_key=os.getenv("OPENAI_API_KEY"),\n'
            model_code += '        base_url=os.getenv("OPENAI_BASE_URL"),\n'
            model_code += '    ),'
            return model_code

        # Custom OpenAI-like models (with provider prefix)
        elif "/" in model:
            provider, model_name = model.split("/", 1)
            provider_upper = provider.upper()

            # Generate OpenAILike model with custom configuration
            model_code = 'model=OpenAILike(\n'
            model_code += f'        id="{model}",\n'
            model_code += f'        api_key=os.getenv("{provider_upper}_API_KEY"),\n'
            model_code += f'        base_url=os.getenv("{provider_upper}_BASE_URL"),\n'
            model_code += '    ),'
            return model_code

        # Default to OpenAILike for unrecognized patterns
        else:
            # Check if we have OpenAI-like environment variables configured
            has_openai_config = any(
                (isinstance(secret, str) and secret in ["OPENAI_API_KEY", "OPENAI_BASE_URL"])
                or (hasattr(secret, 'name') and secret.name in ["OPENAI_API_KEY", "OPENAI_BASE_URL"])
                for secret in self.config.secrets
            )

            if has_openai_config:
                # Use OpenAI environment variables for custom models
                model_code = 'model=OpenAILike(\n'
                model_code += f'        id="{model}",\n'
                model_code += '        api_key=os.getenv("OPENAI_API_KEY"),\n'
                model_code += '        base_url=os.getenv("OPENAI_BASE_URL"),\n'
                model_code += '    ),'
                return model_code
            else:
                return f'model=OpenAILike(id="{model}"),'

    def _get_custom_model_providers(self) -> set:
        """Extract custom model providers from all models used."""
        providers = set()

        # Check default model
        if self.config.default_model and "/" in self.config.default_model:
            provider = self.config.default_model.split("/")[0]
            # Skip official providers that don't need custom base URLs
            if provider.lower() not in ["openai", "anthropic"]:
                providers.add(provider)

        # Check agent models
        for agent in self.config.agents.values():
            if agent.model and "/" in agent.model:
                provider = agent.model.split("/")[0]
                # Skip official providers that don't need custom base URLs
                if provider.lower() not in ["openai", "anthropic"]:
                    providers.add(provider)

        return providers


def build_from_agentfile(agentfile_path: str, output_dir: str = "output") -> None:
    """Build agent files from an Agentfile."""
    parser = AgentfileParser()
    config = parser.parse_file(agentfile_path)

    # Extract source directory from agentfile path
    source_dir = Path(agentfile_path).parent

    builder = AgentBuilder(config, output_dir, source_dir)
    builder.build_all()

    print(f"✅ Generated agent files in {output_dir}/")
    print("   - agent.py")

    # Show framework-specific config files
    if config.framework == "agno":
        print("   - .env")
    else:
        print("   - fastagent.config.yaml")
        print("   - fastagent.secrets.yaml")

    print("   - Dockerfile")
    print("   - requirements.txt")
    print("   - .dockerignore")

    # Check if prompt.txt was copied
    if builder.has_prompt_file:
        print("   - prompt.txt")
