"""Tests for timmy.sovereignty.session_report.

Refs: #957 (Session Sovereignty Report Generator)
"""

import base64
import json
import time
from datetime import UTC, datetime
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest

pytestmark = pytest.mark.unit

from timmy.sovereignty.session_report import (
    _format_duration,
    _gather_session_data,
    _gather_sovereignty_data,
    _render_markdown,
    commit_report,
    generate_and_commit_report,
    generate_report,
    mark_session_start,
)


# ---------------------------------------------------------------------------
# _format_duration
# ---------------------------------------------------------------------------


class TestFormatDuration:
    def test_seconds_only(self):
        assert _format_duration(45) == "45s"

    def test_minutes_and_seconds(self):
        assert _format_duration(125) == "2m 5s"

    def test_hours_minutes_seconds(self):
        assert _format_duration(3661) == "1h 1m 1s"

    def test_zero(self):
        assert _format_duration(0) == "0s"


# ---------------------------------------------------------------------------
# mark_session_start + generate_report (smoke)
# ---------------------------------------------------------------------------


class TestMarkSessionStart:
    def test_sets_session_start(self):
        import timmy.sovereignty.session_report as sr

        sr._SESSION_START = None
        mark_session_start()
        assert sr._SESSION_START is not None
        assert sr._SESSION_START.tzinfo == UTC

    def test_idempotent_overwrite(self):
        import timmy.sovereignty.session_report as sr

        mark_session_start()
        first = sr._SESSION_START
        time.sleep(0.01)
        mark_session_start()
        second = sr._SESSION_START
        assert second >= first


# ---------------------------------------------------------------------------
# _gather_session_data
# ---------------------------------------------------------------------------


class TestGatherSessionData:
    def test_returns_defaults_when_no_file(self, tmp_path):
        mock_logger = MagicMock()
        mock_logger.flush.return_value = None
        mock_logger.session_file = tmp_path / "nonexistent.jsonl"

        with patch(
            "timmy.sovereignty.session_report.get_session_logger",
            return_value=mock_logger,
        ):
            data = _gather_session_data()

        assert data["user_messages"] == 0
        assert data["timmy_messages"] == 0
        assert data["tool_calls"] == 0
        assert data["errors"] == 0
        assert data["tool_call_breakdown"] == {}

    def test_counts_entries_correctly(self, tmp_path):
        session_file = tmp_path / "session_2026-03-23.jsonl"
        entries = [
            {"type": "message", "role": "user", "content": "hello"},
            {"type": "message", "role": "timmy", "content": "hi"},
            {"type": "message", "role": "user", "content": "test"},
            {"type": "tool_call", "tool": "memory_search", "args": {}, "result": "found"},
            {"type": "tool_call", "tool": "memory_search", "args": {}, "result": "nope"},
            {"type": "tool_call", "tool": "shell", "args": {}, "result": "ok"},
            {"type": "error", "error": "boom"},
        ]
        with open(session_file, "w") as f:
            for e in entries:
                f.write(json.dumps(e) + "\n")

        mock_logger = MagicMock()
        mock_logger.flush.return_value = None
        mock_logger.session_file = session_file

        with patch(
            "timmy.sovereignty.session_report.get_session_logger",
            return_value=mock_logger,
        ):
            data = _gather_session_data()

        assert data["user_messages"] == 2
        assert data["timmy_messages"] == 1
        assert data["tool_calls"] == 3
        assert data["errors"] == 1
        assert data["tool_call_breakdown"]["memory_search"] == 2
        assert data["tool_call_breakdown"]["shell"] == 1

    def test_graceful_on_import_error(self):
        with patch(
            "timmy.sovereignty.session_report.get_session_logger",
            side_effect=ImportError("no session_logger"),
        ):
            data = _gather_session_data()

        assert data["tool_calls"] == 0


# ---------------------------------------------------------------------------
# _gather_sovereignty_data
# ---------------------------------------------------------------------------


class TestGatherSovereigntyData:
    def test_returns_empty_on_import_error(self):
        with patch.dict("sys.modules", {"infrastructure.sovereignty_metrics": None}):
            with patch(
                "timmy.sovereignty.session_report.get_sovereignty_store",
                side_effect=ImportError("no store"),
            ):
                data = _gather_sovereignty_data()

        assert data["metrics"] == {}
        assert data["deltas"] == {}
        assert data["previous_session"] == {}

    def test_populates_deltas_from_history(self):
        mock_store = MagicMock()
        mock_store.get_summary.return_value = {
            "cache_hit_rate": {"current": 0.5, "phase": "week1"},
        }
        # get_latest returns newest-first
        mock_store.get_latest.return_value = [
            {"value": 0.5},
            {"value": 0.3},
            {"value": 0.1},
        ]

        with patch(
            "timmy.sovereignty.session_report.get_sovereignty_store",
            return_value=mock_store,
        ):
            with patch(
                "timmy.sovereignty.session_report.GRADUATION_TARGETS",
                {"cache_hit_rate": {"graduation": 0.9}},
            ):
                data = _gather_sovereignty_data()

        delta = data["deltas"].get("cache_hit_rate")
        assert delta is not None
        assert delta["start"] == 0.1  # oldest in window
        assert delta["end"] == 0.5    # most recent
        assert data["previous_session"]["cache_hit_rate"] == 0.3

    def test_single_data_point_no_delta(self):
        mock_store = MagicMock()
        mock_store.get_summary.return_value = {}
        mock_store.get_latest.return_value = [{"value": 0.4}]

        with patch(
            "timmy.sovereignty.session_report.get_sovereignty_store",
            return_value=mock_store,
        ):
            with patch(
                "timmy.sovereignty.session_report.GRADUATION_TARGETS",
                {"api_cost": {"graduation": 0.01}},
            ):
                data = _gather_sovereignty_data()

        delta = data["deltas"]["api_cost"]
        assert delta["start"] == 0.4
        assert delta["end"] == 0.4
        assert data["previous_session"]["api_cost"] is None


# ---------------------------------------------------------------------------
# generate_report (integration — smoke test)
# ---------------------------------------------------------------------------


class TestGenerateReport:
    def _minimal_session_data(self):
        return {
            "user_messages": 3,
            "timmy_messages": 3,
            "tool_calls": 2,
            "errors": 0,
            "tool_call_breakdown": {"memory_search": 2},
        }

    def _minimal_sov_data(self):
        return {
            "metrics": {
                "cache_hit_rate": {"current": 0.45, "phase": "week1"},
                "api_cost": {"current": 0.12, "phase": "pre-start"},
            },
            "deltas": {
                "cache_hit_rate": {"start": 0.40, "end": 0.45},
                "api_cost": {"start": 0.10, "end": 0.12},
            },
            "previous_session": {
                "cache_hit_rate": 0.40,
                "api_cost": 0.10,
            },
        }

    def test_smoke_produces_markdown(self):
        with (
            patch(
                "timmy.sovereignty.session_report._gather_session_data",
                return_value=self._minimal_session_data(),
            ),
            patch(
                "timmy.sovereignty.session_report._gather_sovereignty_data",
                return_value=self._minimal_sov_data(),
            ),
        ):
            report = generate_report("test-session")

        assert "# Sovereignty Session Report" in report
        assert "test-session" in report
        assert "## Session Activity" in report
        assert "## Sovereignty Scorecard" in report
        assert "## Cost Breakdown" in report
        assert "## Trend vs Previous Session" in report

    def test_report_contains_session_stats(self):
        with (
            patch(
                "timmy.sovereignty.session_report._gather_session_data",
                return_value=self._minimal_session_data(),
            ),
            patch(
                "timmy.sovereignty.session_report._gather_sovereignty_data",
                return_value=self._minimal_sov_data(),
            ),
        ):
            report = generate_report()

        assert "| User messages | 3 |" in report
        assert "memory_search" in report

    def test_report_no_previous_session(self):
        sov = self._minimal_sov_data()
        sov["previous_session"] = {"cache_hit_rate": None, "api_cost": None}

        with (
            patch(
                "timmy.sovereignty.session_report._gather_session_data",
                return_value=self._minimal_session_data(),
            ),
            patch(
                "timmy.sovereignty.session_report._gather_sovereignty_data",
                return_value=sov,
            ),
        ):
            report = generate_report()

        assert "No previous session data" in report


# ---------------------------------------------------------------------------
# commit_report
# ---------------------------------------------------------------------------


class TestCommitReport:
    def test_returns_false_when_gitea_disabled(self):
        with patch("timmy.sovereignty.session_report.settings") as mock_settings:
            mock_settings.gitea_enabled = False
            result = commit_report("# test", "dashboard")

        assert result is False

    def test_returns_false_when_no_token(self):
        with patch("timmy.sovereignty.session_report.settings") as mock_settings:
            mock_settings.gitea_enabled = True
            mock_settings.gitea_token = ""
            result = commit_report("# test", "dashboard")

        assert result is False

    def test_creates_file_via_put(self):
        mock_response = MagicMock()
        mock_response.status_code = 201
        mock_response.raise_for_status.return_value = None

        mock_check = MagicMock()
        mock_check.status_code = 404  # file does not exist yet

        mock_client = MagicMock()
        mock_client.__enter__ = MagicMock(return_value=mock_client)
        mock_client.__exit__ = MagicMock(return_value=False)
        mock_client.get.return_value = mock_check
        mock_client.put.return_value = mock_response

        with (
            patch("timmy.sovereignty.session_report.settings") as mock_settings,
            patch("timmy.sovereignty.session_report.httpx.Client", return_value=mock_client),
        ):
            mock_settings.gitea_enabled = True
            mock_settings.gitea_token = "fake-token"
            mock_settings.gitea_url = "http://localhost:3000"
            mock_settings.gitea_repo = "owner/repo"

            result = commit_report("# report content", "dashboard")

        assert result is True
        mock_client.put.assert_called_once()
        call_kwargs = mock_client.put.call_args
        payload = call_kwargs.kwargs.get("json", call_kwargs.args[1] if len(call_kwargs.args) > 1 else {})
        decoded = base64.b64decode(payload["content"]).decode()
        assert "# report content" in decoded

    def test_updates_existing_file_with_sha(self):
        mock_check = MagicMock()
        mock_check.status_code = 200
        mock_check.json.return_value = {"sha": "abc123"}

        mock_response = MagicMock()
        mock_response.raise_for_status.return_value = None

        mock_client = MagicMock()
        mock_client.__enter__ = MagicMock(return_value=mock_client)
        mock_client.__exit__ = MagicMock(return_value=False)
        mock_client.get.return_value = mock_check
        mock_client.put.return_value = mock_response

        with (
            patch("timmy.sovereignty.session_report.settings") as mock_settings,
            patch("timmy.sovereignty.session_report.httpx.Client", return_value=mock_client),
        ):
            mock_settings.gitea_enabled = True
            mock_settings.gitea_token = "fake-token"
            mock_settings.gitea_url = "http://localhost:3000"
            mock_settings.gitea_repo = "owner/repo"

            result = commit_report("# updated", "dashboard")

        assert result is True
        payload = mock_client.put.call_args.kwargs.get("json", {})
        assert payload.get("sha") == "abc123"

    def test_returns_false_on_http_error(self):
        import httpx

        mock_check = MagicMock()
        mock_check.status_code = 404

        mock_client = MagicMock()
        mock_client.__enter__ = MagicMock(return_value=mock_client)
        mock_client.__exit__ = MagicMock(return_value=False)
        mock_client.get.return_value = mock_check
        mock_client.put.side_effect = httpx.HTTPStatusError(
            "403", request=MagicMock(), response=MagicMock(status_code=403)
        )

        with (
            patch("timmy.sovereignty.session_report.settings") as mock_settings,
            patch("timmy.sovereignty.session_report.httpx.Client", return_value=mock_client),
        ):
            mock_settings.gitea_enabled = True
            mock_settings.gitea_token = "fake-token"
            mock_settings.gitea_url = "http://localhost:3000"
            mock_settings.gitea_repo = "owner/repo"

            result = commit_report("# test", "dashboard")

        assert result is False


# ---------------------------------------------------------------------------
# generate_and_commit_report (async)
# ---------------------------------------------------------------------------


class TestGenerateAndCommitReport:
    async def test_returns_true_on_success(self):
        with (
            patch(
                "timmy.sovereignty.session_report.generate_report",
                return_value="# mock report",
            ),
            patch(
                "timmy.sovereignty.session_report.commit_report",
                return_value=True,
            ),
        ):
            result = await generate_and_commit_report("test")

        assert result is True

    async def test_returns_false_when_commit_fails(self):
        with (
            patch(
                "timmy.sovereignty.session_report.generate_report",
                return_value="# mock report",
            ),
            patch(
                "timmy.sovereignty.session_report.commit_report",
                return_value=False,
            ),
        ):
            result = await generate_and_commit_report()

        assert result is False

    async def test_graceful_on_exception(self):
        with patch(
            "timmy.sovereignty.session_report.generate_report",
            side_effect=RuntimeError("explode"),
        ):
            result = await generate_and_commit_report()

        assert result is False
