"""Unit tests for remote test report generation and GitHub posting."""

from __future__ import annotations

import json
from typing import TYPE_CHECKING
from unittest.mock import MagicMock, patch

import pytest

if TYPE_CHECKING:
    from pathlib import Path

from tests.remote.report import (
    generate_markdown_report,
    post_to_github,
    save_json_report,
)
from tests.remote.suites.base import StepStatus, SuiteResult, TestStep

# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


@pytest.fixture()
def passing_result() -> SuiteResult:
    return SuiteResult(
        suite_name="install",
        device_name="alienware",
        steps=[
            TestStep(
                name="pip_install",
                description="Install llmhosts from PyPI",
                status=StepStatus.PASSED,
                stdout="Successfully installed llmhosts-0.5.0",
                duration_s=12.3,
            ),
            TestStep(
                name="verify_version",
                description="Verify version output: 0.5.0",
                status=StepStatus.PASSED,
                stdout="llmhosts 0.5.0",
                duration_s=0.5,
            ),
        ],
        total_duration_s=12.8,
        started_at="2026-03-01T00:00:00+00:00",
        finished_at="2026-03-01T00:00:13+00:00",
    )


@pytest.fixture()
def failing_result() -> SuiteResult:
    return SuiteResult(
        suite_name="serve",
        device_name="mac-mini",
        steps=[
            TestStep(
                name="start_server",
                description="Start llmhosts serve",
                status=StepStatus.PASSED,
                duration_s=1.0,
            ),
            TestStep(
                name="health_check",
                description="Wait for /health to respond OK",
                status=StepStatus.FAILED,
                error_message="Health check failed after 30s",
                duration_s=30.0,
            ),
        ],
        total_duration_s=31.0,
        started_at="2026-03-01T00:00:00+00:00",
        finished_at="2026-03-01T00:00:31+00:00",
    )


@pytest.fixture()
def empty_result() -> SuiteResult:
    return SuiteResult(
        suite_name="clean",
        device_name="dual-4090",
        total_duration_s=0.0,
    )


# ---------------------------------------------------------------------------
# Markdown report tests
# ---------------------------------------------------------------------------


class TestMarkdownReport:
    def test_report_contains_summary_table(self, passing_result: SuiteResult) -> None:
        md = generate_markdown_report([passing_result])
        assert "| Device | Suite |" in md
        assert "| alienware | install |" in md

    def test_report_contains_step_details(self, passing_result: SuiteResult) -> None:
        md = generate_markdown_report([passing_result])
        assert "pip_install" in md
        assert "verify_version" in md

    def test_report_shows_pass_status(self, passing_result: SuiteResult) -> None:
        md = generate_markdown_report([passing_result])
        assert "[+] PASS" in md

    def test_report_shows_fail_status(self, failing_result: SuiteResult) -> None:
        md = generate_markdown_report([failing_result])
        assert "[x] FAIL" in md

    def test_report_includes_failure_details(self, failing_result: SuiteResult) -> None:
        md = generate_markdown_report([failing_result])
        assert "Failed step details" in md
        assert "Health check failed after 30s" in md

    def test_report_handles_empty_result(self, empty_result: SuiteResult) -> None:
        md = generate_markdown_report([empty_result])
        assert "No steps recorded" in md

    def test_report_multiple_results(self, passing_result: SuiteResult, failing_result: SuiteResult) -> None:
        md = generate_markdown_report([passing_result, failing_result])
        assert "alienware" in md
        assert "mac-mini" in md
        assert "install" in md
        assert "serve" in md

    def test_report_has_timestamp(self, passing_result: SuiteResult) -> None:
        md = generate_markdown_report([passing_result])
        assert "Remote Test Report" in md
        assert "UTC" in md

    def test_report_has_footer(self, passing_result: SuiteResult) -> None:
        md = generate_markdown_report([passing_result])
        assert "tests/remote/cli.py" in md

    def test_report_truncates_long_details(self, passing_result: SuiteResult) -> None:
        # Add a step with very long description
        passing_result.steps.append(
            TestStep(
                name="long_step",
                description="A" * 200,
                status=StepStatus.PASSED,
                duration_s=1.0,
            )
        )
        md = generate_markdown_report([passing_result])
        # Should be truncated with ...
        assert "..." in md


# ---------------------------------------------------------------------------
# JSON report tests
# ---------------------------------------------------------------------------


class TestJsonReport:
    def test_save_creates_file(self, passing_result: SuiteResult, tmp_path: Path) -> None:
        with patch("tests.remote.report.RESULTS_DIR", tmp_path):
            filepath = save_json_report([passing_result], filename="test_report.json")

        assert filepath.exists()
        data = json.loads(filepath.read_text())
        assert len(data) == 1
        assert data[0]["suite_name"] == "install"
        assert data[0]["device_name"] == "alienware"
        assert data[0]["success"] is True
        assert data[0]["passed"] == 2

    def test_save_generates_filename(self, passing_result: SuiteResult, tmp_path: Path) -> None:
        with patch("tests.remote.report.RESULTS_DIR", tmp_path):
            filepath = save_json_report([passing_result])

        assert filepath.exists()
        assert filepath.name.endswith(".json")
        assert "alienware" in filepath.name

    def test_save_includes_steps(self, failing_result: SuiteResult, tmp_path: Path) -> None:
        with patch("tests.remote.report.RESULTS_DIR", tmp_path):
            filepath = save_json_report([failing_result])

        data = json.loads(filepath.read_text())
        steps = data[0]["steps"]
        assert len(steps) == 2
        assert steps[1]["status"] == "failed"
        assert steps[1]["error_message"] == "Health check failed after 30s"


# ---------------------------------------------------------------------------
# GitHub posting tests
# ---------------------------------------------------------------------------


class TestGitHubPosting:
    @patch("subprocess.run")
    def test_post_success(self, mock_run: MagicMock) -> None:
        mock_run.return_value = MagicMock(returncode=0)

        result = post_to_github(359, "## Test Report\n\nAll passed.")
        assert result is True
        mock_run.assert_called_once()

        call_args = mock_run.call_args
        assert "gh" in call_args[0][0]
        assert "359" in call_args[0][0]

    @patch("subprocess.run")
    def test_post_failure(self, mock_run: MagicMock) -> None:
        mock_run.return_value = MagicMock(returncode=1, stderr="not found")

        result = post_to_github(999, "## Report")
        assert result is False

    @patch("subprocess.run", side_effect=FileNotFoundError)
    def test_post_gh_not_installed(self, mock_run: MagicMock) -> None:
        result = post_to_github(359, "## Report")
        assert result is False

    @patch("subprocess.run", side_effect=TimeoutError)
    def test_post_timeout(self, mock_run: MagicMock) -> None:
        # subprocess.TimeoutExpired requires arguments, but we can use the generic mock
        with patch("subprocess.run") as mock:
            import subprocess as sp

            mock.side_effect = sp.TimeoutExpired(cmd="gh", timeout=30)
            result = post_to_github(359, "## Report")
            assert result is False


# ---------------------------------------------------------------------------
# SuiteResult property tests
# ---------------------------------------------------------------------------


class TestSuiteResultProperties:
    def test_success_when_all_pass(self) -> None:
        r = SuiteResult(suite_name="test", device_name="dev")
        r.steps = [
            TestStep(name="a", description="", status=StepStatus.PASSED),
            TestStep(name="b", description="", status=StepStatus.PASSED),
        ]
        assert r.success is True
        assert r.passed == 2
        assert r.failed == 0

    def test_failure_when_any_fail(self) -> None:
        r = SuiteResult(suite_name="test", device_name="dev")
        r.steps = [
            TestStep(name="a", description="", status=StepStatus.PASSED),
            TestStep(name="b", description="", status=StepStatus.FAILED),
        ]
        assert r.success is False
        assert r.passed == 1
        assert r.failed == 1

    def test_failure_on_error(self) -> None:
        r = SuiteResult(suite_name="test", device_name="dev")
        r.steps = [
            TestStep(name="a", description="", status=StepStatus.ERROR),
        ]
        assert r.success is False
        assert r.errors == 1

    def test_skipped_count(self) -> None:
        r = SuiteResult(suite_name="test", device_name="dev")
        r.steps = [
            TestStep(name="a", description="", status=StepStatus.SKIPPED),
            TestStep(name="b", description="", status=StepStatus.SKIPPED),
        ]
        assert r.skipped == 2
        assert r.success is True  # Skipped does not count as failure
