"""Tests for bot_client_setup.py — setup/remove lifecycle."""

import json
import os
import sys
import tempfile
import unittest

sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "scripts"))

from bot_client_setup import (
    CREDENTIALS_FILENAME,
    SECTION_MARKER_END,
    SECTION_MARKER_START,
    SESSION_FILENAME,
    _find_section,
    generate_proxy_section,
    generate_section,
    remove,
    setup,
)


class TestGenerateSection(unittest.TestCase):
    """Test AGENTS.md section generation (raw mode)."""

    def test_contains_markers(self):
        section = generate_section(
            url="https://example.com/bot-api/chat",
            remote_agent_id="main",
            agent_id="vip-reader",
            credentials_path="data/bot_credentials.json",
        )
        self.assertIn(SECTION_MARKER_START, section)
        self.assertIn(SECTION_MARKER_END, section)

    def test_contains_credentials_path(self):
        section = generate_section(
            url="https://example.com/bot-api/chat",
            remote_agent_id="main",
            agent_id="vip-reader",
            credentials_path="data/bot_credentials.json",
        )
        self.assertIn("data/bot_credentials.json", section)
        self.assertNotIn("https://example.com/bot-api/chat", section)

    def test_contains_agent_ids(self):
        section = generate_section(
            url="https://example.com/bot-api/chat",
            remote_agent_id="custom-agent",
            agent_id="vip-reader",
            credentials_path="data/bot_credentials.json",
        )
        self.assertIn("custom-agent", section)
        self.assertIn("vip-reader", section)

    def test_contains_session_filename(self):
        section = generate_section(
            url="https://example.com/bot-api/chat",
            remote_agent_id="main",
            agent_id="vip-reader",
            credentials_path="data/bot_credentials.json",
        )
        self.assertIn(SESSION_FILENAME, section)

    def test_contains_error_handling(self):
        section = generate_section(
            url="https://example.com/bot-api/chat",
            remote_agent_id="main",
            agent_id="vip-reader",
            credentials_path="data/bot_credentials.json",
        )
        self.assertIn("错误处理", section)
        self.assertIn("401/403", section)


class TestGenerateProxySection(unittest.TestCase):
    """Test AGENTS.md section generation (proxy mode)."""

    def test_contains_markers(self):
        section = generate_proxy_section(
            remote_agent_id="intern-helper",
            agent_id="test",
            wrapper_path="/path/to/bot_client_safe.sh",
        )
        self.assertIn(SECTION_MARKER_START, section)
        self.assertIn(SECTION_MARKER_END, section)

    def test_contains_wrapper_path(self):
        section = generate_proxy_section(
            remote_agent_id="intern-helper",
            agent_id="test",
            wrapper_path="/path/to/bot_client_safe.sh",
        )
        self.assertIn("/path/to/bot_client_safe.sh", section)

    def test_does_not_contain_curl_commands(self):
        """Proxy section should not contain executable curl/python3 commands."""
        section = generate_proxy_section(
            remote_agent_id="intern-helper",
            agent_id="test",
            wrapper_path="/path/to/bot_client_safe.sh",
        )
        self.assertNotIn("curl -s", section)
        self.assertNotIn("python3 -c", section)
        self.assertNotIn("BOT_URL=", section)

    def test_does_not_contain_credentials(self):
        section = generate_proxy_section(
            remote_agent_id="intern-helper",
            agent_id="test",
            wrapper_path="/path/to/bot_client_safe.sh",
        )
        self.assertNotIn("bot_credentials.json", section)
        self.assertNotIn("BOT_SECRET", section)

    def test_contains_agent_ids(self):
        section = generate_proxy_section(
            remote_agent_id="intern-helper",
            agent_id="test",
            wrapper_path="/path/to/bot_client_safe.sh",
        )
        self.assertIn("intern-helper", section)
        self.assertIn("test", section)

    def test_forbids_direct_access(self):
        section = generate_proxy_section(
            remote_agent_id="intern-helper",
            agent_id="test",
            wrapper_path="/path/to/bot_client_safe.sh",
        )
        self.assertIn("禁止", section)
        self.assertIn("系统强制执行", section)


class TestFindSection(unittest.TestCase):
    """Test section boundary detection."""

    def test_no_section(self):
        self.assertIsNone(_find_section("# Hello\nSome content."))

    def test_finds_section(self):
        content = f"before\n{SECTION_MARKER_START}\nmiddle\n{SECTION_MARKER_END}\nafter"
        bounds = _find_section(content)
        self.assertIsNotNone(bounds)
        start, end = bounds
        extracted = content[start:end]
        self.assertTrue(extracted.startswith(SECTION_MARKER_START))
        self.assertTrue(extracted.endswith(SECTION_MARKER_END))

    def test_missing_end_marker(self):
        content = f"before\n{SECTION_MARKER_START}\nmiddle\n"
        self.assertIsNone(_find_section(content))


class TestSetup(unittest.TestCase):
    """Test setup command (raw mode)."""

    def setUp(self):
        self.tmpdir = tempfile.mkdtemp()
        self.workspace_root = os.path.join(self.tmpdir, "workspaces")
        self.agent_id = "test-agent"
        self.workspace = os.path.join(self.workspace_root, self.agent_id)
        os.makedirs(self.workspace)
        self.agents_md = os.path.join(self.workspace, "AGENTS.md")
        with open(self.agents_md, "w") as f:
            f.write("# Test Agent\n\nExisting content.\n")
        os.makedirs(os.path.join(self.workspace, "data"))

    def tearDown(self):
        import shutil
        shutil.rmtree(self.tmpdir, ignore_errors=True)

    def test_setup_success(self):
        result = setup(
            agent_id=self.agent_id,
            url="https://example.com/bot-api/chat",
            secret="test-secret",
            remote_agent_id="main",
            workspace_root=self.workspace_root,
        )
        self.assertEqual(result["status"], "ok")
        self.assertEqual(result["agent_id"], self.agent_id)
        self.assertEqual(result["url"], "https://example.com/bot-api/chat")

    def test_agents_md_updated(self):
        setup(
            agent_id=self.agent_id,
            url="https://example.com/bot-api/chat",
            secret="test-secret",
            remote_agent_id="main",
            workspace_root=self.workspace_root,
        )
        with open(self.agents_md) as f:
            content = f.read()
        self.assertIn(SECTION_MARKER_START, content)
        self.assertIn(SECTION_MARKER_END, content)
        self.assertIn("Existing content.", content)

    def test_credentials_file_created(self):
        setup(
            agent_id=self.agent_id,
            url="https://example.com/bot-api/chat",
            secret="test-secret",
            remote_agent_id="main",
            workspace_root=self.workspace_root,
        )
        creds_path = os.path.join(self.workspace, "data", CREDENTIALS_FILENAME)
        self.assertTrue(os.path.isfile(creds_path))
        with open(creds_path) as f:
            creds = json.load(f)
        self.assertEqual(creds["url"], "https://example.com/bot-api/chat")
        self.assertEqual(creds["secret"], "test-secret")
        self.assertEqual(creds["remote_agent_id"], "main")

    def test_idempotent_setup(self):
        """Running setup twice should replace, not duplicate."""
        for _ in range(2):
            setup(
                agent_id=self.agent_id,
                url="https://example.com/bot-api/chat",
                secret="test-secret",
                remote_agent_id="main",
                workspace_root=self.workspace_root,
            )
        with open(self.agents_md) as f:
            content = f.read()
        self.assertEqual(content.count(SECTION_MARKER_START), 1)
        self.assertEqual(content.count(SECTION_MARKER_END), 1)

    def test_setup_updates_url(self):
        """Second setup with different URL should update."""
        setup(
            agent_id=self.agent_id,
            url="https://old.com/bot-api/chat",
            secret="old-secret",
            remote_agent_id="main",
            workspace_root=self.workspace_root,
        )
        setup(
            agent_id=self.agent_id,
            url="https://new.com/bot-api/chat",
            secret="new-secret",
            remote_agent_id="main",
            workspace_root=self.workspace_root,
        )
        creds_path = os.path.join(self.workspace, "data", CREDENTIALS_FILENAME)
        with open(creds_path) as f:
            creds = json.load(f)
        self.assertEqual(creds["url"], "https://new.com/bot-api/chat")
        self.assertEqual(creds["secret"], "new-secret")

    def test_workspace_not_found(self):
        result = setup(
            agent_id="nonexistent",
            url="https://example.com/bot-api/chat",
            secret="s",
            remote_agent_id="main",
            workspace_root=self.workspace_root,
        )
        self.assertEqual(result["status"], "error")

    def test_agents_md_not_found(self):
        os.remove(self.agents_md)
        result = setup(
            agent_id=self.agent_id,
            url="https://example.com/bot-api/chat",
            secret="s",
            remote_agent_id="main",
            workspace_root=self.workspace_root,
        )
        self.assertEqual(result["status"], "error")

    def test_data_dir_created_if_missing(self):
        """If data/ doesn't exist, setup should create it."""
        import shutil
        shutil.rmtree(os.path.join(self.workspace, "data"))
        result = setup(
            agent_id=self.agent_id,
            url="https://example.com/bot-api/chat",
            secret="s",
            remote_agent_id="main",
            workspace_root=self.workspace_root,
        )
        self.assertEqual(result["status"], "ok")
        self.assertTrue(os.path.isdir(os.path.join(self.workspace, "data")))


class TestSetupProxy(unittest.TestCase):
    """Test setup command (proxy mode)."""

    def setUp(self):
        self.tmpdir = tempfile.mkdtemp()
        self.workspace_root = os.path.join(self.tmpdir, "workspaces")
        self.admin_dir = os.path.join(self.tmpdir, "admin")
        self.exec_approvals = os.path.join(self.tmpdir, "exec-approvals.json")
        self.agent_id = "proxy-test"
        self.workspace = os.path.join(self.workspace_root, self.agent_id)
        os.makedirs(self.workspace)
        self.agents_md = os.path.join(self.workspace, "AGENTS.md")
        with open(self.agents_md, "w") as f:
            f.write("# Proxy Test Agent\n\nExisting content.\n")

    def tearDown(self):
        import shutil
        shutil.rmtree(self.tmpdir, ignore_errors=True)

    def test_proxy_setup_success(self):
        result = setup(
            agent_id=self.agent_id,
            url="https://example.com/bot-api/chat",
            secret="proxy-secret",
            remote_agent_id="intern-helper",
            workspace_root=self.workspace_root,
            mode="proxy",
            admin_dir=self.admin_dir,
            exec_approvals_path=self.exec_approvals,
        )
        self.assertEqual(result["status"], "ok")
        self.assertEqual(result["mode"], "proxy")
        self.assertTrue(result["exec_allowlist_updated"])

    def test_proxy_credentials_in_admin_dir(self):
        """Proxy mode stores credentials in admin_dir, not workspace."""
        setup(
            agent_id=self.agent_id,
            url="https://example.com/bot-api/chat",
            secret="proxy-secret",
            remote_agent_id="intern-helper",
            workspace_root=self.workspace_root,
            mode="proxy",
            admin_dir=self.admin_dir,
            exec_approvals_path=self.exec_approvals,
        )
        admin_creds = os.path.join(self.admin_dir, self.agent_id, CREDENTIALS_FILENAME)
        self.assertTrue(os.path.isfile(admin_creds))
        with open(admin_creds) as f:
            creds = json.load(f)
        self.assertEqual(creds["url"], "https://example.com/bot-api/chat")
        self.assertEqual(creds["secret"], "proxy-secret")

        workspace_creds = os.path.join(self.workspace, "data", CREDENTIALS_FILENAME)
        self.assertFalse(os.path.exists(workspace_creds))

    def test_proxy_agents_md_no_curl_commands(self):
        """Proxy mode AGENTS.md should not contain executable curl/python3 commands."""
        setup(
            agent_id=self.agent_id,
            url="https://example.com/bot-api/chat",
            secret="proxy-secret",
            remote_agent_id="intern-helper",
            workspace_root=self.workspace_root,
            mode="proxy",
            admin_dir=self.admin_dir,
            exec_approvals_path=self.exec_approvals,
        )
        with open(self.agents_md) as f:
            content = f.read()
        self.assertIn(SECTION_MARKER_START, content)
        self.assertIn("bot_client_safe.sh", content)
        self.assertNotIn("curl -s", content)
        self.assertNotIn("python3 -c", content)
        self.assertNotIn("BOT_SECRET", content)
        self.assertNotIn("BOT_URL=", content)

    def test_proxy_agents_md_readonly(self):
        """Proxy mode should set AGENTS.md to read-only."""
        setup(
            agent_id=self.agent_id,
            url="https://example.com/bot-api/chat",
            secret="proxy-secret",
            remote_agent_id="intern-helper",
            workspace_root=self.workspace_root,
            mode="proxy",
            admin_dir=self.admin_dir,
            exec_approvals_path=self.exec_approvals,
        )
        mode = os.stat(self.agents_md).st_mode
        self.assertFalse(mode & 0o200, "AGENTS.md should be read-only")

    def test_proxy_exec_allowlist_updated(self):
        """Proxy mode should add bot_client_safe.sh to exec allowlist."""
        setup(
            agent_id=self.agent_id,
            url="https://example.com/bot-api/chat",
            secret="proxy-secret",
            remote_agent_id="intern-helper",
            workspace_root=self.workspace_root,
            mode="proxy",
            admin_dir=self.admin_dir,
            exec_approvals_path=self.exec_approvals,
        )
        self.assertTrue(os.path.isfile(self.exec_approvals))
        with open(self.exec_approvals) as f:
            data = json.load(f)
        agent_entry = data["agents"][self.agent_id]
        patterns = [e["pattern"] for e in agent_entry["allowlist"]]
        self.assertTrue(
            any("bot_client_safe.sh" in p for p in patterns),
            f"bot_client_safe.sh not in allowlist: {patterns}",
        )

    def test_proxy_idempotent(self):
        """Running proxy setup twice should not duplicate."""
        for _ in range(2):
            setup(
                agent_id=self.agent_id,
                url="https://example.com/bot-api/chat",
                secret="proxy-secret",
                remote_agent_id="intern-helper",
                workspace_root=self.workspace_root,
                mode="proxy",
                admin_dir=self.admin_dir,
                exec_approvals_path=self.exec_approvals,
            )
        with open(self.agents_md) as f:
            content = f.read()
        self.assertEqual(content.count(SECTION_MARKER_START), 1)

        with open(self.exec_approvals) as f:
            data = json.load(f)
        patterns = [e["pattern"] for e in data["agents"][self.agent_id]["allowlist"]]
        safe_sh_count = sum(1 for p in patterns if "bot_client_safe.sh" in p)
        self.assertEqual(safe_sh_count, 1)

    def test_proxy_handles_existing_readonly(self):
        """Proxy setup should handle already read-only AGENTS.md."""
        import stat as stat_mod
        os.chmod(self.agents_md, stat_mod.S_IRUSR | stat_mod.S_IRGRP | stat_mod.S_IROTH)
        result = setup(
            agent_id=self.agent_id,
            url="https://example.com/bot-api/chat",
            secret="proxy-secret",
            remote_agent_id="intern-helper",
            workspace_root=self.workspace_root,
            mode="proxy",
            admin_dir=self.admin_dir,
            exec_approvals_path=self.exec_approvals,
        )
        self.assertEqual(result["status"], "ok")


class TestRemove(unittest.TestCase):
    """Test remove command."""

    def setUp(self):
        self.tmpdir = tempfile.mkdtemp()
        self.workspace_root = os.path.join(self.tmpdir, "workspaces")
        self.admin_dir = os.path.join(self.tmpdir, "admin")
        self.agent_id = "test-agent"
        self.workspace = os.path.join(self.workspace_root, self.agent_id)
        os.makedirs(os.path.join(self.workspace, "data"))
        self.agents_md = os.path.join(self.workspace, "AGENTS.md")
        with open(self.agents_md, "w") as f:
            f.write("# Test Agent\n\nExisting content.\n")

    def tearDown(self):
        import shutil
        shutil.rmtree(self.tmpdir, ignore_errors=True)

    def _setup_raw(self):
        setup(
            agent_id=self.agent_id,
            url="https://example.com/bot-api/chat",
            secret="test-secret",
            remote_agent_id="main",
            workspace_root=self.workspace_root,
        )
        session_path = os.path.join(self.workspace, "data", SESSION_FILENAME)
        with open(session_path, "w") as f:
            f.write("session-key-123")

    def test_remove_success(self):
        self._setup_raw()
        result = remove(
            agent_id=self.agent_id,
            workspace_root=self.workspace_root,
            admin_dir=self.admin_dir,
        )
        self.assertEqual(result["status"], "ok")
        self.assertTrue(result["removed_section"])

    def test_section_removed_from_agents_md(self):
        self._setup_raw()
        remove(
            agent_id=self.agent_id,
            workspace_root=self.workspace_root,
            admin_dir=self.admin_dir,
        )
        with open(self.agents_md) as f:
            content = f.read()
        self.assertNotIn(SECTION_MARKER_START, content)
        self.assertNotIn(SECTION_MARKER_END, content)

    def test_original_content_preserved(self):
        with open(self.agents_md, "w") as f:
            f.write("# Test Agent\n\nExisting content.\n")
        self._setup_raw()
        remove(
            agent_id=self.agent_id,
            workspace_root=self.workspace_root,
            admin_dir=self.admin_dir,
        )
        with open(self.agents_md) as f:
            content = f.read()
        self.assertIn("Existing content.", content)

    def test_credentials_and_session_deleted(self):
        self._setup_raw()
        result = remove(
            agent_id=self.agent_id,
            workspace_root=self.workspace_root,
            admin_dir=self.admin_dir,
        )
        self.assertIn(CREDENTIALS_FILENAME, result["removed_files"])
        self.assertIn(SESSION_FILENAME, result["removed_files"])
        self.assertFalse(
            os.path.isfile(os.path.join(self.workspace, "data", CREDENTIALS_FILENAME))
        )
        self.assertFalse(
            os.path.isfile(os.path.join(self.workspace, "data", SESSION_FILENAME))
        )

    def test_remove_no_section(self):
        with open(self.agents_md, "w") as f:
            f.write("# Test Agent\n")
        result = remove(
            agent_id=self.agent_id,
            workspace_root=self.workspace_root,
            admin_dir=self.admin_dir,
        )
        self.assertEqual(result["status"], "ok")
        self.assertFalse(result["removed_section"])

    def test_remove_workspace_not_found(self):
        result = remove(
            agent_id="nonexistent",
            workspace_root=self.workspace_root,
            admin_dir=self.admin_dir,
        )
        self.assertEqual(result["status"], "error")


class TestRemoveProxy(unittest.TestCase):
    """Test remove command for proxy mode setups."""

    def setUp(self):
        self.tmpdir = tempfile.mkdtemp()
        self.workspace_root = os.path.join(self.tmpdir, "workspaces")
        self.admin_dir = os.path.join(self.tmpdir, "admin")
        self.exec_approvals = os.path.join(self.tmpdir, "exec-approvals.json")
        self.agent_id = "proxy-remove-test"
        self.workspace = os.path.join(self.workspace_root, self.agent_id)
        os.makedirs(self.workspace)
        with open(os.path.join(self.workspace, "AGENTS.md"), "w") as f:
            f.write("# Proxy Remove Test\n")

    def tearDown(self):
        import shutil
        shutil.rmtree(self.tmpdir, ignore_errors=True)

    def test_remove_proxy_credentials(self):
        """Remove should clean up admin_dir credentials."""
        setup(
            agent_id=self.agent_id,
            url="https://example.com/bot-api/chat",
            secret="s",
            remote_agent_id="main",
            workspace_root=self.workspace_root,
            mode="proxy",
            admin_dir=self.admin_dir,
            exec_approvals_path=self.exec_approvals,
        )
        admin_creds = os.path.join(self.admin_dir, self.agent_id, CREDENTIALS_FILENAME)
        self.assertTrue(os.path.isfile(admin_creds))

        result = remove(
            agent_id=self.agent_id,
            workspace_root=self.workspace_root,
            admin_dir=self.admin_dir,
        )
        self.assertEqual(result["status"], "ok")
        self.assertFalse(os.path.isfile(admin_creds))
        self.assertIn(CREDENTIALS_FILENAME, result["removed_files"])


class TestCLI(unittest.TestCase):
    """Test command-line interface via subprocess."""

    def setUp(self):
        self.tmpdir = tempfile.mkdtemp()
        self.workspace_root = os.path.join(self.tmpdir, "workspaces")
        self.admin_dir = os.path.join(self.tmpdir, "admin")
        self.exec_approvals = os.path.join(self.tmpdir, "exec-approvals.json")
        self.agent_id = "cli-test"
        self.workspace = os.path.join(self.workspace_root, self.agent_id)
        os.makedirs(os.path.join(self.workspace, "data"))
        with open(os.path.join(self.workspace, "AGENTS.md"), "w") as f:
            f.write("# CLI Test Agent\n")

    def tearDown(self):
        import shutil
        shutil.rmtree(self.tmpdir, ignore_errors=True)

    def _run_cli(self, args: list[str]) -> tuple[dict, int]:
        import subprocess
        script = os.path.join(
            os.path.dirname(__file__), "..", "scripts", "bot_client_setup.py"
        )
        result = subprocess.run(
            [sys.executable, script, *args],
            capture_output=True,
            text=True,
        )
        output = json.loads(result.stdout) if result.stdout.strip() else {}
        return output, result.returncode

    def test_cli_setup_raw(self):
        output, rc = self._run_cli([
            "setup",
            "--agent-id", self.agent_id,
            "--url", "https://example.com/bot-api/chat",
            "--secret", "cli-secret",
            "--remote-agent-id", "main",
            "--workspace-root", self.workspace_root,
        ])
        self.assertEqual(rc, 0)
        self.assertEqual(output["status"], "ok")

    def test_cli_setup_proxy(self):
        output, rc = self._run_cli([
            "setup",
            "--agent-id", self.agent_id,
            "--url", "https://example.com/bot-api/chat",
            "--secret", "cli-secret",
            "--remote-agent-id", "intern-helper",
            "--mode", "proxy",
            "--workspace-root", self.workspace_root,
            "--admin-dir", self.admin_dir,
            "--exec-approvals-path", self.exec_approvals,
        ])
        self.assertEqual(rc, 0)
        self.assertEqual(output["status"], "ok")
        self.assertEqual(output["mode"], "proxy")

    def test_cli_remove(self):
        self._run_cli([
            "setup",
            "--agent-id", self.agent_id,
            "--url", "https://example.com/bot-api/chat",
            "--secret", "cli-secret",
            "--remote-agent-id", "main",
            "--workspace-root", self.workspace_root,
        ])
        output, rc = self._run_cli([
            "remove",
            "--agent-id", self.agent_id,
            "--workspace-root", self.workspace_root,
            "--admin-dir", self.admin_dir,
        ])
        self.assertEqual(rc, 0)
        self.assertEqual(output["status"], "ok")
        self.assertTrue(output["removed_section"])

    def test_cli_setup_missing_workspace(self):
        output, rc = self._run_cli([
            "setup",
            "--agent-id", "nonexistent",
            "--url", "https://example.com/bot-api/chat",
            "--secret", "s",
            "--remote-agent-id", "main",
            "--workspace-root", self.workspace_root,
        ])
        self.assertEqual(rc, 1)
        self.assertEqual(output["status"], "error")


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