"""
Tests for bootstrap.py .env loading fix (issue #730).

bootstrap.py is the primary documented entry point ("python3 bootstrap.py").
Previously it did not load REPO_ROOT/.env, so HERMES_WEBUI_HOST, HERMES_WEBUI_PORT
etc. were silently ignored when launching without start.sh.

Covers:
  1. _load_repo_dotenv() sets env vars from a repo .env file
  2. _load_repo_dotenv() ignores commented lines and blank lines
  3. _load_repo_dotenv() strips quotes from values
  4. _load_repo_dotenv() is a no-op when .env does not exist
  5. _load_repo_dotenv() prints a warning (not crash) on unreadable .env
  6. _load_repo_dotenv() overwrites existing env vars (shell source semantics)
  7. _load_repo_dotenv() handles 'export FOO=bar' prefix
  8. _load_repo_dotenv() preserves values containing '='
  9. Variables are set unconditionally (not setdefault)
  10. Structural: loader is called before DEFAULT_HOST/DEFAULT_PORT
"""
import os
import sys
from pathlib import Path
from unittest.mock import patch

import pytest

REPO_ROOT = Path(__file__).parent.parent


class TestLoadRepoDotenv:

    def setup_method(self):
        self._saved_env = os.environ.copy()

    def teardown_method(self):
        os.environ.clear()
        os.environ.update(self._saved_env)

    def _run(self, tmp_path, env_content: str):
        """Write .env to tmp_path and run _load_repo_dotenv() with that root."""
        import bootstrap as bs
        (tmp_path / ".env").write_text(env_content, encoding="utf-8")
        orig_root = bs.REPO_ROOT
        try:
            bs.REPO_ROOT = tmp_path
            bs._load_repo_dotenv()
        finally:
            bs.REPO_ROOT = orig_root

    def test_sets_env_var_from_dotenv(self, tmp_path):
        """Basic key=value is loaded into os.environ."""
        self._run(tmp_path, "HERMES_WEBUI_HOST=0.0.0.0\n")
        assert os.environ.get("HERMES_WEBUI_HOST") == "0.0.0.0"

    def test_sets_port_from_dotenv(self, tmp_path):
        """HERMES_WEBUI_PORT is loaded as a string (caller does int() conversion)."""
        self._run(tmp_path, "HERMES_WEBUI_PORT=18787\n")
        assert os.environ.get("HERMES_WEBUI_PORT") == "18787"

    def test_ignores_comment_lines(self, tmp_path):
        """Lines starting with # are not loaded."""
        os.environ.pop("HERMES_WEBUI_HOST", None)
        self._run(tmp_path, "# HERMES_WEBUI_HOST=should-be-ignored\n")
        assert os.environ.get("HERMES_WEBUI_HOST") is None

    def test_ignores_blank_lines(self, tmp_path):
        """Blank lines are silently skipped without error."""
        self._run(tmp_path, "\n\nHERMES_WEBUI_PORT=9000\n\n")
        assert os.environ.get("HERMES_WEBUI_PORT") == "9000"

    def test_strips_double_quoted_values(self, tmp_path):
        """Values wrapped in double quotes are stripped."""
        self._run(tmp_path, 'HERMES_WEBUI_HOST="0.0.0.0"\n')
        assert os.environ.get("HERMES_WEBUI_HOST") == "0.0.0.0"

    def test_strips_single_quoted_values(self, tmp_path):
        """Values wrapped in single quotes are stripped."""
        self._run(tmp_path, "HERMES_WEBUI_HOST='0.0.0.0'\n")
        assert os.environ.get("HERMES_WEBUI_HOST") == "0.0.0.0"

    def test_noop_when_no_dotenv(self, tmp_path):
        """No .env file — function returns silently without error."""
        import bootstrap as bs
        orig = bs.REPO_ROOT
        try:
            bs.REPO_ROOT = tmp_path  # tmp_path has no .env
            bs._load_repo_dotenv()  # must not raise
        finally:
            bs.REPO_ROOT = orig

    def test_noop_when_dotenv_unreadable(self, tmp_path, capsys):
        """Unreadable .env prints a warning to stderr — does not crash."""
        import bootstrap as bs
        env_path = tmp_path / ".env"
        env_path.write_text("HERMES_WEBUI_PORT=9999\n")
        orig = bs.REPO_ROOT
        try:
            bs.REPO_ROOT = tmp_path
            with patch("pathlib.Path.read_text", side_effect=PermissionError("no access")):
                bs._load_repo_dotenv()  # must not raise
        finally:
            bs.REPO_ROOT = orig
        captured = capsys.readouterr()
        assert "bootstrap" in captured.err.lower() or "warning" in captured.err.lower() or \
               "could not load" in captured.err.lower(), (
            "_load_repo_dotenv() should print a warning to stderr on read failure"
        )

    def test_overwrites_existing_env_var(self, tmp_path):
        """Unconditional overwrite matches shell source semantics."""
        os.environ["HERMES_WEBUI_HOST"] = "127.0.0.1"
        self._run(tmp_path, "HERMES_WEBUI_HOST=0.0.0.0\n")
        assert os.environ.get("HERMES_WEBUI_HOST") == "0.0.0.0"

    def test_does_not_set_empty_values(self, tmp_path):
        """A key whose value is empty after stripping is not set to a non-empty string."""
        os.environ.pop("HERMES_EMPTY_KEY", None)
        self._run(tmp_path, 'HERMES_EMPTY_KEY=""\n')
        # The current implementation sets key to "" (empty string) — verify it is
        # not set to a non-empty string, which would be clearly wrong.
        val = os.environ.get("HERMES_EMPTY_KEY")
        assert val != "something-wrong", "Empty-value key must not be set to a non-empty string"
        # Specifically: empty string or absent are both acceptable behaviours.
        assert val in (None, ""), f"Unexpected value for empty-quoted key: {val!r}"

    def test_multiple_keys_all_loaded(self, tmp_path):
        """Multiple key=value pairs in one file are all loaded."""
        content = "HERMES_WEBUI_HOST=0.0.0.0\nHERMES_WEBUI_PORT=18787\n"
        self._run(tmp_path, content)
        assert os.environ.get("HERMES_WEBUI_HOST") == "0.0.0.0"
        assert os.environ.get("HERMES_WEBUI_PORT") == "18787"

    def test_value_with_equals_sign_preserved(self, tmp_path):
        """Values containing '=' (e.g. base64) are preserved correctly."""
        self._run(tmp_path, "MY_KEY=abc=def==\n")
        assert os.environ.get("MY_KEY") == "abc=def=="

    def test_export_prefix_stripped(self, tmp_path):
        """'export FOO=bar' lines are parsed correctly — export prefix is stripped."""
        self._run(tmp_path, "export HERMES_WEBUI_HOST=0.0.0.0\n")
        assert os.environ.get("HERMES_WEBUI_HOST") == "0.0.0.0", (
            "'export KEY=value' lines must set KEY, not 'export KEY'"
        )


# ---------------------------------------------------------------------------
# Structural tests — confirm the fix is in place
# ---------------------------------------------------------------------------

class TestBootstrapStructure:

    def test_load_repo_dotenv_function_exists(self):
        """bootstrap.py must export _load_repo_dotenv()."""
        import bootstrap as bs
        assert callable(getattr(bs, "_load_repo_dotenv", None)), (
            "bootstrap.py must define _load_repo_dotenv() so that "
            "python3 bootstrap.py loads REPO_ROOT/.env before reading env defaults"
        )

    def test_dotenv_loaded_before_default_host_port(self):
        """_load_repo_dotenv() call must appear before DEFAULT_HOST/DEFAULT_PORT in source."""
        src = (REPO_ROOT / "bootstrap.py").read_text(encoding="utf-8")
        load_pos = src.find("_load_repo_dotenv()")
        host_pos = src.find("DEFAULT_HOST")
        port_pos = src.find("DEFAULT_PORT")
        assert load_pos != -1, "_load_repo_dotenv() call not found in bootstrap.py"
        assert load_pos < host_pos, (
            "_load_repo_dotenv() must be called before DEFAULT_HOST assignment "
            "so that HERMES_WEBUI_HOST from .env is picked up"
        )
        assert load_pos < port_pos, (
            "_load_repo_dotenv() must be called before DEFAULT_PORT assignment "
            "so that HERMES_WEBUI_PORT from .env is picked up"
        )

    def test_start_sh_and_bootstrap_equivalent_env_loading(self):
        """start.sh sources .env before bootstrap.py; bootstrap.py must now do the same."""
        start_sh = (REPO_ROOT / "start.sh").read_text(encoding="utf-8")
        bootstrap_src = (REPO_ROOT / "bootstrap.py").read_text(encoding="utf-8")
        # start.sh sources .env
        assert "source" in start_sh and ".env" in start_sh, (
            "start.sh should still source .env (regression guard)"
        )
        # bootstrap.py now loads it too
        assert "_load_repo_dotenv" in bootstrap_src, (
            "bootstrap.py must load .env so direct invocation matches start.sh behaviour"
        )
