"""Tests for admin panel server and API handlers."""

import json
import subprocess
import sys
import time
import urllib.error
import urllib.request

import pytest

from weilink import WeiLink


@pytest.fixture()
def wl(tmp_path):
    """Create a WeiLink client with temp base_path."""
    return WeiLink(base_path=tmp_path)


class TestAdminServer:
    """Tests for AdminServer lifecycle."""

    def test_start_and_stop(self, wl):
        info = wl.start_admin(port=0)
        assert info.port > 0
        assert "http://localhost:" in info.url
        wl.stop_admin()

    def test_start_returns_existing_info(self, wl):
        info1 = wl.start_admin(port=0)
        info2 = wl.start_admin(port=0)
        assert info1.port == info2.port
        wl.stop_admin()

    def test_close_stops_admin(self, wl):
        wl.start_admin(port=0)
        wl.close()
        assert wl._admin_server is None

    def test_stop_admin_when_not_running(self, wl):
        wl.stop_admin()  # should not raise


class TestAdminCLI:
    """Tests for standalone weilink-admin CLI entry point."""

    def test_cli_starts_and_responds(self, tmp_path):
        proc = subprocess.Popen(
            [
                sys.executable,
                "-u",
                "-m",
                "weilink.admin",
                "--no-banner",
                "--port",
                "0",
                "-d",
                str(tmp_path),
            ],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True,
        )
        try:
            # Read the "running at" line to get the URL
            assert proc.stdout is not None
            url = None
            for _ in range(50):
                line = proc.stdout.readline()
                if "running at" in line:
                    url = line.strip().split()[-1]
                    break
            assert url is not None, "CLI did not print the URL"

            # Verify the server is actually responding
            req = urllib.request.Request(url + "/api/status")
            with urllib.request.urlopen(req, timeout=5) as resp:
                data = json.loads(resp.read())
            assert "version" in data
        finally:
            proc.terminate()
            proc.wait(timeout=5)

    def test_cli_custom_base_path_printed(self, tmp_path):
        proc = subprocess.Popen(
            [
                sys.executable,
                "-u",
                "-m",
                "weilink.admin",
                "--no-banner",
                "--port",
                "0",
                "-d",
                str(tmp_path),
            ],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True,
        )
        try:
            assert proc.stdout is not None
            lines = []
            for _ in range(50):
                line = proc.stdout.readline()
                lines.append(line)
                if "Data directory" in line:
                    break
            assert any(str(tmp_path) in ln for ln in lines)
        finally:
            proc.terminate()
            proc.wait(timeout=5)

    def test_cli_sigterm_graceful_shutdown(self, tmp_path):
        import signal

        proc = subprocess.Popen(
            [
                sys.executable,
                "-u",
                "-m",
                "weilink.admin",
                "--no-banner",
                "--port",
                "0",
                "-d",
                str(tmp_path),
            ],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True,
        )
        # Wait for startup
        assert proc.stdout is not None
        for _ in range(50):
            line = proc.stdout.readline()
            if "running at" in line:
                break

        proc.send_signal(signal.SIGTERM)
        proc.wait(timeout=10)
        assert proc.returncode is not None


class TestAdminAPI:
    """Tests for admin REST API endpoints."""

    @pytest.fixture(autouse=True)
    def _start_server(self, wl):
        self.info = wl.start_admin(port=0)
        self.wl = wl
        self.base = self.info.url
        yield
        wl.stop_admin()

    def _get(self, path):
        req = urllib.request.Request(self.base + path)
        with urllib.request.urlopen(req, timeout=5) as resp:
            return json.loads(resp.read())

    def _post(self, path, data=None):
        body = json.dumps(data or {}).encode()
        req = urllib.request.Request(
            self.base + path,
            data=body,
            method="POST",
            headers={"Content-Type": "application/json"},
        )
        with urllib.request.urlopen(req, timeout=5) as resp:
            return json.loads(resp.read())

    def test_root_serves_html(self):
        req = urllib.request.Request(self.base + "/")
        with urllib.request.urlopen(req, timeout=5) as resp:
            content = resp.read().decode()
            assert "WeiLink Admin" in content
            assert "<html" in content

    def test_api_status(self):
        data = self._get("/api/status")
        assert "version" in data
        assert data["is_connected"] is False
        assert data["session_count"] == 1  # default session

    def test_api_sessions_default(self):
        data = self._get("/api/sessions")
        sessions = data["sessions"]
        assert len(sessions) == 1
        assert sessions[0]["name"] == "default"
        assert sessions[0]["connected"] is False
        assert sessions[0]["users"] == []

    def test_api_sessions_with_context(self, tmp_path):
        # Inject a context token into the default session
        s = self.wl._sessions["default"]
        s.context_tokens["user1@im.wechat"] = "tok123"
        s.context_timestamps["user1@im.wechat"] = time.time()

        data = self._get("/api/sessions")
        session = data["sessions"][0]
        assert session["user_count"] == 1
        assert session["users"][0]["user_id"] == "user1@im.wechat"
        assert session["users"][0]["fresh"] is True

    def test_api_logout_nonexistent(self):
        try:
            self._post("/api/sessions/nonexistent/logout")
        except urllib.error.HTTPError as e:
            assert e.code == 404

    def test_api_rename_missing_new_name(self):
        try:
            self._post("/api/sessions/default/rename", {})
        except urllib.error.HTTPError as e:
            assert e.code == 400

    def test_locale_en(self):
        data = self._get("/locales/en.json")
        assert "app" in data
        assert data["app"]["title"] == "WeiLink Admin"

    def test_locale_zh(self):
        data = self._get("/locales/zh.json")
        assert "app" in data

    def test_locale_not_found(self):
        try:
            self._get("/locales/xx.json")
        except urllib.error.HTTPError as e:
            assert e.code == 404

    def test_api_sessions_has_is_default(self):
        data = self._get("/api/sessions")
        session = data["sessions"][0]
        assert "is_default" in session
        assert session["is_default"] is True

    def test_api_set_default(self, tmp_path):
        from weilink.client import _Session
        from weilink.models import BotInfo

        s2 = _Session(
            name="zb",
            token_path=tmp_path / "zb" / "token.json",
            bot_info=BotInfo(
                bot_id="zb@im.bot",
                base_url="https://example.com",
                token="tok_zb",
            ),
        )
        self.wl._sessions["zb"] = s2

        data = self._post("/api/set-default", {"name": "zb"})
        assert data["success"] is True
        assert self.wl.bot_id == "zb@im.bot"

    def test_api_set_default_nonexistent(self):
        try:
            self._post("/api/set-default", {"name": "nope"})
        except urllib.error.HTTPError as e:
            assert e.code == 404

    def test_api_set_default_missing_name(self):
        try:
            self._post("/api/set-default", {})
        except urllib.error.HTTPError as e:
            assert e.code == 400

    def test_not_found(self):
        try:
            self._get("/api/nonexistent")
        except urllib.error.HTTPError as e:
            assert e.code == 404
