"""Tests for configuration, hardware detection, and engine recommendation."""

from __future__ import annotations

from pathlib import Path

from openjarvis.core.config import (
    AgentConfig,
    ChannelConfig,
    EngineConfig,
    GpuInfo,
    HardwareInfo,
    IntelligenceConfig,
    JarvisConfig,
    LearningConfig,
    SandboxConfig,
    SchedulerConfig,
    SecurityConfig,
    WhatsAppBaileysChannelConfig,
    generate_default_toml,
    load_config,
    recommend_engine,
)


class TestDefaults:
    def test_jarvis_config_defaults(self) -> None:
        cfg = JarvisConfig()
        assert cfg.engine.default == "ollama"
        assert cfg.memory.default_backend == "sqlite"
        assert cfg.telemetry.enabled is True

    def test_engine_config_defaults(self) -> None:
        ec = EngineConfig()
        # Nested configs
        assert ec.ollama.host == ""
        assert ec.vllm.host == "http://localhost:8000"
        assert ec.sglang.host == "http://localhost:30000"
        assert ec.llamacpp.host == "http://localhost:8080"
        assert ec.lemonade.host == "http://localhost:13305"
        assert ec.llamacpp.binary_path == ""
        # Backward-compat properties still work
        assert ec.ollama_host == ""
        assert ec.vllm_host == "http://localhost:8000"
        assert ec.lemonade_host == "http://localhost:13305"


class TestRecommendEngine:
    def test_no_gpu(self) -> None:
        hw = HardwareInfo(platform="linux")
        assert recommend_engine(hw) == "llamacpp"

    def test_apple_silicon(self) -> None:
        hw = HardwareInfo(
            platform="darwin",
            gpu=GpuInfo(vendor="apple", name="Apple M2 Max"),
        )
        assert recommend_engine(hw) == "mlx"

    def test_nvidia_datacenter(self) -> None:
        hw = HardwareInfo(
            platform="linux",
            gpu=GpuInfo(vendor="nvidia", name="NVIDIA A100-SXM4-80GB", vram_gb=80),
        )
        assert recommend_engine(hw) == "vllm"

    def test_nvidia_consumer(self) -> None:
        hw = HardwareInfo(
            platform="linux",
            gpu=GpuInfo(vendor="nvidia", name="NVIDIA GeForce RTX 4090", vram_gb=24),
        )
        assert recommend_engine(hw) == "ollama"

    def test_amd(self) -> None:
        hw = HardwareInfo(
            platform="linux",
            gpu=GpuInfo(vendor="amd", name="Radeon RX 7900 XTX"),
        )
        assert recommend_engine(hw) == "lemonade"


class TestTomlLoading:
    def test_load_missing_file_uses_defaults(self, tmp_path: Path) -> None:
        cfg = load_config(tmp_path / "nonexistent.toml")
        assert isinstance(cfg, JarvisConfig)
        # engine default is derived from detected hardware — just ensure it's a string
        assert isinstance(cfg.engine.default, str)

    def test_load_overrides(self, tmp_path: Path) -> None:
        toml_file = tmp_path / "config.toml"
        toml_file.write_text(
            '[engine]\ndefault = "vllm"\n\n[memory]\ndefault_backend = "faiss"\n'
        )
        cfg = load_config(toml_file)
        assert cfg.engine.default == "vllm"
        assert cfg.memory.default_backend == "faiss"

    def test_loads_nested_lemonade_host_override(self, tmp_path: Path) -> None:
        toml_file = tmp_path / "config.toml"
        toml_file.write_text(
            '[engine]\ndefault = "lemonade"\n\n'
            '[engine.lemonade]\nhost = "http://custom-lemonade:19000"\n'
        )
        cfg = load_config(toml_file)
        assert cfg.engine.default == "lemonade"
        assert cfg.engine.lemonade.host == "http://custom-lemonade:19000"
        assert cfg.engine.lemonade_host == "http://custom-lemonade:19000"

    def test_system_prompt_block_parsed(self, tmp_path: Path) -> None:
        """Regression for #401: the [system_prompt] block (and its prefix)
        must reach the runtime config, not be dropped by load_config()."""
        toml_file = tmp_path / "config.toml"
        toml_file.write_text(
            '[system_prompt]\nprefix = "You are Jarvis."\nsoul_max_chars = 999\n'
        )
        cfg = load_config(toml_file)
        assert cfg.system_prompt.prefix == "You are Jarvis."
        assert cfg.system_prompt.soul_max_chars == 999

    def test_config_without_system_prompt_block_defaults(self, tmp_path: Path) -> None:
        """Backward compatibility: a config lacking [system_prompt] keeps
        the empty-prefix default (no behavior change for existing configs)."""
        toml_file = tmp_path / "config.toml"
        toml_file.write_text('[engine]\ndefault = "ollama"\n')
        cfg = load_config(toml_file)
        assert cfg.system_prompt.prefix == ""


class TestGenerateToml:
    def test_contains_engine_section(self) -> None:
        hw = HardwareInfo(
            platform="linux",
            cpu_brand="Intel Xeon",
            cpu_count=16,
            ram_gb=64.0,
            gpu=GpuInfo(vendor="nvidia", name="NVIDIA H100", vram_gb=80),
        )
        toml = generate_default_toml(hw)
        assert "[engine]" in toml
        assert 'default = "vllm"' in toml
        assert "H100" in toml


class TestSecurityConfig:
    def test_security_config_defaults(self) -> None:
        sc = SecurityConfig()
        assert sc.enabled is True
        assert sc.scan_input is True
        assert sc.scan_output is True
        assert sc.mode == "redact"
        assert sc.secret_scanner is True
        assert sc.pii_scanner is True
        assert sc.enforce_tool_confirmation is True

    def test_security_config_on_jarvis_config(self) -> None:
        cfg = JarvisConfig()
        assert isinstance(cfg.security, SecurityConfig)

    def test_security_config_loads_from_toml(self, tmp_path: Path) -> None:
        toml_file = tmp_path / "config.toml"
        toml_file.write_text('[security]\nmode = "block"\nscan_input = false\n')
        cfg = load_config(toml_file)
        assert cfg.security.mode == "block"
        assert cfg.security.scan_input is False

    def test_security_config_in_default_toml(self) -> None:
        output = generate_default_toml(HardwareInfo())
        assert "[security]" in output


class TestChannelConfig:
    def test_channel_config_defaults(self) -> None:
        cc = ChannelConfig()
        assert cc.enabled is False
        assert cc.default_agent == "simple"

    def test_channel_config_on_jarvis_config(self) -> None:
        cfg = JarvisConfig()
        assert isinstance(cfg.channel, ChannelConfig)

    def test_channel_config_loads_from_toml(self, tmp_path: Path) -> None:
        toml_file = tmp_path / "config.toml"
        toml_file.write_text(
            '[channel]\nenabled = true\ndefault_channel = "telegram"\n'
        )
        cfg = load_config(toml_file)
        assert cfg.channel.enabled is True
        assert cfg.channel.default_channel == "telegram"

    def test_channel_config_in_default_toml(self) -> None:
        output = generate_default_toml(HardwareInfo())
        assert "[channel]" in output


# ---------------------------------------------------------------------------
# New config structure tests
# ---------------------------------------------------------------------------


class TestIntelligenceGenerationDefaults:
    def test_generation_fields_exist(self) -> None:
        ic = IntelligenceConfig()
        assert ic.temperature == 0.7
        assert ic.max_tokens == 1024
        assert ic.top_p == 0.9
        assert ic.top_k == 40
        assert ic.repetition_penalty == 1.0
        assert ic.stop_sequences == ""

    def test_custom_generation_values(self) -> None:
        ic = IntelligenceConfig(temperature=0.3, max_tokens=512, top_p=0.5)
        assert ic.temperature == 0.3
        assert ic.max_tokens == 512
        assert ic.top_p == 0.5


class TestAgentConfigNew:
    def test_new_fields(self) -> None:
        ac = AgentConfig()
        assert ac.objective == ""
        assert ac.system_prompt == ""
        assert ac.system_prompt_path == ""
        assert ac.context_from_memory is True
        assert ac.tools == ""
        assert ac.max_turns == 10

    def test_default_tools_backward_compat(self) -> None:
        ac = AgentConfig()
        ac.default_tools = "calculator,think"
        assert ac.tools == "calculator,think"
        assert ac.default_tools == "calculator,think"

    def test_no_temperature_or_max_tokens(self) -> None:
        ac = AgentConfig()
        assert (
            not hasattr(ac.__class__, "temperature")
            or isinstance(getattr(ac.__class__, "temperature", None), property) is False
        )

    def test_default_system_prompt_anchors_identity(self) -> None:
        """#540: the hardened wording must name OpenJarvis and explicitly
        deny the model's training identity so distilled models stop
        claiming to be Claude/ChatGPT/etc."""
        prompt = AgentConfig().default_system_prompt
        assert "OpenJarvis" in prompt
        assert "not Claude" in prompt


class TestNestedEngineConfig:
    def test_nested_access(self) -> None:
        ec = EngineConfig()
        assert ec.ollama.host == ""
        assert ec.vllm.host == "http://localhost:8000"
        assert ec.sglang.host == "http://localhost:30000"
        assert ec.llamacpp.host == "http://localhost:8080"
        assert ec.lemonade.host == "http://localhost:13305"
        assert ec.llamacpp.binary_path == ""

    def test_backward_compat_setter(self) -> None:
        ec = EngineConfig()
        ec.ollama_host = "http://custom:1234"
        assert ec.ollama.host == "http://custom:1234"
        assert ec.ollama_host == "http://custom:1234"

    def test_llamacpp_path_compat(self) -> None:
        ec = EngineConfig()
        ec.llamacpp_path = "/usr/bin/llama-server"
        assert ec.llamacpp.binary_path == "/usr/bin/llama-server"

    def test_loads_nested_toml(self, tmp_path: Path) -> None:
        toml_file = tmp_path / "config.toml"
        toml_file.write_text(
            '[engine]\ndefault = "vllm"\n\n'
            '[engine.ollama]\nhost = "http://custom:11434"\n\n'
            '[engine.llamacpp]\nbinary_path = "/opt/llama"\n'
        )
        cfg = load_config(toml_file)
        assert cfg.engine.default == "vllm"
        assert cfg.engine.ollama.host == "http://custom:11434"
        assert cfg.engine.llamacpp.binary_path == "/opt/llama"

    def test_loads_old_flat_toml(self, tmp_path: Path) -> None:
        """Old flat engine keys still work via backward-compat properties."""
        toml_file = tmp_path / "config.toml"
        toml_file.write_text(
            '[engine]\ndefault = "ollama"\n'
            'ollama_host = "http://old:11434"\n'
            'vllm_host = "http://old:8000"\n'
        )
        cfg = load_config(toml_file)
        assert cfg.engine.ollama.host == "http://old:11434"
        assert cfg.engine.vllm.host == "http://old:8000"

    def test_loads_old_flat_lemonade_host(self, tmp_path: Path) -> None:
        toml_file = tmp_path / "config.toml"
        toml_file.write_text(
            '[engine]\ndefault = "lemonade"\n'
            'lemonade_host = "http://legacy-lemonade:19191"\n'
        )
        cfg = load_config(toml_file)
        assert cfg.engine.default == "lemonade"
        assert cfg.engine.lemonade.host == "http://legacy-lemonade:19191"
        assert cfg.engine.lemonade_host == "http://legacy-lemonade:19191"


class TestNestedLearningConfig:
    def test_defaults(self) -> None:
        lc = LearningConfig()
        assert lc.enabled is False
        assert lc.update_interval == 100
        assert lc.auto_update is False
        assert lc.routing.policy == "heuristic"
        assert lc.routing.min_samples == 5
        assert lc.intelligence.policy == "none"
        assert lc.agent.policy == "none"
        assert lc.metrics.accuracy_weight == 0.6
        assert lc.metrics.latency_weight == 0.2

    def test_backward_compat_default_policy(self) -> None:
        lc = LearningConfig()
        assert lc.default_policy == "heuristic"
        lc.default_policy = "grpo"
        assert lc.routing.policy == "grpo"

    def test_backward_compat_intelligence_policy(self) -> None:
        lc = LearningConfig()
        lc.intelligence_policy = "sft"
        assert lc.intelligence.policy == "sft"

    def test_backward_compat_agent_policy(self) -> None:
        lc = LearningConfig()
        lc.agent_policy = "agent_advisor"
        assert lc.agent.policy == "agent_advisor"

    def test_backward_compat_reward_weights(self) -> None:
        lc = LearningConfig()
        lc.reward_weights = "latency=0.4,cost=0.3"
        assert lc.metrics.latency_weight == 0.4
        assert lc.metrics.cost_weight == 0.3

    def test_loads_nested_toml(self, tmp_path: Path) -> None:
        toml_file = tmp_path / "config.toml"
        toml_file.write_text(
            "[learning]\nenabled = true\nupdate_interval = 50\n\n"
            '[learning.routing]\npolicy = "learned"\n\n'
            "[learning.metrics]\nlatency_weight = 0.5\n"
        )
        cfg = load_config(toml_file)
        assert cfg.learning.enabled is True
        assert cfg.learning.update_interval == 50
        assert cfg.learning.routing.policy == "learned"
        assert cfg.learning.metrics.latency_weight == 0.5

    def test_loads_old_flat_learning_toml(self, tmp_path: Path) -> None:
        """Old flat learning keys still work via backward-compat properties."""
        toml_file = tmp_path / "config.toml"
        toml_file.write_text(
            '[learning]\ndefault_policy = "grpo"\n'
            'intelligence_policy = "sft"\n'
            'reward_weights = "latency=0.5"\n'
        )
        cfg = load_config(toml_file)
        assert cfg.learning.routing.policy == "grpo"
        assert cfg.learning.intelligence.policy == "sft"
        assert cfg.learning.metrics.latency_weight == 0.5


class TestBackwardCompatMigration:
    def test_agent_temperature_migrates_to_intelligence(self, tmp_path: Path) -> None:
        toml_file = tmp_path / "config.toml"
        toml_file.write_text("[agent]\ntemperature = 0.3\nmax_tokens = 512\n")
        cfg = load_config(toml_file)
        assert cfg.intelligence.temperature == 0.3
        assert cfg.intelligence.max_tokens == 512

    def test_memory_context_injection_migrates_to_agent(self, tmp_path: Path) -> None:
        toml_file = tmp_path / "config.toml"
        toml_file.write_text("[memory]\ncontext_injection = false\n")
        cfg = load_config(toml_file)
        assert cfg.agent.context_from_memory is False

    def test_tools_storage_context_injection_migrates(self, tmp_path: Path) -> None:
        toml_file = tmp_path / "config.toml"
        toml_file.write_text(
            '[tools.storage]\ncontext_injection = false\ndefault_backend = "faiss"\n'
        )
        cfg = load_config(toml_file)
        assert cfg.agent.context_from_memory is False
        assert cfg.tools.storage.default_backend == "faiss"


class TestGenerateDefaultTomlNew:
    def test_nested_engine_sections(self) -> None:
        hw = HardwareInfo()
        toml_str = generate_default_toml(hw)
        assert "[engine.ollama]" in toml_str
        assert "[engine.vllm]" in toml_str
        assert "[engine.sglang]" in toml_str

    def test_intelligence_generation_params(self) -> None:
        hw = HardwareInfo()
        toml_str = generate_default_toml(hw)
        assert "temperature = 0.7" in toml_str
        assert "max_tokens = 1024" in toml_str

    def test_agent_new_fields(self) -> None:
        hw = HardwareInfo()
        toml_str = generate_default_toml(hw)
        assert "context_from_memory = true" in toml_str

    def test_learning_nested_sections(self) -> None:
        hw = HardwareInfo()
        toml_str = generate_default_toml(hw)
        assert "[learning.routing]" in toml_str
        assert 'policy = "heuristic"' in toml_str


# ---------------------------------------------------------------------------
# Sandbox config tests
# ---------------------------------------------------------------------------


class TestSandboxConfig:
    def test_defaults(self) -> None:
        sc = SandboxConfig()
        assert sc.enabled is False
        assert sc.image == "openjarvis-sandbox:latest"
        assert sc.timeout == 300
        assert sc.workspace == ""
        assert sc.mount_allowlist_path == ""
        assert sc.max_concurrent == 5
        assert sc.runtime == "docker"

    def test_custom_values(self) -> None:
        sc = SandboxConfig(
            enabled=True,
            image="custom:v2",
            timeout=600,
            runtime="podman",
        )
        assert sc.enabled is True
        assert sc.image == "custom:v2"
        assert sc.timeout == 600
        assert sc.runtime == "podman"

    def test_on_jarvis_config(self) -> None:
        cfg = JarvisConfig()
        assert isinstance(cfg.sandbox, SandboxConfig)
        assert cfg.sandbox.enabled is False

    def test_loads_from_toml(self, tmp_path: Path) -> None:
        toml_file = tmp_path / "config.toml"
        toml_file.write_text(
            '[sandbox]\nenabled = true\ntimeout = 600\nruntime = "podman"\n'
        )
        cfg = load_config(toml_file)
        assert cfg.sandbox.enabled is True
        assert cfg.sandbox.timeout == 600
        assert cfg.sandbox.runtime == "podman"


# ---------------------------------------------------------------------------
# Scheduler config tests
# ---------------------------------------------------------------------------


class TestSchedulerConfig:
    def test_defaults(self) -> None:
        sc = SchedulerConfig()
        assert sc.enabled is False
        assert sc.poll_interval == 60
        assert sc.db_path == ""

    def test_custom_values(self) -> None:
        sc = SchedulerConfig(enabled=True, poll_interval=30, db_path="/tmp/sched.db")
        assert sc.enabled is True
        assert sc.poll_interval == 30
        assert sc.db_path == "/tmp/sched.db"

    def test_on_jarvis_config(self) -> None:
        cfg = JarvisConfig()
        assert isinstance(cfg.scheduler, SchedulerConfig)
        assert cfg.scheduler.enabled is False

    def test_loads_from_toml(self, tmp_path: Path) -> None:
        toml_file = tmp_path / "config.toml"
        toml_file.write_text(
            "[scheduler]\nenabled = true\npoll_interval = 30\n"
            'db_path = "/tmp/sched.db"\n'
        )
        cfg = load_config(toml_file)
        assert cfg.scheduler.enabled is True
        assert cfg.scheduler.poll_interval == 30
        assert cfg.scheduler.db_path == "/tmp/sched.db"


# ---------------------------------------------------------------------------
# WhatsApp Baileys channel config tests
# ---------------------------------------------------------------------------


class TestApplyTomlSectionListNormalization:
    def test_apply_toml_section_list_to_str_field(self) -> None:
        """TOML arrays assigned to str-typed fields should be joined with ','."""
        from openjarvis.core.config import ToolsConfig, _apply_toml_section

        target = ToolsConfig()
        tools = ["code_interpreter", "web_search", "file_read"]
        _apply_toml_section(target, {"enabled": tools})
        assert isinstance(target.enabled, str)
        assert target.enabled == "code_interpreter,web_search,file_read"

    def test_apply_toml_section_list_to_property_setter(self) -> None:
        """TOML arrays passed to backward-compat property setters should be
        normalized to comma-separated strings, not passed as raw lists."""
        from openjarvis.core.config import _apply_toml_section

        target = LearningConfig()
        _apply_toml_section(
            target,
            {
                "reward_weights": ["accuracy=0.8", "latency=0.2"],
            },
        )
        assert target.metrics.accuracy_weight == 0.8
        assert target.metrics.latency_weight == 0.2

    def test_apply_toml_section_agent_tools_list(self) -> None:
        """Agent tools should work as a TOML array."""
        from openjarvis.core.config import _apply_toml_section

        target = AgentConfig()
        _apply_toml_section(
            target,
            {
                "tools": ["web_search", "http_request", "file_read"],
            },
        )
        assert isinstance(target.tools, str)
        assert target.tools == "web_search,http_request,file_read"


class TestWhatsAppBaileysChannelConfig:
    def test_defaults(self) -> None:
        wc = WhatsAppBaileysChannelConfig()
        assert wc.auth_dir == ""
        assert wc.assistant_name == "Jarvis"
        assert wc.assistant_has_own_number is False

    def test_custom_values(self) -> None:
        wc = WhatsAppBaileysChannelConfig(
            auth_dir="/tmp/wa",
            assistant_name="Andy",
            assistant_has_own_number=True,
        )
        assert wc.auth_dir == "/tmp/wa"
        assert wc.assistant_name == "Andy"
        assert wc.assistant_has_own_number is True

    def test_on_channel_config(self) -> None:
        cc = ChannelConfig()
        assert isinstance(cc.whatsapp_baileys, WhatsAppBaileysChannelConfig)


# ---------------------------------------------------------------------------
# Mining config integration tests
# ---------------------------------------------------------------------------


def test_mining_config_absent_means_none(tmp_path):
    from openjarvis.core.config import load_config

    cfg_path = tmp_path / "config.toml"
    cfg_path.write_text("")  # empty config
    cfg = load_config(cfg_path)
    assert cfg.mining is None


def test_mining_config_solo_parsed(tmp_path):
    from pathlib import Path

    from openjarvis.core.config import load_config
    from openjarvis.mining._stubs import SoloTarget

    src = Path(__file__).parent.parent / "mining" / "fixtures" / "config_minimal.toml"
    target = tmp_path / "config.toml"
    target.write_text(src.read_text())
    cfg = load_config(target)
    assert cfg.mining is not None
    assert cfg.mining.provider == "vllm-pearl"
    assert cfg.mining.wallet_address == "prl1qexampleaddress"
    assert isinstance(cfg.mining.submit_target, SoloTarget)
    assert cfg.mining.submit_target.pearld_rpc_url == "http://localhost:44107"
    assert cfg.mining.fee_bps == 0
    assert cfg.mining.extra["model"] == "pearl-ai/Llama-3.3-70B-Instruct-pearl"


def test_mining_config_pool_parsed_as_pool_target(tmp_path):
    from pathlib import Path

    from openjarvis.core.config import load_config
    from openjarvis.mining._stubs import PoolTarget

    src = Path(__file__).parent.parent / "mining" / "fixtures" / "config_pool_v2.toml"
    target = tmp_path / "config.toml"
    target.write_text(src.read_text())
    cfg = load_config(target)
    assert isinstance(cfg.mining.submit_target, PoolTarget)
    assert cfg.mining.submit_target.url == "https://pool.openjarvis.ai/submit"
