"""Tests for TOML config loading, precedence, and masking."""

from __future__ import annotations

from pathlib import Path
from unittest.mock import patch

import pytest

from certisigma_census.config import (
    _deep_merge,
    _find_project_config,
    config_paths,
    init_config,
    load_config,
    mask_secret_tail,
    mask_sensitive,
)


class TestDeepMerge:
    def test_flat_merge(self):
        result = _deep_merge({"a": 1, "b": 2}, {"b": 3, "c": 4})
        assert result == {"a": 1, "b": 3, "c": 4}

    def test_nested_merge(self):
        base = {"scan": {"include": [], "max_size": "1G"}}
        overlay = {"scan": {"include": ["*.pdf"]}}
        result = _deep_merge(base, overlay)
        assert result["scan"]["include"] == ["*.pdf"]
        assert result["scan"]["max_size"] == "1G"

    def test_does_not_mutate_base(self):
        base = {"a": {"b": 1}}
        overlay = {"a": {"b": 2}}
        _deep_merge(base, overlay)
        assert base["a"]["b"] == 1


class TestMaskSecretTail:
    def test_short_value_fully_masked(self):
        assert mask_secret_tail("abc") == "****"
        assert mask_secret_tail("abcd") == "****"

    def test_normal_value_shows_last_4(self):
        assert mask_secret_tail("cs_abcdefghij1234567890") == "...7890"

    def test_8_char_boundary_no_prefix_leak(self):
        assert mask_secret_tail("cs_12345") == "...2345"
        assert "cs_" not in mask_secret_tail("cs_12345")


class TestMaskSensitive:
    def test_masks_api_key(self):
        cfg = {"api_key": "cs_abcdefghij1234567890"}
        masked = mask_sensitive(cfg)
        assert masked["api_key"] == "...7890"

    def test_short_key_masked_fully(self):
        cfg = {"api_key": "ab"}
        masked = mask_sensitive(cfg)
        assert masked["api_key"] == "****"

    def test_medium_key_only_last_four_visible(self):
        cfg = {"api_key": "short"}
        masked = mask_sensitive(cfg)
        assert masked["api_key"] == "...hort"

    def test_empty_key_not_masked(self):
        cfg = {"api_key": ""}
        masked = mask_sensitive(cfg)
        assert masked["api_key"] == ""

    def test_masks_encryption_key(self):
        cfg = {"encryption_key": "a" * 64}
        masked = mask_sensitive(cfg)
        assert masked["encryption_key"] == "...aaaa"
        assert "a" * 64 not in str(masked)

    def test_non_sensitive_preserved(self):
        cfg = {"base_url": "https://api.certisigma.ch", "source": "test"}
        masked = mask_sensitive(cfg)
        assert masked["base_url"] == "https://api.certisigma.ch"

    def test_nested_masking(self):
        cfg = {"scan": {"include": ["*.pdf"]}}
        masked = mask_sensitive(cfg)
        assert masked["scan"]["include"] == ["*.pdf"]


class TestFindProjectConfig:
    def test_finds_config_in_current_dir(self, tmp_path):
        config_file = tmp_path / ".census.toml"
        config_file.write_text('api_key = "test"')
        result = _find_project_config(tmp_path)
        assert result == config_file

    def test_finds_config_in_parent(self, tmp_path):
        config_file = tmp_path / ".census.toml"
        config_file.write_text('api_key = "test"')
        child = tmp_path / "sub" / "dir"
        child.mkdir(parents=True)
        result = _find_project_config(child)
        assert result == config_file

    def test_returns_none_when_not_found(self, tmp_path):
        result = _find_project_config(tmp_path)
        assert result is None


class TestLoadConfig:
    def test_returns_defaults_when_no_files(self, tmp_path):
        cfg = load_config(project_dir=tmp_path, skip_user=True)
        assert cfg["api_key"] == ""
        assert cfg["base_url"] == "https://api.certisigma.ch"

    def test_project_config_overrides_defaults(self, tmp_path):
        config_file = tmp_path / ".census.toml"
        config_file.write_text('source = "my-project"\n')
        with patch("certisigma_census.config._find_project_config", return_value=config_file):
            cfg = load_config(skip_user=True)
        assert cfg["source"] == "my-project"

    def test_user_config_loaded(self, tmp_path):
        user_config = tmp_path / "config.toml"
        user_config.write_text('base_url = "https://custom.api.ch"\n')
        with patch("certisigma_census.config._user_config_path", return_value=user_config):
            cfg = load_config(skip_project=True)
        assert cfg["base_url"] == "https://custom.api.ch"

    def test_project_overrides_user(self, tmp_path):
        user_config = tmp_path / "user.toml"
        user_config.write_text('source = "user-source"\n')
        project_config = tmp_path / ".census.toml"
        project_config.write_text('source = "project-source"\n')
        with patch("certisigma_census.config._user_config_path", return_value=user_config), \
             patch("certisigma_census.config._find_project_config", return_value=project_config):
            cfg = load_config()
        assert cfg["source"] == "project-source"


class TestInitConfig:
    def test_creates_template(self, tmp_path):
        path = tmp_path / "sub" / "config.toml"
        init_config(path)
        assert path.exists()
        content = path.read_text()
        assert "CertiSigma Census" in content
        assert "api_key" in content

    def test_creates_parent_dirs(self, tmp_path):
        path = tmp_path / "deep" / "nested" / "config.toml"
        init_config(path)
        assert path.exists()


class TestLoadConfigIsolation:
    def test_returned_config_does_not_share_references_with_defaults(self, tmp_path):
        """load_config() must return an independent copy — no aliasing of nested dicts."""
        from certisigma_census.config import _DEFAULT_CONFIG

        cfg = load_config(project_dir=tmp_path, skip_user=True)
        cfg["scan"]["include"].append("*.log")
        assert _DEFAULT_CONFIG["scan"]["include"] == []


class TestResolveEncryptionKey:
    def test_explicit_key_wins(self):
        from certisigma_census.config import resolve_encryption_key
        assert resolve_encryption_key("ab" * 32) == "ab" * 32

    def test_from_config(self, tmp_path):
        from certisigma_census.config import resolve_encryption_key
        cfg_file = tmp_path / ".certisigma-census.toml"
        cfg_file.write_text(f'encryption_key = "{"cc" * 32}"\n')
        with patch("certisigma_census.config._find_project_config", return_value=cfg_file):
            key = resolve_encryption_key()
        assert key == "cc" * 32

    def test_missing_key_raises(self, tmp_path):
        from certisigma_census.config import resolve_encryption_key
        with patch("certisigma_census.config._find_project_config", return_value=None):
            with patch("certisigma_census.config._user_config_path", return_value=tmp_path / "nope.toml"):
                with pytest.raises(ValueError, match="No encryption key"):
                    resolve_encryption_key()


class TestConfigPaths:
    def test_returns_user_and_project(self):
        paths = config_paths()
        assert "user" in paths
        assert "project" in paths
