from __future__ import annotations

import json
import os
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch

from memnixa.config import (
    build_example_config,
    get_default_config_path,
    get_default_database_path,
    load_config,
    sync_default_config,
)


class ConfigLoadingTests(unittest.TestCase):
    def test_loads_current_directory_config_when_flag_is_missing(self) -> None:
        with tempfile.TemporaryDirectory() as temp_dir:
            temp_path = Path(temp_dir)
            config_path = temp_path / "config.json"
            config_path.write_text(
                json.dumps(
                    {
                        "default_provider": "2",
                        "providers": [
                            {
                                "id": "1",
                                "preset": "zhipu_coding_plan",
                                "api_key": "z-key",
                                "model": "glm-4.7",
                            },
                            {
                                "id": "2",
                                "preset": "openai_chatgpt",
                                "api_key": "o-key",
                                "model": "gpt-4.1",
                            },
                        ],
                    }
                ),
                encoding="utf-8",
            )
            with patch.dict(os.environ, {}, clear=False):
                current = os.getcwd()
                try:
                    os.chdir(temp_path)
                    config = load_config(None)
                finally:
                    os.chdir(current)

        self.assertEqual(config.default_provider, "2")
        self.assertEqual(config.get_provider_config().api_key, "o-key")
        self.assertEqual(config.get_provider_config().model, "gpt-4.1")

    def test_loads_user_home_config_when_current_directory_has_no_config(self) -> None:
        with tempfile.TemporaryDirectory() as temp_dir:
            memnixa_home = Path(temp_dir) / ".memnixa"
            memnixa_home.mkdir(parents=True, exist_ok=True)
            user_config_path = memnixa_home / "config.json"
            user_config_path.write_text(
                json.dumps(
                    {
                        "default_provider": "1",
                        "providers": [
                            {
                                "id": "1",
                                "preset": "openai_chatgpt",
                                "api_key": "home-key",
                                "model": "gpt-4.1",
                            }
                        ],
                    }
                ),
                encoding="utf-8",
            )
            with patch.dict(
                os.environ,
                {"MEMNIXA_HOME": str(memnixa_home)},
                clear=False,
            ):
                with tempfile.TemporaryDirectory() as cwd_dir:
                    current = os.getcwd()
                    try:
                        os.chdir(cwd_dir)
                        config = load_config(None)
                    finally:
                        os.chdir(current)

        self.assertEqual(config.get_provider_config().api_key, "home-key")
        self.assertEqual(config.get_provider_config().model, "gpt-4.1")

    def test_loads_nested_channel_settings(self) -> None:
        with tempfile.TemporaryDirectory() as temp_dir:
            config_path = Path(temp_dir) / "config.json"
            config_path.write_text(
                json.dumps(
                    {
                        "default_provider": "1",
                        "providers": [
                            {
                                "id": "1",
                                "preset": "openai_chatgpt",
                                "api_key": "test-key",
                            }
                        ],
                        "channels": {
                            "feishu": {
                                "enabled": True,
                                "app_id": "cli_a",
                                "app_secret": "cli_s",
                                "group_policy": "open",
                            },
                            "qq": {
                                "enabled": True,
                                "app_id": "qq_a",
                                "secret": "qq_s",
                            },
                        },
                    }
                ),
                encoding="utf-8",
            )
            config = load_config(config_path)

        self.assertTrue(config.channels.feishu.enabled)
        self.assertEqual(config.channels.feishu.app_id, "cli_a")
        self.assertEqual(config.channels.feishu.group_policy, "open")
        self.assertTrue(config.channels.qq.enabled)
        self.assertEqual(config.channels.qq.secret, "qq_s")

    def test_default_database_path_comes_from_user_memnixa_home(self) -> None:
        with tempfile.TemporaryDirectory() as temp_dir:
            memnixa_home = Path(temp_dir) / ".memnixa"
            with patch.dict(
                os.environ,
                {"MEMNIXA_HOME": str(memnixa_home)},
                clear=False,
            ):
                database_path = get_default_database_path()
                config_path = get_default_config_path()

        self.assertEqual(database_path, memnixa_home / "memnixa.db")
        self.assertEqual(config_path, memnixa_home / "config.json")

    def test_sync_default_config_adds_missing_fields_without_overwriting_existing_ones(
        self,
    ) -> None:
        with tempfile.TemporaryDirectory() as temp_dir:
            memnixa_home = Path(temp_dir) / ".memnixa"
            memnixa_home.mkdir(parents=True, exist_ok=True)
            config_path = memnixa_home / "config.json"
            config_path.write_text(
                json.dumps(
                    {
                        "providers": [
                            {
                                "id": "9",
                                "preset": "custom_openai_compatible",
                                "api_key": "custom-key",
                            }
                        ],
                        "channels": {
                            "feishu": {
                                "enabled": True,
                            }
                        },
                        "custom_field": {
                            "keep_me": True,
                        },
                    }
                ),
                encoding="utf-8",
            )
            with patch.dict(
                os.environ,
                {"MEMNIXA_HOME": str(memnixa_home)},
                clear=False,
            ):
                synced_path, created, added_paths = sync_default_config()

            merged = json.loads(config_path.read_text(encoding="utf-8"))

        self.assertEqual(synced_path, config_path)
        self.assertFalse(created)
        self.assertIn("default_provider", added_paths)
        self.assertIn("database_path", added_paths)
        self.assertEqual(merged["providers"][0]["id"], "9")
        self.assertTrue(merged["channels"]["feishu"]["enabled"])
        self.assertTrue(merged["custom_field"]["keep_me"])

    def test_example_config_includes_siliconflow_provider(self) -> None:
        example = build_example_config()
        providers = example["providers"]
        siliconflow = next(
            provider for provider in providers if provider["preset"] == "siliconflow"
        )

        self.assertEqual(siliconflow["api_base"], "https://api.siliconflow.cn/v1")
        self.assertEqual(siliconflow["model"], "deepseek-ai/DeepSeek-V3")

    def test_example_config_includes_jina_api_key_placeholder(self) -> None:
        example = build_example_config()

        self.assertEqual(example["jina_api_key"], "YOUR_JINA_API_KEY")

    def test_example_config_includes_memory_settings(self) -> None:
        example = build_example_config()

        self.assertIn("memory", example)
        self.assertEqual(example["memory"]["enabled"], False)
        self.assertEqual(example["memory"]["extraction_provider_id"], "2")
        self.assertEqual(example["memory"]["extraction_timeout_seconds"], 20)

    def test_load_config_reads_jina_api_key_from_environment(self) -> None:
        with tempfile.TemporaryDirectory() as temp_dir:
            config_path = Path(temp_dir) / "config.json"
            config_path.write_text(
                json.dumps(
                    {
                        "default_provider": "1",
                        "providers": [
                            {
                                "id": "1",
                                "preset": "openai_chatgpt",
                                "api_key": "test-key",
                            }
                        ],
                    }
                ),
                encoding="utf-8",
            )
            with patch.dict(os.environ, {"JINA_API_KEY": "jina-env-key"}, clear=False):
                config = load_config(config_path)

        self.assertEqual(config.jina_api_key, "jina-env-key")

    def test_memory_enabled_requires_extraction_provider_id(self) -> None:
        with tempfile.TemporaryDirectory() as temp_dir:
            config_path = Path(temp_dir) / "config.json"
            config_path.write_text(
                json.dumps(
                    {
                        "default_provider": "1",
                        "providers": [
                            {
                                "id": "1",
                                "preset": "openai_chatgpt",
                                "api_key": "test-key",
                            }
                        ],
                        "memory": {
                            "enabled": True,
                        },
                    }
                ),
                encoding="utf-8",
            )

            with self.assertRaisesRegex(
                ValueError,
                "memory.extraction_provider_id is required",
            ):
                load_config(config_path)

    def test_memory_enabled_requires_valid_extraction_provider_id(self) -> None:
        with tempfile.TemporaryDirectory() as temp_dir:
            config_path = Path(temp_dir) / "config.json"
            config_path.write_text(
                json.dumps(
                    {
                        "default_provider": "1",
                        "providers": [
                            {
                                "id": "1",
                                "preset": "openai_chatgpt",
                                "api_key": "test-key",
                            }
                        ],
                        "memory": {
                            "enabled": True,
                            "extraction_provider_id": "9",
                        },
                    }
                ),
                encoding="utf-8",
            )

            with self.assertRaisesRegex(
                ValueError,
                "memory.extraction_provider_id refers to unknown provider",
            ):
                load_config(config_path)

    def test_memory_enabled_loads_extraction_provider_settings(self) -> None:
        with tempfile.TemporaryDirectory() as temp_dir:
            config_path = Path(temp_dir) / "config.json"
            config_path.write_text(
                json.dumps(
                    {
                        "default_provider": "1",
                        "providers": [
                            {
                                "id": "1",
                                "preset": "openai_chatgpt",
                                "api_key": "main-key",
                            },
                            {
                                "id": "8",
                                "preset": "custom_openai_compatible",
                                "api_key": "memory-key",
                                "api_base": "http://localhost:11434/v1",
                                "model": "qwen-memory-8b",
                            },
                        ],
                        "memory": {
                            "enabled": True,
                            "extraction_provider_id": "8",
                            "extraction_timeout_seconds": 12,
                            "retrieval_limit": 7,
                            "max_injected_chars": 3200,
                        },
                    }
                ),
                encoding="utf-8",
            )
            config = load_config(config_path)

        self.assertTrue(config.memory.enabled)
        self.assertEqual(config.memory.extraction_provider_id, "8")
        self.assertEqual(config.memory.extraction_timeout_seconds, 12)
        self.assertEqual(config.memory.retrieval_limit, 7)
        self.assertEqual(config.memory.max_injected_chars, 3200)


if __name__ == "__main__":
    unittest.main()
