"""Tests for cli.py — CLI option parsing and basic modes."""

from __future__ import annotations

import json
from unittest.mock import patch

import pytest
from click.testing import CliRunner

from deathbed.cli import main
from deathbed.scoring import FileMetrics, compute_scores


def _healthy_metric(path="src/foo.py") -> FileMetrics:
    m = FileMetrics(
        path=path, lines=50, days_since_commit=5, commit_count=3, author_count=1,
        avg_complexity=2.0, has_test_file=True, test_file_recent=True,
        recent_churn=1, prev_churn=0,
    )
    compute_scores(m)
    return m


def _critical_metric(path="src/bad.py") -> FileMetrics:
    m = FileMetrics(
        path=path, lines=2000, days_since_commit=800, commit_count=200,
        author_count=15, avg_complexity=20.0, has_test_file=False,
        test_file_recent=False, recent_churn=50, prev_churn=10,
    )
    compute_scores(m)
    return m


@pytest.fixture
def runner():
    return CliRunner()


def test_version_flag(runner):
    result = runner.invoke(main, ["--version"])
    assert result.exit_code == 0
    assert "3.1.0" in result.output


def test_help_flag(runner):
    result = runner.invoke(main, ["--help"])
    assert result.exit_code == 0
    assert "deathbed" in result.output.lower()


def test_format_choices_include_markdown(runner):
    result = runner.invoke(main, ["--help"])
    assert "markdown" in result.output


def test_format_choices_include_json(runner):
    result = runner.invoke(main, ["--help"])
    assert "json" in result.output


def test_watch_flag_shown_in_help(runner):
    result = runner.invoke(main, ["--help"])
    assert "--watch" in result.output


def test_diff_option_shown_in_help(runner):
    result = runner.invoke(main, ["--help"])
    assert "--diff" in result.output


def test_export_option_shown_in_help(runner):
    result = runner.invoke(main, ["--help"])
    assert "--export" in result.output


def test_ci_flag_shown_in_help(runner):
    result = runner.invoke(main, ["--help"])
    assert "--ci" in result.output


def test_json_format_with_mock(runner, tmp_path):
    [_healthy_metric()]
    with patch("deathbed.cli._run_json") as mock_json:
        mock_json.return_value = None
        runner.invoke(main, ["--format", "json", "--path", str(tmp_path)])
        mock_json.assert_called_once()


def test_markdown_format_with_mock(runner, tmp_path):
    with patch("deathbed.cli._run_markdown") as mock_md:
        mock_md.return_value = None
        runner.invoke(main, ["--format", "markdown", "--path", str(tmp_path)])
        mock_md.assert_called_once()


def test_ci_mode_passes_ci_flag(runner, tmp_path):
    with patch("deathbed.display.run_display") as mock_display:
        mock_display.return_value = None
        runner.invoke(main, ["--ci", "--path", str(tmp_path)])
        mock_display.assert_called_once()
        _, kwargs = mock_display.call_args
        assert kwargs.get("ci_mode") is True


def test_diff_dispatches_to_diff_display(runner, tmp_path):
    with patch("deathbed.display.run_diff_display") as mock_diff:
        mock_diff.return_value = None
        runner.invoke(main, ["--diff", "HEAD~1", "--path", str(tmp_path)])
        mock_diff.assert_called_once()


def test_watch_dispatches_to_watch_display(runner, tmp_path):
    with patch("deathbed.display.run_watch_display") as mock_watch:
        mock_watch.return_value = None
        runner.invoke(main, ["--watch", "--path", str(tmp_path)])
        mock_watch.assert_called_once()


def test_export_html_dispatches(runner, tmp_path):
    with patch("deathbed.cli._run_html_export") as mock_export:
        mock_export.return_value = None
        runner.invoke(main, ["--export", "html", "--path", str(tmp_path)])
        mock_export.assert_called_once()


def test_run_json_output(runner, tmp_path):
    results = [_healthy_metric()]
    with patch("deathbed.analyzer.analyze_repo", return_value=results):
        result = runner.invoke(main, ["--format", "json", "--path", str(tmp_path)])
    if result.output:
        try:
            payload = json.loads(result.output)
            assert "files" in payload
            assert "version" in payload
        except json.JSONDecodeError:
            pass  # May have Rich output mixed in — that's OK in test env


def test_run_markdown_output(runner, tmp_path):
    results = [_healthy_metric("src/foo.py")]
    with patch("deathbed.analyzer.analyze_repo", return_value=results):
        result = runner.invoke(main, ["--format", "markdown", "--path", str(tmp_path)])
    # Markdown output should contain pipe characters
    if result.exit_code == 0 and result.output:
        assert "|" in result.output or result.output == ""


def test_since_flag_shown_in_help(runner):
    result = runner.invoke(main, ["--help"])
    assert "--since" in result.output


def test_blame_flag_shown_in_help(runner):
    result = runner.invoke(main, ["--help"])
    assert "--blame" in result.output


def test_leaderboard_flag_shown_in_help(runner):
    result = runner.invoke(main, ["--help"])
    assert "--leaderboard" in result.output


def test_leaderboard_dispatches(runner, tmp_path):
    with patch("deathbed.display.run_leaderboard_display") as mock_lb:
        mock_lb.return_value = None
        runner.invoke(main, ["--leaderboard", "--path", str(tmp_path)])
        mock_lb.assert_called_once()


def test_since_passes_to_run_display(runner, tmp_path):
    with patch("deathbed.display.run_display") as mock_display:
        mock_display.return_value = None
        runner.invoke(main, ["--since", "main", "--path", str(tmp_path)])
        mock_display.assert_called_once()
        _, kwargs = mock_display.call_args
        assert kwargs.get("since_ref") == "main"


def test_blame_passes_to_run_display(runner, tmp_path):
    with patch("deathbed.display.run_display") as mock_display:
        mock_display.return_value = None
        runner.invoke(main, ["--blame", "--path", str(tmp_path)])
        mock_display.assert_called_once()
        _, kwargs = mock_display.call_args
        assert kwargs.get("include_blame") is True


# ── v2.0.0: new flags ─────────────────────────────────────────────────────────

def test_org_flag_shown_in_help(runner):
    result = runner.invoke(main, ["--help"])
    assert "--org" in result.output


def test_plan_flag_shown_in_help(runner):
    result = runner.invoke(main, ["--help"])
    assert "--plan" in result.output


def test_init_ci_flag_shown_in_help(runner):
    result = runner.invoke(main, ["--help"])
    assert "--init-ci" in result.output


def test_badge_flag_shown_in_help(runner):
    result = runner.invoke(main, ["--help"])
    assert "--badge" in result.output


def test_org_dispatches_to_run_org_display(runner, tmp_path):
    with patch("deathbed.display.run_org_display") as mock_org:
        mock_org.return_value = None
        runner.invoke(main, ["--org", str(tmp_path)])
        mock_org.assert_called_once()


def test_plan_dispatches_to_run_plan_display(runner, tmp_path):
    with patch("deathbed.display.run_plan_display") as mock_plan:
        mock_plan.return_value = None
        runner.invoke(main, ["--plan", "--path", str(tmp_path)])
        mock_plan.assert_called_once()


def test_init_ci_dispatches(runner, tmp_path):
    with patch("deathbed.cli._run_init_ci") as mock_ci:
        mock_ci.return_value = None
        runner.invoke(main, ["--init-ci", "--path", str(tmp_path)])
        mock_ci.assert_called_once()


def test_badge_dispatches(runner, tmp_path):
    with patch("deathbed.cli._run_badge") as mock_badge:
        mock_badge.return_value = None
        runner.invoke(main, ["--badge", "--path", str(tmp_path)])
        mock_badge.assert_called_once()


def test_org_with_repo_drills_down(runner, tmp_path):
    with patch("deathbed.display.run_display") as mock_display:
        mock_display.return_value = None
        runner.invoke(main, ["--org", str(tmp_path), "--repo", "myrepo"])
        mock_display.assert_called_once()


def test_json_output_includes_coupling(runner, tmp_path):
    results = [_healthy_metric()]
    with patch("deathbed.analyzer.analyze_repo", return_value=results):
        result = runner.invoke(main, ["--format", "json", "--path", str(tmp_path)])
    if result.output:
        try:
            payload = json.loads(result.output)
            if payload.get("files"):
                assert "coupling_count" in payload["files"][0]
        except (json.JSONDecodeError, KeyError):
            pass
