"""Phase 2: Agent fleet creation (AD-517).

Creates agent pools, builds the codebase index, refreshes intent
descriptors, wires strategy advisors, and spawns red-team agents.

Note: The AgentOnboardingService is created in start() *before* this
phase runs, because wire_agent (called during pool creation) depends
on self.onboarding being set.
"""

from __future__ import annotations

import logging
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable

from probos.startup.results import AgentFleetResult
from probos.substrate.identity import generate_pool_ids

if TYPE_CHECKING:
    from probos.cognitive.llm_client import BaseLLMClient
    from probos.config import SystemConfig
    from probos.cognitive.decomposer import IntentDecomposer
    from probos.substrate.pool import ResourcePool

logger = logging.getLogger(__name__)


async def create_agent_fleet(
    *,
    config: "SystemConfig",
    pools: dict[str, "ResourcePool"],
    llm_client: "BaseLLMClient",
    decomposer: "IntentDecomposer",
    strategy_advisor: Any,
    runtime: Any,  # ProbOSRuntime reference for create_pool
    create_pool_fn: Callable[..., Any],
    spawn_red_team_fn: Callable[..., Any],
    collect_intent_descriptors_fn: Callable[[], list[Any]],
) -> AgentFleetResult:
    """Create agent pools, build codebase index, wire strategy advisors."""
    logger.info("Startup [agent_fleet]: starting")

    # Start default pools (Phase 14c: deterministic agent IDs)
    _builtin_pools = [
        ("system", "system_heartbeat", 2),
        ("filesystem", "file_reader", 3),
        ("filesystem_writers", "file_writer", 3),
        ("directory", "directory_list", 3),
        ("search", "file_search", 3),
        ("shell", "shell_command", 3),
        ("http", "http_fetch", 3),
    ]
    for pool_name, agent_type, size in _builtin_pools:
        ids = generate_pool_ids(agent_type, pool_name, size)
        await create_pool_fn(pool_name, agent_type, target_size=size, agent_ids=ids)

    # Introspect pool (needs runtime kwarg)
    ids = generate_pool_ids("introspect", "introspect", 2)
    await create_pool_fn("introspect", "introspect", target_size=2, agent_ids=ids, runtime=runtime)

    # NL-to-Graph Query pool (AD-691) — single-agent dispatcher onto runtime.nl_graph_query
    # BF-262: removed runtime.nl_graph_query guard (service wired in finalize, after fleet)
    if (
        getattr(config, "nl_graph_query", None)
        and config.nl_graph_query.enabled
    ):
        ids = generate_pool_ids("nl_graph_query", "nl_graph_query", 1)
        await create_pool_fn(
            "nl_graph_query", "nl_graph_query",
            target_size=1, agent_ids=ids, runtime=runtime,
        )

    # Bundled CognitiveAgent pools (Phase 22, AD-252)
    if config.utility_agents.enabled:
        _utility_pools = [
            ("web_search", "web_search", 2),
            ("page_reader", "page_reader", 2),
            ("weather", "weather", 2),
            ("news", "news", 2),
            ("translator", "translator", 2),
            ("summarizer", "summarizer", 2),
            ("calculator", "calculator", 2),
            ("todo_manager", "todo_manager", 2),
            ("note_taker", "note_taker", 2),
            ("scheduler", "scheduler", 2),
        ]
        for pool_name, agent_type, size in _utility_pools:
            ids = generate_pool_ids(agent_type, pool_name, size)
            await create_pool_fn(
                pool_name, agent_type, target_size=size,
                agent_ids=ids, llm_client=llm_client, runtime=runtime,
            )

    # Engineering team — Builder Agent (AD-302)
    if config.utility_agents.enabled:
        ids = generate_pool_ids("builder", "builder", 1)
        await create_pool_fn(
            "builder", "builder", target_size=1,
            agent_ids=ids, llm_client=llm_client, runtime=runtime,
        )

    # AD-476 — opt-in SWE specialist pools.
    if (
        getattr(config, "swe_specialists", None)
        and config.swe_specialists.enabled
    ):
        pool_size = config.swe_specialists.pool_size_per_specialty
        for specialty_template in (
            "backend_swe", "frontend_swe", "test_swe",
            "infrastructure_swe", "data_swe",
        ):
            try:
                ids = generate_pool_ids(specialty_template, specialty_template, pool_size)
                await create_pool_fn(
                    specialty_template, specialty_template,
                    target_size=pool_size,
                    agent_ids=ids,
                    llm_client=llm_client,
                    runtime=runtime,
                )
                logger.info(
                    "AD-476: spawned %s pool (target_size=%d)",
                    specialty_template, pool_size,
                )
            except Exception as exc:  # tier-2 log-and-degrade
                logger.warning(
                    "AD-476: failed to spawn %s pool: %s — continuing",
                    specialty_template, exc,
                )
    else:
        logger.info(
            "AD-476: SWE specialist pools disabled "
            "(config.swe_specialists.enabled=False)"
        )

    # Science team — Architect Agent (AD-307)
    if config.utility_agents.enabled:
        ids = generate_pool_ids("architect", "architect", 1)
        await create_pool_fn(
            "architect", "architect", target_size=1,
            agent_ids=ids, llm_client=llm_client, runtime=runtime,
        )

    # Science team — Scout Agent (AD-394)
    if config.utility_agents.enabled:
        ids = generate_pool_ids("scout", "scout", 1)
        await create_pool_fn(
            "scout", "scout", target_size=1,
            agent_ids=ids, llm_client=llm_client, runtime=runtime,
        )

    # Bridge crew — Counselor (AD-398, AD-503: profile store wiring)
    if config.utility_agents.enabled:
        from probos.cognitive.counselor import CounselorProfileStore

        counselor_profile_store = CounselorProfileStore(
            data_dir=runtime._data_dir,
        )
        await counselor_profile_store.start()
        runtime._counselor_profile_store = counselor_profile_store

        ids = generate_pool_ids("counselor", "counselor", 1)
        await create_pool_fn(
            "counselor", "counselor", target_size=1,
            agent_ids=ids, llm_client=llm_client, runtime=runtime,
            profile_store=counselor_profile_store,
        )

    # Bridge crew — Yeoman (AD-766: Captain's personal assistant, singleton)
    if config.utility_agents.enabled:
        ids = generate_pool_ids("yeoman", "yeoman", 1)
        await create_pool_fn(
            "yeoman", "yeoman", target_size=1,
            agent_ids=ids, llm_client=llm_client, runtime=runtime,
        )
        # Wire Captain Card persona + duty schedule into the spawned Yeoman
        # so the live agent adopts the operator's persona and respects the
        # work-hours / quiet-hours policy before the first proactive scan.
        from probos.cognitive.yeoman import YeomanAgent
        yeo_agents = [
            agent for agent in runtime.registry.get_by_pool("yeoman")
            if isinstance(agent, YeomanAgent)
        ]
        if len(yeo_agents) > 1:
            logger.warning(
                "AD-766: more than one YeomanAgent in registry "
                "(found %d); singleton invariant violated",
                len(yeo_agents),
            )
        if yeo_agents:
            yeoman = yeo_agents[0]
            try:
                await yeoman.initialize(
                    captain_card=getattr(runtime, "captain_card", None),
                    duty_schedule=getattr(runtime, "duty_schedule", None),
                    digest_window_seconds=getattr(
                        config.proactive_cognitive,
                        "yeoman_digest_window_seconds",
                        60.0,
                    ),
                )
            except Exception:
                logger.warning(
                    "AD-766: YeomanAgent.initialize() failed; agent live "
                    "but proactive-scan aggregation disabled",
                    exc_info=True,
                )

    # Security team — Security Officer (AD-398)
    if config.utility_agents.enabled:
        ids = generate_pool_ids("security_officer", "security_officer", 1)
        await create_pool_fn(
            "security_officer", "security_officer", target_size=1,
            agent_ids=ids, llm_client=llm_client, runtime=runtime,
        )

    # Operations team — Operations Officer (AD-398)
    if config.utility_agents.enabled:
        ids = generate_pool_ids("operations_officer", "operations_officer", 1)
        await create_pool_fn(
            "operations_officer", "operations_officer", target_size=1,
            agent_ids=ids, llm_client=llm_client, runtime=runtime,
        )

    # Training (AD-628) — Tucker
    if config.utility_agents.enabled:
        ids = generate_pool_ids("training_officer", "training_officer", 1)
        await create_pool_fn(
            "training_officer", "training_officer", target_size=1,
            agent_ids=ids, llm_client=llm_client, runtime=runtime,
        )

    # Engineering team — Engineering Officer (AD-398)
    if config.utility_agents.enabled:
        ids = generate_pool_ids("engineering_officer", "engineering_officer", 1)
        await create_pool_fn(
            "engineering_officer", "engineering_officer", target_size=1,
            agent_ids=ids, llm_client=llm_client, runtime=runtime,
        )

    # AD-457: Engineering crew — Performance / Maintenance / Damage Control
    if config.engineering.enabled:
        eng_cfg = config.engineering
        _engineering_heartbeat: list[tuple[str, str, float]] = [
            ("performance_monitor", "engineering_performance",
             eng_cfg.performance_interval_seconds),
            ("maintenance", "engineering_maintenance",
             eng_cfg.maintenance_interval_seconds),
            ("damage_control", "engineering_damage_control",
             30.0),
        ]
        for agent_type_name, pool_name, interval in _engineering_heartbeat:
            ids = generate_pool_ids(agent_type_name, pool_name, 1)
            await create_pool_fn(
                pool_name, agent_type_name, target_size=1,
                agent_ids=ids, runtime=runtime, interval=interval,
            )

    # AD-467: Operations crew -- Resource Allocator / Scheduler / Coordinator
    if config.operations.enabled:
        ops_cfg = config.operations
        _operations_heartbeat: list[tuple[str, str, float]] = [
            ("operations_resource_allocator", "operations_resource_allocator",
             ops_cfg.resource_interval_seconds),
            ("operations_scheduler", "operations_scheduler",
             ops_cfg.scheduler_interval_seconds),
            ("operations_coordinator", "operations_coordinator",
             ops_cfg.coordinator_interval_seconds),
        ]
        for agent_type_name, pool_name, interval in _operations_heartbeat:
            ids = generate_pool_ids(agent_type_name, pool_name, 1)
            await create_pool_fn(
                pool_name, agent_type_name, target_size=1,
                agent_ids=ids, runtime=runtime, interval=interval,
            )

    # AD-755: Office document skills (OSS desktop scope)
    office_cfg = getattr(config, "office_skills", None)
    if office_cfg and office_cfg.enabled:
        for pool_name, agent_type_name in (
            ("office_docx", "office_docx"),
            ("office_pptx", "office_pptx"),
            ("office_xlsx", "office_xlsx"),
        ):
            ids = generate_pool_ids(agent_type_name, pool_name, 1)
            await create_pool_fn(
                pool_name,
                agent_type_name,
                target_size=1,
                agent_ids=ids,
                llm_client=llm_client,
                runtime=runtime,
            )

    # Build CodebaseIndex — ship's library, available to all agents (AD-297)
    from probos.cognitive.codebase_index import CodebaseIndex

    codebase_index = CodebaseIndex(source_root=Path(__file__).resolve().parent.parent)
    codebase_index.build()

    # Medical team pool (AD-290)
    if config.medical.enabled:
        med_cfg = config.medical

        # Create vitals monitor pool entry (HeartbeatAgent — no LLM)
        ids = generate_pool_ids("vitals_monitor", "medical_vitals", 1)
        await create_pool_fn(
            "medical_vitals", "vitals_monitor", target_size=1,
            agent_ids=ids, runtime=runtime,
            window_size=med_cfg.vitals_window_size,
            pool_health_min=med_cfg.pool_health_min,
            trust_floor=med_cfg.trust_floor,
            health_floor=med_cfg.health_floor,
            max_trust_outliers=med_cfg.max_trust_outliers,
        )

        # CognitiveAgent medical agents — all share "medical" pool
        _medical_cognitive = [
            ("diagnostician", "diagnostician"),
            ("surgeon", "surgeon"),
            ("pharmacist", "pharmacist"),
            ("pathologist", "pathologist"),
        ]
        for agent_type_name, pool_suffix in _medical_cognitive:
            ids = generate_pool_ids(agent_type_name, f"medical_{pool_suffix}", 1)
            await create_pool_fn(
                f"medical_{pool_suffix}", agent_type_name, target_size=1,
                agent_ids=ids, llm_client=llm_client, runtime=runtime,
            )

        # Register codebase_knowledge skill on CognitiveAgent medical agents
        from probos.cognitive.codebase_skill import create_codebase_skill

        # Science analytical team (AD-560) — Data Analyst, Systems Analyst, Research Specialist
        _science_cognitive = [
            ("data_analyst", "data_analyst"),
            ("systems_analyst", "systems_analyst"),
            ("research_specialist", "research_specialist"),
        ]
        for agent_type_name, pool_suffix in _science_cognitive:
            ids = generate_pool_ids(agent_type_name, f"science_{pool_suffix}", 1)
            await create_pool_fn(
                f"science_{pool_suffix}", agent_type_name, target_size=1,
                agent_ids=ids, llm_client=llm_client, runtime=runtime,
            )

        codebase_skill = create_codebase_skill(codebase_index)
        for pool_name in ["medical_pathologist"]:
            pool = pools.get(pool_name)
            if pool:
                for agent in pool.healthy_agents:
                    if hasattr(agent, "add_skill"):
                        agent.add_skill(codebase_skill)

    # Attach codebase_knowledge skill to architect pool (AD-307)
    if config.utility_agents.enabled:
        from probos.cognitive.codebase_skill import create_codebase_skill as _create_cb_skill

        _cb_skill = _create_cb_skill(codebase_index)
        pool = pools.get("architect")
        if pool:
            for agent in pool.healthy_agents:
                if hasattr(agent, "add_skill"):
                    agent.add_skill(_cb_skill)

    # Refresh decomposer with intent descriptors from all registered templates
    decomposer.refresh_descriptors(collect_intent_descriptors_fn())

    # Wire StrategyAdvisor on all CognitiveAgent instances (AD-384)
    if strategy_advisor:
        from probos.cognitive.cognitive_agent import CognitiveAgent as _CA

        for pool in pools.values():
            for agent in pool.healthy_agents:
                if isinstance(agent, _CA) and hasattr(agent, "set_strategy_advisor"):
                    agent.set_strategy_advisor(strategy_advisor)

    # Spawn red team agents (populates runtime.red_team_agents in-place)
    await spawn_red_team_fn(config.consensus.red_team_pool_size)

    logger.info("Startup [agent_fleet]: complete")
    return AgentFleetResult(
        onboarding_service=None,  # created in start() before this phase
        codebase_index=codebase_index,
        red_team_agents=[],  # populated directly on runtime by spawn_red_team_fn
    )
