"""gfo.commands.milestone のテスト。"""

from __future__ import annotations

import contextlib
import json
from unittest.mock import MagicMock, patch

import pytest

from gfo.adapter.base import Milestone
from gfo.commands import milestone as milestone_cmd
from gfo.exceptions import ConfigError
from tests.test_commands.conftest import make_args


def _make_milestone() -> Milestone:
    return Milestone(
        number=1,
        title="v1.0",
        description="First release",
        state="open",
        due_date="2026-04-01",
    )


def _make_adapter(sample_milestone: Milestone) -> MagicMock:
    adapter = MagicMock()
    adapter.list_milestones.return_value = [sample_milestone]
    adapter.create_milestone.return_value = sample_milestone
    return adapter


@contextlib.contextmanager
def _patch_all(config, adapter: MagicMock):
    with patch("gfo.commands.milestone.get_adapter", return_value=adapter):
        yield


class TestHandleList:
    def setup_method(self):
        self.milestone = _make_milestone()
        self.adapter = _make_adapter(self.milestone)

    def test_calls_list_milestones(self, sample_config):
        args = make_args()
        with _patch_all(sample_config, self.adapter):
            milestone_cmd.handle_list(args, fmt="table")

        self.adapter.list_milestones.assert_called_once_with()

    def test_outputs_results(self, sample_config, capsys):
        args = make_args()
        with _patch_all(sample_config, self.adapter):
            milestone_cmd.handle_list(args, fmt="table")

        out = capsys.readouterr().out
        assert "v1.0" in out

    def test_json_format(self, sample_config, capsys):
        args = make_args()
        with _patch_all(sample_config, self.adapter):
            milestone_cmd.handle_list(args, fmt="json")

        out = capsys.readouterr().out
        data = json.loads(out)
        assert isinstance(data, list)
        assert data[0]["title"] == "v1.0"

    def test_table_output_includes_state(self, sample_config, capsys):
        args = make_args()
        with _patch_all(sample_config, self.adapter):
            milestone_cmd.handle_list(args, fmt="table")

        out = capsys.readouterr().out
        assert "open" in out

    def test_table_output_includes_due_date(self, sample_config, capsys):
        args = make_args()
        with _patch_all(sample_config, self.adapter):
            milestone_cmd.handle_list(args, fmt="table")

        out = capsys.readouterr().out
        assert "2026-04-01" in out

    def test_empty_list(self, sample_config, capsys):
        self.adapter.list_milestones.return_value = []
        args = make_args()
        with _patch_all(sample_config, self.adapter):
            milestone_cmd.handle_list(args, fmt="table")

        # 空リストでも例外なく実行される
        self.adapter.list_milestones.assert_called_once_with()

    def test_json_format_has_all_fields(self, sample_config, capsys):
        args = make_args()
        with _patch_all(sample_config, self.adapter):
            milestone_cmd.handle_list(args, fmt="json")

        out = capsys.readouterr().out
        data = json.loads(out)
        item = data[0]
        assert "title" in item
        assert "state" in item
        assert "number" in item

    def test_plain_format(self, sample_config, capsys):
        args = make_args()
        with _patch_all(sample_config, self.adapter):
            milestone_cmd.handle_list(args, fmt="plain")

        out = capsys.readouterr().out
        assert "\t" in out
        assert "NUMBER" not in out
        assert "v1.0" in out


class TestHandleCreate:
    def setup_method(self):
        self.milestone = _make_milestone()
        self.adapter = _make_adapter(self.milestone)

    def test_basic_create(self, sample_config):
        args = make_args(title="v1.0", description="First release", due="2026-04-01")
        with _patch_all(sample_config, self.adapter):
            milestone_cmd.handle_create(args, fmt="table")

        self.adapter.create_milestone.assert_called_once_with(
            title="v1.0",
            description="First release",
            due_date="2026-04-01",
        )

    def test_create_without_optional_args(self, sample_config):
        args = make_args(title="v2.0", description=None, due=None)
        with _patch_all(sample_config, self.adapter):
            milestone_cmd.handle_create(args, fmt="table")

        self.adapter.create_milestone.assert_called_once_with(
            title="v2.0",
            description=None,
            due_date=None,
        )

    def test_due_to_due_date_mapping(self, sample_config):
        args = make_args(title="v3.0", description=None, due="2026-06-30")
        with _patch_all(sample_config, self.adapter):
            milestone_cmd.handle_create(args, fmt="table")

        call_kwargs = self.adapter.create_milestone.call_args.kwargs
        assert "due_date" in call_kwargs
        assert call_kwargs["due_date"] == "2026-06-30"
        assert "due" not in call_kwargs

    def test_create_outputs_title(self, sample_config, capsys):
        args = make_args(title="v1.0", description="First release", due="2026-04-01")
        with _patch_all(sample_config, self.adapter):
            milestone_cmd.handle_create(args, fmt="table")

        out = capsys.readouterr().out
        assert "v1.0" in out

    def test_create_json_format(self, sample_config, capsys):
        args = make_args(title="v1.0", description="First release", due="2026-04-01")
        with _patch_all(sample_config, self.adapter):
            milestone_cmd.handle_create(args, fmt="json")

        out = capsys.readouterr().out
        data = json.loads(out)
        item = data[0]
        assert item["title"] == "v1.0"

    def test_create_adapter_error_propagates(self, sample_config):
        self.adapter.create_milestone.side_effect = RuntimeError("API error")
        args = make_args(title="v1.0", description=None, due=None)
        with _patch_all(sample_config, self.adapter):
            with pytest.raises(RuntimeError, match="API error"):
                milestone_cmd.handle_create(args, fmt="table")

    def test_create_args_forwarded_correctly(self, sample_config):
        args = make_args(title="v4.0", description="Major release", due="2026-12-31")
        with _patch_all(sample_config, self.adapter):
            milestone_cmd.handle_create(args, fmt="table")

        call_kwargs = self.adapter.create_milestone.call_args.kwargs
        assert call_kwargs["title"] == "v4.0"
        assert call_kwargs["description"] == "Major release"
        assert call_kwargs["due_date"] == "2026-12-31"

    def test_title_with_surrounding_whitespace_is_stripped(self, sample_config):
        args = make_args(title="  v1.0  ", description=None, due=None)
        with _patch_all(sample_config, self.adapter):
            milestone_cmd.handle_create(args, fmt="table")

        call_kwargs = self.adapter.create_milestone.call_args.kwargs
        assert call_kwargs["title"] == "v1.0"

    def test_whitespace_only_title_raises_config_error(self, sample_config):
        args = make_args(title="   ", description=None, due=None)
        with _patch_all(sample_config, self.adapter):
            with pytest.raises(ConfigError):
                milestone_cmd.handle_create(args, fmt="table")


class TestHandleDelete:
    def setup_method(self):
        self.milestone = _make_milestone()
        self.adapter = _make_adapter(self.milestone)

    def test_delete_calls_adapter(self, sample_config):
        args = make_args(number=3)
        with _patch_all(sample_config, self.adapter):
            milestone_cmd.handle_delete(args, fmt="table")

        self.adapter.delete_milestone.assert_called_once_with(number=3)

    def test_delete_prints_message(self, sample_config, capsys):
        args = make_args(number=5)
        with _patch_all(sample_config, self.adapter):
            milestone_cmd.handle_delete(args, fmt="table")

        out = capsys.readouterr().out
        assert "5" in out
        assert "Deleted" in out
