"""Tests for configuration management."""

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

from askfind.config import Config, get_api_key, get_config_path


class TestGetConfigPath:
    def test_default_path(self):
        path = get_config_path()
        assert path == Path.home() / ".config" / "askfind" / "config.toml"


class TestConfig:
    def test_defaults(self):
        config = Config()
        assert config.base_url == "https://openrouter.ai/api/v1"
        assert config.model == "openai/gpt-4o-mini"
        assert config.llm_mode == "always"
        assert config.default_root == "."
        assert config.max_results == 50
        assert config.parallel_workers == 1
        assert config.cache_enabled is True
        assert config.cache_ttl_seconds == 300
        assert config.respect_ignore_files is True
        assert config.follow_symlinks is False
        assert config.exclude_binary_files is True
        assert config.search_archives is False
        assert config.similarity_threshold == 0.55
        assert config.editor == "vim"

    def test_from_toml_string(self, tmp_path):
        toml_content = b'[provider]\nbase_url = "http://localhost:11434/v1"\nmodel = "llama3"\n'
        config_file = tmp_path / "config.toml"
        config_file.write_bytes(toml_content)
        config = Config.from_file(config_file)
        assert config.base_url == "http://localhost:11434/v1"
        assert config.model == "llama3"

    def test_from_missing_file_returns_defaults(self, tmp_path):
        config = Config.from_file(tmp_path / "nonexistent.toml")
        assert config.base_url == "https://openrouter.ai/api/v1"

    def test_partial_toml_merges_with_defaults(self, tmp_path):
        toml_content = b'[provider]\nmodel = "gpt-4o"\n'
        config_file = tmp_path / "config.toml"
        config_file.write_bytes(toml_content)
        config = Config.from_file(config_file)
        assert config.model == "gpt-4o"
        assert config.base_url == "https://openrouter.ai/api/v1"  # default preserved

    def test_malformed_toml_returns_defaults(self, tmp_path):
        config_file = tmp_path / "config.toml"
        config_file.write_text("[provider\nmodel = \"gpt-4o\"\n", encoding="utf-8")

        config = Config.from_file(config_file)

        assert config == Config()

    def test_toml_io_error_returns_defaults(self, tmp_path):
        config_file = tmp_path / "config.toml"
        config_file.write_text("[provider]\nmodel = \"gpt-4o\"\n", encoding="utf-8")

        with patch("askfind.config.tomllib.load", side_effect=OSError("boom")):
            config = Config.from_file(config_file)

        assert config == Config()

    def test_save_and_reload(self, tmp_path):
        config = Config(
            model="custom-model",
            llm_mode="auto",
            respect_ignore_files=False,
            parallel_workers=8,
            cache_enabled=False,
            cache_ttl_seconds=120,
            follow_symlinks=True,
            exclude_binary_files=False,
            search_archives=True,
            similarity_threshold=0.72,
            editor="nano",
        )
        config_file = tmp_path / "config.toml"
        config.save(config_file)
        reloaded = Config.from_file(config_file)
        assert reloaded.model == "custom-model"
        assert reloaded.llm_mode == "auto"
        assert reloaded.respect_ignore_files is False
        assert reloaded.parallel_workers == 8
        assert reloaded.cache_enabled is False
        assert reloaded.cache_ttl_seconds == 120
        assert reloaded.follow_symlinks is True
        assert reloaded.exclude_binary_files is False
        assert reloaded.search_archives is True
        assert reloaded.similarity_threshold == 0.72
        assert reloaded.editor == "nano"

    def test_invalid_respect_ignore_files_type_falls_back_to_default(self, tmp_path):
        toml_content = b'[search]\nrespect_ignore_files = "yes"\n'
        config_file = tmp_path / "config.toml"
        config_file.write_bytes(toml_content)

        config = Config.from_file(config_file)
        assert config.respect_ignore_files is True

    def test_invalid_follow_symlinks_type_falls_back_to_default(self, tmp_path):
        toml_content = b'[search]\nfollow_symlinks = "yes"\nexclude_binary_files = "no"\nsearch_archives = "yes"\n'
        config_file = tmp_path / "config.toml"
        config_file.write_bytes(toml_content)

        config = Config.from_file(config_file)
        assert config.follow_symlinks is False
        assert config.exclude_binary_files is True
        assert config.search_archives is False

    def test_invalid_parallel_workers_type_or_value_falls_back_to_default(self, tmp_path):
        toml_content = b'[search]\nparallel_workers = 0\n'
        config_file = tmp_path / "config.toml"
        config_file.write_bytes(toml_content)

        config = Config.from_file(config_file)
        assert config.parallel_workers == 1

    def test_invalid_cache_settings_fall_back_to_defaults(self, tmp_path):
        toml_content = b'[search]\ncache_enabled = "yes"\ncache_ttl_seconds = 0\n'
        config_file = tmp_path / "config.toml"
        config_file.write_bytes(toml_content)

        config = Config.from_file(config_file)
        assert config.cache_enabled is True
        assert config.cache_ttl_seconds == 300

    def test_invalid_negative_max_results_falls_back_to_default(self, tmp_path):
        toml_content = b"[search]\nmax_results = -5\n"
        config_file = tmp_path / "config.toml"
        config_file.write_bytes(toml_content)

        config = Config.from_file(config_file)
        assert config.max_results == 50

    def test_invalid_bool_max_results_falls_back_to_default(self, tmp_path):
        toml_content = b"[search]\nmax_results = true\n"
        config_file = tmp_path / "config.toml"
        config_file.write_bytes(toml_content)

        config = Config.from_file(config_file)
        assert config.max_results == 50

    def test_invalid_similarity_threshold_falls_back_to_default(self, tmp_path):
        toml_content = b"[search]\nsimilarity_threshold = 1.5\n"
        config_file = tmp_path / "config.toml"
        config_file.write_bytes(toml_content)

        config = Config.from_file(config_file)
        assert config.similarity_threshold == 0.55

    def test_invalid_llm_mode_falls_back_to_default(self, tmp_path):
        toml_content = b'[provider]\nllm_mode = "smart"\n'
        config_file = tmp_path / "config.toml"
        config_file.write_bytes(toml_content)

        config = Config.from_file(config_file)
        assert config.llm_mode == "always"


class TestGetApiKey:
    def test_cli_flag_takes_priority(self):
        key = get_api_key(cli_key="sk-cli")
        assert key == "sk-cli"

    @patch.dict(os.environ, {"ASKFIND_API_KEY": "sk-env"})
    def test_env_var_fallback(self):
        key = get_api_key(cli_key=None)
        assert key == "sk-env"

    @patch("askfind.config.keyring")
    def test_keychain_fallback(self, mock_keyring):
        mock_keyring.get_password.return_value = "sk-keychain"
        with patch.dict(os.environ, {}, clear=True):
            # Remove ASKFIND_API_KEY if present
            os.environ.pop("ASKFIND_API_KEY", None)
            key = get_api_key(cli_key=None)
        assert key == "sk-keychain"

    @patch("askfind.config.keyring")
    def test_no_key_returns_none(self, mock_keyring):
        mock_keyring.get_password.return_value = None
        with patch.dict(os.environ, {}, clear=True):
            os.environ.pop("ASKFIND_API_KEY", None)
            key = get_api_key(cli_key=None)
        assert key is None
