"""Tests for proxym.context module — project detection, tool selection, sessions."""

from __future__ import annotations

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

import pytest

from proxym.context._context import ProjectContext
from proxym.context.detector import (
    detect_project,
    _detect_stack,
    _extract_paths,
    _find_common_root,
    _from_system_prompt,
)
from proxym.context.tool_selector import select_tools, _detect_intent
from proxym.context.session import AgentSession, SessionStore


# ── ProjectContext ────────────────────────────────────────────

class TestProjectContext:
    def test_make_project_id_from_path(self):
        pid = ProjectContext.make_project_id(path="/home/user/project")
        assert len(pid) == 12
        assert pid.isalnum()

    def test_make_project_id_from_remote(self):
        pid = ProjectContext.make_project_id(remote="git@github.com:wronai/proxym.git")
        assert len(pid) == 12

    def test_make_project_id_deterministic(self):
        a = ProjectContext.make_project_id(path="/foo")
        b = ProjectContext.make_project_id(path="/foo")
        assert a == b

    def test_make_project_id_empty(self):
        assert ProjectContext.make_project_id() == ""

    def test_remote_takes_priority(self):
        a = ProjectContext.make_project_id(path="/x", remote="git@gh:a/b.git")
        b = ProjectContext.make_project_id(path="/y", remote="git@gh:a/b.git")
        assert a == b  # same remote → same ID regardless of path


# ── Detector ─────────────────────────────────────────────────

class TestDetector:
    def test_detect_from_header(self, tmp_path):
        (tmp_path / "pyproject.toml").write_text("[project]\nname='test'\n")
        ctx = detect_project([], headers={"x-working-directory": str(tmp_path)})
        assert ctx.project_root == str(tmp_path)
        assert ctx.project_name == tmp_path.name
        assert "python" in ctx.stack

    def test_detect_from_header_nonexistent(self):
        ctx = detect_project([], headers={"x-working-directory": "/nonexistent/path"})
        assert ctx.project_root == ""

    def test_detect_from_messages_with_paths(self, tmp_path):
        (tmp_path / "Cargo.toml").write_text("[package]\nname='test'\n")
        msg = [{"role": "user", "content": f"Fix the bug in {tmp_path}/src/main.rs"}]
        with patch("proxym.context.detector._find_common_root", return_value=str(tmp_path)):
            result = detect_project(msg)
            assert isinstance(result, ProjectContext)

    def test_detect_from_system_prompt(self):
        msgs = [{"role": "system", "content": "You are working on project: proxym"}]
        ctx = detect_project(msgs)
        assert ctx.project_name == "proxym"

    def test_detect_empty(self):
        ctx = detect_project([])
        assert ctx.project_id == ""
        assert ctx.project_root == ""

    def test_detect_stack_python(self, tmp_path):
        (tmp_path / "pyproject.toml").write_text("")
        stack = _detect_stack(tmp_path)
        assert "python" in stack

    def test_detect_stack_js(self, tmp_path):
        (tmp_path / "package.json").write_text('{"dependencies":{"react":"18"}}')
        stack = _detect_stack(tmp_path)
        assert "javascript" in stack
        assert "react" in stack

    def test_detect_stack_multiple(self, tmp_path):
        (tmp_path / "pyproject.toml").write_text("[tool]\n")
        (tmp_path / "tsconfig.json").write_text("{}")
        stack = _detect_stack(tmp_path)
        assert "python" in stack
        assert "typescript" in stack

    def test_detect_stack_empty(self, tmp_path):
        stack = _detect_stack(tmp_path)
        assert stack == []


class TestExtractPaths:
    def test_extracts_linux_paths(self):
        paths = _extract_paths([
            {"role": "user", "content": "Look at /home/tom/github/proxym/main.py please"}
        ])
        assert any("proxym" in p for p in paths)

    def test_no_paths(self):
        paths = _extract_paths([{"role": "user", "content": "Hello world"}])
        assert paths == []

    def test_multipart_content(self):
        paths = _extract_paths([{
            "role": "user",
            "content": [
                {"type": "text", "text": "Fix /home/user/project/bug.py"},
            ],
        }])
        assert len(paths) >= 1


class TestFindCommonRoot:
    def test_single_path(self):
        assert _find_common_root(["/home/user/project/src/main.py"]) == "/home/user/project/src"

    def test_multiple_paths(self):
        root = _find_common_root([
            "/home/user/project/src/a.py",
            "/home/user/project/src/b.py",
        ])
        assert root == "/home/user/project/src"

    def test_divergent_paths(self):
        root = _find_common_root([
            "/home/user/project/src/a.py",
            "/home/user/project/tests/b.py",
        ])
        assert root == "/home/user/project"

    def test_empty(self):
        assert _find_common_root([]) == ""


class TestFromSystemPrompt:
    def test_detects_project_name(self):
        ctx = _from_system_prompt("You are working on project: my-cool-app")
        assert ctx.project_name == "my-cool-app"

    def test_detects_repo_name(self):
        ctx = _from_system_prompt("Repository: proxym")
        assert ctx.project_name == "proxym"

    def test_no_match(self):
        ctx = _from_system_prompt("Hello, I am an assistant.")
        assert ctx.project_name == ""


# ── Tool Selector ────────────────────────────────────────────

class TestToolSelector:
    def test_python_project_gets_filesystem(self):
        ctx = ProjectContext(stack=["python", "fastapi"])
        tools = select_tools(ctx)
        assert "filesystem" in tools
        assert "code2llm" in tools

    def test_empty_context_gets_filesystem(self):
        ctx = ProjectContext()
        tools = select_tools(ctx)
        assert tools == ["filesystem"]

    def test_analysis_adds_tools(self):
        from proxym.router import AnalysisResult
        from proxym.providers import TaskTier
        ctx = ProjectContext(stack=["python"])
        analysis = AnalysisResult(
            tier=TaskTier.STANDARD,
            capabilities=frozenset(),
            signals=["git_operation detected"],
        )
        tools = select_tools(ctx, analysis=analysis)
        assert "github" in tools

    def test_filter_by_available(self):
        ctx = ProjectContext(stack=["python", "fastapi"])
        tools = select_tools(ctx, available_servers={"filesystem"})
        assert tools == ["filesystem"]

    def test_detect_intent_testing(self):
        from proxym.router import AnalysisResult
        from proxym.providers import TaskTier
        analysis = AnalysisResult(
            tier=TaskTier.STANDARD,
            capabilities=frozenset(),
            signals=["test_pattern: write tests"],
        )
        intents = _detect_intent(analysis)
        assert "testing" in intents


# ── Session ──────────────────────────────────────────────────

class TestAgentSession:
    def test_create_session(self):
        s = AgentSession(project_id="abc123")
        assert len(s.session_id) == 12
        assert s.project_id == "abc123"

    def test_add_exchange(self):
        s = AgentSession()
        s.add_exchange("Hello", "Hi there!")
        assert len(s.compressed_history) == 2
        assert s.compressed_history[0]["role"] == "user"
        assert s.compressed_history[1]["role"] == "assistant"

    def test_history_bounded(self):
        s = AgentSession()
        for i in range(60):
            s.add_exchange(f"msg {i}", f"reply {i}")
        assert len(s.compressed_history) <= 100

    def test_touch_updates_last_active(self):
        s = AgentSession()
        old = s.last_active
        time.sleep(0.01)
        s.touch()
        assert s.last_active > old

    def test_age_and_idle(self):
        s = AgentSession()
        assert s.age_seconds >= 0
        assert s.idle_seconds >= 0


class TestSessionStore:
    def test_get_or_create_new(self):
        store = SessionStore()
        s = store.get_or_create(project_id="proj1")
        assert s.project_id == "proj1"
        assert len(store.list_sessions()) == 1

    def test_get_or_create_existing(self):
        store = SessionStore()
        s1 = store.get_or_create(project_id="proj1")
        s2 = store.get_or_create(session_id=s1.session_id)
        assert s1.session_id == s2.session_id

    def test_get_nonexistent(self):
        store = SessionStore()
        assert store.get("nonexistent") is None

    def test_delete(self):
        store = SessionStore()
        s = store.get_or_create(project_id="proj1")
        assert store.delete(s.session_id)
        assert store.get(s.session_id) is None

    def test_evict_expired(self):
        store = SessionStore(max_idle_seconds=0.01)
        store.get_or_create(project_id="proj1")
        time.sleep(0.02)
        sessions = store.list_sessions()
        assert len(sessions) == 0

    def test_max_sessions(self):
        store = SessionStore(max_sessions=3)
        for i in range(5):
            store.get_or_create(project_id=f"proj{i}")
        assert len(store.list_sessions()) <= 3
