"""Agent resource setup — shared by CLI and job executor."""

import os
from collections.abc import Callable
from dataclasses import dataclass
from pathlib import Path

import structlog

from fliiq.runtime.agent.tools import ToolRegistry
from fliiq.runtime.llm.providers import BaseLLM, LLMConfig

log = structlog.get_logger()


@dataclass
class AgentResources:
    """Resources needed to run the agent (before prompt-dependent setup)."""

    llm: BaseLLM
    tools: ToolRegistry
    soul: str
    user_soul: str | None
    playbook_content: str | None
    memory_context: str | None
    skill_info: list[dict]
    project_root: Path
    user_profile: dict | None = None
    fliiq_email: str | None = None
    mcp_manager: object | None = None


def resolve_llm_config(
    provider_override: str | None = None,
    model_override: str | None = None,
) -> LLMConfig:
    """Resolve LLM config from env vars.

    Resolution order for model:
      1. model_override param (from CLI --model flag, alias-resolved)
      2. Provider-specific env var (ANTHROPIC_MODEL, OPENAI_MODEL, GEMINI_MODEL)
      3. DEFAULT_MODELS hardcoded default

    Resolution order for provider:
      1. provider_override param (from CLI --provider flag)
      2. FLIIQ_PROVIDER env var (persistent default)
      3. First API key found: Anthropic -> OpenAI -> Gemini

    Raises RuntimeError if no API key found.
    """
    from fliiq.runtime.llm.providers import LLMProvider, resolve_model_alias

    # Resolve model alias if provided
    resolved_model = resolve_model_alias(model_override) if model_override else None

    providers = [
        ("ANTHROPIC_API_KEY", LLMProvider.ANTHROPIC, "ANTHROPIC_MODEL", "ANTHROPIC_BASE_URL"),
        ("OPENAI_API_KEY", LLMProvider.OPENAI, "OPENAI_MODEL", "OPENAI_BASE_URL"),
        ("GEMINI_API_KEY", LLMProvider.GEMINI, "GEMINI_MODEL", "GEMINI_BASE_URL"),
    ]

    # Determine explicit provider selection (CLI flag or env var)
    explicit = provider_override or os.environ.get("FLIIQ_PROVIDER") or None
    if explicit:
        explicit = explicit.strip().lower()
        for key_var, provider, model_var, url_var in providers:
            if provider.value == explicit:
                key = os.environ.get(key_var)
                if not key:
                    raise RuntimeError(
                        f"Provider '{explicit}' selected but {key_var} is not set."
                    )
                return LLMConfig(
                    provider=provider,
                    api_key=key,
                    model=resolved_model or os.environ.get(model_var) or None,
                    base_url=os.environ.get(url_var) or None,
                )
        valid = ", ".join(p.value for _, p, _, _ in providers)
        raise RuntimeError(f"Unknown provider '{explicit}'. Valid: {valid}")

    # Default: first key found
    for key_var, provider, model_var, url_var in providers:
        key = os.environ.get(key_var)
        if key:
            return LLMConfig(
                provider=provider,
                api_key=key,
                model=resolved_model or os.environ.get(model_var) or None,
                base_url=os.environ.get(url_var) or None,
            )

    raise RuntimeError(
        "No LLM API key found. Set one of: ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY"
    )


async def setup_agent_resources(
    mode: str,
    llm_config: LLMConfig,
    project_root: Path | None = None,
    ask_user_handler: Callable | None = None,
    ask_user_choice_handler: Callable | None = None,
    plan_complete_handler: Callable | None = None,
) -> AgentResources:
    """Set up agent resources (LLM, tools, soul, playbook, memory).

    Raises RuntimeError on failure (caller decides how to surface it).
    """
    from fliiq.runtime.llm.providers import create_llm
    from fliiq.runtime.planning.playbook_loader import load_soul
    from fliiq.runtime.skills.loader import discover_skills

    if project_root is None:
        from fliiq.runtime.config import resolve_fliiq_dir

        project_root = resolve_fliiq_dir().parent
    llm = create_llm(llm_config)

    # 1. Load identity
    soul, user_soul = load_soul(project_root=project_root)

    # 2. Discover skills + build tool registry
    skills = discover_skills(project_root=project_root)
    tools = ToolRegistry()

    for skill in skills.values():
        tools.register_skill(skill)

    tools.register_builtins(
        ask_user_handler=ask_user_handler,
        ask_user_choice_handler=ask_user_choice_handler,
        plan_complete_handler=plan_complete_handler,
        mode=mode,
        project_root=project_root,
    )

    # 3. Build skill info for prompt
    skill_info = [{"name": s.name, "description": s.description} for s in skills.values()]

    # 4. Connect MCP servers (if configured)
    mcp_manager = None
    try:
        from fliiq.runtime.mcp.client import MCPManager, load_mcp_config

        mcp_config = load_mcp_config(project_root)
        if mcp_config.get("servers"):
            mcp_manager = MCPManager()
            mcp_tool_infos = await mcp_manager.connect_all(mcp_config, tools)
            skill_info.extend(mcp_tool_infos)
    except Exception as e:
        log.debug("mcp_setup_skipped", error=str(e))

    # 5. Load memory context (non-fatal if missing)
    memory_context = _load_memory_context(project_root)

    # 6. Load user profile (non-fatal if missing)
    from fliiq.runtime.config import load_user_profile

    user_profile = load_user_profile() or None

    # 7. Resolve Fliiq's own bot email (non-fatal if not configured)
    from fliiq.runtime.google_auth import get_fliiq_email

    fliiq_email = get_fliiq_email()

    return AgentResources(
        llm=llm,
        tools=tools,
        soul=soul,
        user_soul=user_soul,
        playbook_content=None,  # Set after domain detection (requires prompt)
        memory_context=memory_context,
        skill_info=skill_info,
        project_root=project_root,
        user_profile=user_profile,
        fliiq_email=fliiq_email,
        mcp_manager=mcp_manager,
    )


def _load_memory_context(project_root: Path) -> str | None:
    """Load memory context from .fliiq/memory/. Returns None on any failure."""
    try:
        from fliiq.runtime.memory.manager import MemoryManager
        from fliiq.runtime.memory.retrieval import load_recent_daily

        manager = MemoryManager.from_project_root(project_root)
        mem_parts = []

        curated = manager.load_curated()
        if curated:
            mem_parts.append(f"### Long-term Memory\n{curated}")

        recent = load_recent_daily(manager, days=3)
        if recent:
            mem_parts.append(f"### Recent Activity\n{recent}")

        skill_memories = manager.list_skill_memories()
        if skill_memories:
            mem_parts.append(
                "### Available Skill Memories\n"
                "Load these on-demand with memory_read when relevant:\n"
                + "\n".join(f"- {s}" for s in skill_memories)
            )

        if mem_parts:
            return "\n\n".join(mem_parts)
    except Exception as e:
        log.debug("memory_load_skipped", error=str(e))

    return None
