"""
test_tech_debt.py

scripts/tech-debt.py 단위 테스트 (아르고스 작성)

테스트 항목:
- load_data(): 기존 파일 로드 / 파일 없을 때 기본 구조 반환
- save_data(): 파일 저장, 통계 업데이트, last_updated 갱신, 디렉토리 자동 생성
- generate_id(): TD-001 형식 ID 생성, 최대값+1, 갭 처리
- add_item(): 항목 추가, 필드 검증, ID 증가, 퍼시스턴스
- list_items(): 전체/상태별/심각도별 조회, 정렬 검증
- resolve_item(): 해결 처리, None 반환 케이스, 퍼시스턴스
- show_item(): 단건 조회, 존재하지 않는 ID
- show_stats(): 통계 구조, 항목 추가/해결 후 통계 반영
- print_item_table(): 테이블 출력, 빈 목록 메시지
- print_item_detail(): 상세 출력, 해결된 항목 출력
- main(): CLI 서브커맨드 동작 검증

격리: monkeypatch로 _mod.DATA_FILE을 tmp_path로 교체하여 파일시스템 완전 격리
"""

import importlib.util
import json
import os
import sys
from pathlib import Path

import pytest

# ---------------------------------------------------------------------------
# tech-debt 모듈 로드 (하이픈 포함 파일명 → importlib 필요)
# ---------------------------------------------------------------------------

_WORKSPACE = Path(os.environ.get("WORKSPACE_ROOT", "/home/jay/workspace"))
_SCRIPT_PATH = _WORKSPACE / "scripts/tech-debt.py"


def _load_module():
    spec = importlib.util.spec_from_file_location("tech_debt", _SCRIPT_PATH)
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    return module


_mod = _load_module()


# ---------------------------------------------------------------------------
# 공통 픽스처
# ---------------------------------------------------------------------------


@pytest.fixture
def data_file(tmp_path, monkeypatch):
    """DATA_FILE을 tmp_path 아래의 임시 파일로 교체하는 픽스처."""
    tmp_data_file = tmp_path / "memory" / "tech-debt.json"
    monkeypatch.setattr(_mod, "DATA_FILE", tmp_data_file)
    return tmp_data_file


@pytest.fixture
def sample_data():
    """기본 샘플 데이터 구조 반환."""
    return {
        "version": "1.0",
        "items": [
            {
                "id": "TD-001",
                "title": "레거시 인증 시스템 리팩토링",
                "severity": "high",
                "status": "open",
                "description": "JWT 토큰 검증 로직이 여러 곳에 중복되어 있음",
                "tags": ["auth", "security"],
                "created_at": "2026-03-02 04:05:58",
                "resolved_at": None,
                "resolution_note": None,
            }
        ],
        "metadata": {
            "created": "2026-03-02",
            "last_updated": "2026-03-02 04:05:58",
            "stats": {
                "total": 1,
                "open": 1,
                "resolved": 0,
                "high_open": 1,
                "medium_open": 0,
                "low_open": 0,
            },
        },
    }


# ---------------------------------------------------------------------------
# TestLoadData
# ---------------------------------------------------------------------------


class TestLoadData:
    def test_load_existing_data(self, data_file, sample_data):
        """기존 파일이 있으면 내용을 로드한다."""
        data_file.parent.mkdir(parents=True, exist_ok=True)
        data_file.write_text(
            json.dumps(sample_data, ensure_ascii=False), encoding="utf-8"
        )

        result = _mod.load_data()

        assert result["version"] == "1.0"
        assert len(result["items"]) == 1
        assert result["items"][0]["id"] == "TD-001"

    def test_load_missing_file(self, data_file):
        """파일이 없으면 기본 구조를 반환한다."""
        assert not data_file.exists()

        result = _mod.load_data()

        assert isinstance(result, dict)
        assert "version" in result

    def test_default_has_items_list(self, data_file):
        """기본 구조에 items 키가 있고 리스트여야 한다."""
        result = _mod.load_data()

        assert "items" in result
        assert isinstance(result["items"], list)
        assert result["items"] == []

    def test_default_has_metadata(self, data_file):
        """기본 구조에 metadata 키가 있어야 한다."""
        result = _mod.load_data()

        assert "metadata" in result
        assert isinstance(result["metadata"], dict)


# ---------------------------------------------------------------------------
# TestSaveData
# ---------------------------------------------------------------------------


class TestSaveData:
    def test_save_creates_file(self, data_file):
        """save_data()를 호출하면 파일이 생성된다."""
        data = _mod.load_data()
        _mod.save_data(data)

        assert data_file.exists()

    def test_save_updates_stats(self, data_file):
        """save_data() 후 metadata.stats가 올바르게 계산된다."""
        data = {
            "version": "1.0",
            "items": [
                {
                    "id": "TD-001",
                    "title": "High open item",
                    "severity": "high",
                    "status": "open",
                },
                {
                    "id": "TD-002",
                    "title": "Medium open item",
                    "severity": "medium",
                    "status": "open",
                },
                {
                    "id": "TD-003",
                    "title": "Resolved item",
                    "severity": "low",
                    "status": "resolved",
                },
            ],
            "metadata": {"created": "2026-03-02", "last_updated": ""},
        }

        _mod.save_data(data)

        stats = data["metadata"]["stats"]
        assert stats["total"] == 3
        assert stats["open"] == 2
        assert stats["resolved"] == 1
        assert stats["high_open"] == 1
        assert stats["medium_open"] == 1
        assert stats["low_open"] == 0

    def test_save_updates_last_updated(self, data_file):
        """save_data() 후 last_updated가 현재 시각으로 갱신된다."""
        data = _mod.load_data()
        old_timestamp = "1970-01-01 00:00:00"
        data["metadata"]["last_updated"] = old_timestamp

        _mod.save_data(data)

        assert data["metadata"]["last_updated"] != old_timestamp
        # 날짜 형식 검증: "YYYY-MM-DD HH:MM:SS"
        ts = data["metadata"]["last_updated"]
        assert len(ts) == 19
        assert ts[4] == "-" and ts[7] == "-" and ts[10] == " "

    def test_save_creates_parent_dirs(self, tmp_path, monkeypatch):
        """부모 디렉토리가 없어도 자동으로 생성한다."""
        nested_path = tmp_path / "deep" / "nested" / "dir" / "tech-debt.json"
        monkeypatch.setattr(_mod, "DATA_FILE", nested_path)

        data = {
            "version": "1.0",
            "items": [],
            "metadata": {"created": "2026-03-02", "last_updated": ""},
        }
        _mod.save_data(data)

        assert nested_path.exists()

    def test_save_persists_to_disk(self, data_file):
        """저장된 데이터가 실제로 디스크에 반영된다."""
        data = _mod.load_data()
        data["items"].append(
            {
                "id": "TD-001",
                "title": "Disk persist test",
                "severity": "low",
                "status": "open",
            }
        )
        _mod.save_data(data)

        # 파일을 직접 읽어서 확인
        with open(data_file, encoding="utf-8") as f:
            loaded = json.load(f)

        assert len(loaded["items"]) == 1
        assert loaded["items"][0]["title"] == "Disk persist test"


# ---------------------------------------------------------------------------
# TestGenerateId
# ---------------------------------------------------------------------------


class TestGenerateId:
    def test_first_id_is_td_001(self, data_file):
        """items가 비어 있으면 TD-001을 반환한다."""
        result = _mod.generate_id()

        assert result == "TD-001"

    def test_increments_from_existing(self, data_file):
        """TD-001이 있으면 TD-002를 반환한다."""
        data = _mod.load_data()
        data["items"].append(
            {"id": "TD-001", "title": "Existing", "severity": "low", "status": "open"}
        )
        _mod.save_data(data)

        result = _mod.generate_id()

        assert result == "TD-002"

    def test_finds_max_id(self, data_file):
        """여러 항목 중 최대 번호+1을 반환한다."""
        data = _mod.load_data()
        for i in [1, 3, 7]:
            data["items"].append(
                {
                    "id": f"TD-{i:03d}",
                    "title": f"Item {i}",
                    "severity": "low",
                    "status": "open",
                }
            )
        _mod.save_data(data)

        result = _mod.generate_id()

        assert result == "TD-008"

    def test_handles_gap_in_ids(self, data_file):
        """TD-001, TD-005가 있으면 TD-006을 반환한다 (갭 무시, 최대+1)."""
        data = _mod.load_data()
        for i in [1, 5]:
            data["items"].append(
                {
                    "id": f"TD-{i:03d}",
                    "title": f"Item {i}",
                    "severity": "low",
                    "status": "open",
                }
            )
        _mod.save_data(data)

        result = _mod.generate_id()

        assert result == "TD-006"


# ---------------------------------------------------------------------------
# TestAddItem
# ---------------------------------------------------------------------------


class TestAddItem:
    def test_add_item_returns_dict(self, data_file):
        """add_item()은 딕셔너리를 반환한다."""
        result = _mod.add_item("Test item", "high")

        assert isinstance(result, dict)

    def test_add_item_status_open(self, data_file):
        """새로 추가된 항목의 status는 'open'이다."""
        item = _mod.add_item("Status test", "medium")

        assert item["status"] == "open"

    def test_add_item_persisted(self, data_file):
        """추가된 항목이 파일에 저장된다."""
        _mod.add_item("Persist test", "low")

        data = _mod.load_data()
        assert len(data["items"]) == 1
        assert data["items"][0]["title"] == "Persist test"

    def test_add_item_with_tags(self, data_file):
        """태그가 올바르게 저장된다."""
        item = _mod.add_item("Tagged item", "high", tags=["auth", "security"])

        assert item["tags"] == ["auth", "security"]

    def test_add_item_with_description(self, data_file):
        """설명이 올바르게 저장된다."""
        desc = "JWT 토큰 검증 로직 중복"
        item = _mod.add_item("Desc test", "medium", description=desc)

        assert item["description"] == desc

    def test_add_item_increments_id(self, data_file):
        """여러 번 추가하면 ID가 순서대로 증가한다."""
        item1 = _mod.add_item("First", "high")
        item2 = _mod.add_item("Second", "low")
        item3 = _mod.add_item("Third", "medium")

        assert item1["id"] == "TD-001"
        assert item2["id"] == "TD-002"
        assert item3["id"] == "TD-003"

    def test_add_item_default_tags_empty(self, data_file):
        """태그를 지정하지 않으면 빈 리스트가 저장된다."""
        item = _mod.add_item("No tags", "low")

        assert item["tags"] == []

    def test_add_item_all_required_fields_present(self, data_file):
        """반환된 항목에 필수 필드가 모두 포함된다."""
        item = _mod.add_item("Full fields", "high", description="desc", tags=["t1"])

        required_fields = [
            "id",
            "title",
            "severity",
            "status",
            "description",
            "tags",
            "created_at",
            "resolved_at",
            "resolution_note",
        ]
        for field in required_fields:
            assert field in item, f"필드 누락: {field}"

    def test_add_item_resolved_at_is_none(self, data_file):
        """새 항목의 resolved_at은 None이다."""
        item = _mod.add_item("New item", "low")

        assert item["resolved_at"] is None

    def test_add_item_resolution_note_is_none(self, data_file):
        """새 항목의 resolution_note는 None이다."""
        item = _mod.add_item("New item", "low")

        assert item["resolution_note"] is None


# ---------------------------------------------------------------------------
# TestListItems
# ---------------------------------------------------------------------------


class TestListItems:
    @pytest.fixture(autouse=True)
    def setup_items(self, data_file):
        """각 테스트 전에 다양한 항목을 추가한다."""
        _mod.add_item("High open", "high")
        _mod.add_item("Medium open", "medium")
        _mod.add_item("Low open", "low")
        item = _mod.add_item("High to resolve", "high")
        _mod.resolve_item(item["id"], "해결 완료")

    def test_list_all_items(self, data_file):
        """필터 없이 전체 항목을 반환한다."""
        result = _mod.list_items()

        assert len(result) == 4

    def test_list_filter_by_status_open(self, data_file):
        """status='open' 필터 적용 시 open 항목만 반환한다."""
        result = _mod.list_items(status="open")

        assert all(item["status"] == "open" for item in result)
        assert len(result) == 3

    def test_list_filter_by_status_resolved(self, data_file):
        """status='resolved' 필터 적용 시 resolved 항목만 반환한다."""
        result = _mod.list_items(status="resolved")

        assert all(item["status"] == "resolved" for item in result)
        assert len(result) == 1

    def test_list_filter_by_severity(self, data_file):
        """severity 필터 적용 시 해당 심각도 항목만 반환한다."""
        result_high = _mod.list_items(severity="high")
        result_medium = _mod.list_items(severity="medium")
        result_low = _mod.list_items(severity="low")

        assert all(item["severity"] == "high" for item in result_high)
        assert all(item["severity"] == "medium" for item in result_medium)
        assert all(item["severity"] == "low" for item in result_low)

    def test_list_sorted_by_severity(self, data_file):
        """open 항목 내에서 high → medium → low 순으로 정렬된다."""
        result = _mod.list_items(status="open")

        severity_order = {"high": 0, "medium": 1, "low": 2}
        severities = [severity_order[item["severity"]] for item in result]
        assert severities == sorted(severities), "심각도 순 정렬이 올바르지 않습니다"

    def test_list_empty(self, tmp_path, monkeypatch):
        """항목이 없으면 빈 리스트를 반환한다."""
        empty_data_file = tmp_path / "empty" / "tech-debt.json"
        monkeypatch.setattr(_mod, "DATA_FILE", empty_data_file)

        result = _mod.list_items()

        assert result == []

    def test_list_resolved_items_after_open(self, data_file):
        """정렬 결과에서 open 항목이 resolved 항목보다 앞에 나온다."""
        result = _mod.list_items()

        open_indices = [i for i, item in enumerate(result) if item["status"] == "open"]
        resolved_indices = [
            i for i, item in enumerate(result) if item["status"] == "resolved"
        ]

        if open_indices and resolved_indices:
            assert max(open_indices) < min(
                resolved_indices
            ), "open 항목이 resolved 항목보다 먼저 나와야 합니다"

    def test_list_combined_filters(self, data_file):
        """status와 severity 필터를 동시에 적용할 수 있다."""
        result = _mod.list_items(status="open", severity="high")

        assert all(
            item["status"] == "open" and item["severity"] == "high" for item in result
        )


# ---------------------------------------------------------------------------
# TestResolveItem
# ---------------------------------------------------------------------------


class TestResolveItem:
    def test_resolve_sets_status(self, data_file):
        """resolve_item() 후 status가 'resolved'로 변경된다."""
        item = _mod.add_item("To resolve", "high")
        result = _mod.resolve_item(item["id"])

        assert result["status"] == "resolved"

    def test_resolve_sets_resolved_at(self, data_file):
        """resolve_item() 후 resolved_at에 타임스탬프가 설정된다."""
        item = _mod.add_item("To resolve", "high")
        result = _mod.resolve_item(item["id"])

        assert result["resolved_at"] is not None
        # 형식 검증
        ts = result["resolved_at"]
        assert len(ts) == 19
        assert ts[4] == "-" and ts[7] == "-" and ts[10] == " "

    def test_resolve_sets_note(self, data_file):
        """resolution_note가 올바르게 저장된다."""
        item = _mod.add_item("Note test", "medium")
        note = "리팩토링 완료"
        result = _mod.resolve_item(item["id"], resolution_note=note)

        assert result["resolution_note"] == note

    def test_resolve_nonexistent_returns_none(self, data_file):
        """존재하지 않는 ID로 resolve하면 None을 반환한다."""
        result = _mod.resolve_item("TD-999")

        assert result is None

    def test_resolve_already_resolved_returns_none(self, data_file):
        """이미 해결된 항목을 다시 resolve하면 None을 반환한다."""
        item = _mod.add_item("Already resolved", "low")
        _mod.resolve_item(item["id"])

        result = _mod.resolve_item(item["id"])

        assert result is None

    def test_resolve_persists(self, data_file):
        """해결 상태가 파일에 저장된다."""
        item = _mod.add_item("Persist resolve", "high")
        _mod.resolve_item(item["id"], resolution_note="완료")

        # 파일을 새로 로드해서 확인
        data = _mod.load_data()
        saved_item = next(
            (i for i in data["items"] if i["id"] == item["id"]), None
        )
        assert saved_item is not None
        assert saved_item["status"] == "resolved"
        assert saved_item["resolution_note"] == "완료"

    def test_resolve_without_note(self, data_file):
        """note 없이 resolve하면 resolution_note가 None이다."""
        item = _mod.add_item("No note resolve", "medium")
        result = _mod.resolve_item(item["id"])

        assert result["resolution_note"] is None


# ---------------------------------------------------------------------------
# TestShowItem
# ---------------------------------------------------------------------------


class TestShowItem:
    def test_show_existing_item(self, data_file):
        """존재하는 ID로 show_item() 호출 시 항목 딕셔너리를 반환한다."""
        added = _mod.add_item("Show test", "high")
        result = _mod.show_item(added["id"])

        assert result is not None
        assert result["id"] == added["id"]
        assert result["title"] == "Show test"

    def test_show_nonexistent_returns_none(self, data_file):
        """존재하지 않는 ID로 show_item() 호출 시 None을 반환한다."""
        result = _mod.show_item("TD-999")

        assert result is None

    def test_show_returns_correct_item(self, data_file):
        """여러 항목 중 올바른 항목을 반환한다."""
        _mod.add_item("First", "high")
        second = _mod.add_item("Second", "medium")
        _mod.add_item("Third", "low")

        result = _mod.show_item(second["id"])

        assert result["title"] == "Second"
        assert result["severity"] == "medium"


# ---------------------------------------------------------------------------
# TestShowStats
# ---------------------------------------------------------------------------


class TestShowStats:
    def test_stats_structure(self, data_file):
        """show_stats()가 올바른 키를 가진 딕셔너리를 반환한다."""
        result = _mod.show_stats()

        required_keys = [
            "total",
            "open",
            "resolved",
            "high_open",
            "medium_open",
            "low_open",
            "last_updated",
        ]
        for key in required_keys:
            assert key in result, f"통계 키 누락: {key}"

    def test_stats_after_add(self, data_file):
        """항목 추가 후 통계가 올바르게 반영된다."""
        _mod.add_item("High item", "high")
        _mod.add_item("Medium item", "medium")
        _mod.add_item("Low item", "low")

        # 통계는 save_data에서 계산되므로 load 후 확인
        # show_stats()는 파일에 저장된 stats를 읽음
        # 마지막 add_item 후 save_data가 호출되므로 stats 업데이트됨
        stats = _mod.show_stats()

        assert stats["total"] == 3
        assert stats["open"] == 3
        assert stats["resolved"] == 0
        assert stats["high_open"] == 1
        assert stats["medium_open"] == 1
        assert stats["low_open"] == 1

    def test_stats_after_resolve(self, data_file):
        """항목 해결 후 통계가 올바르게 반영된다."""
        item = _mod.add_item("High item", "high")
        _mod.add_item("Medium item", "medium")
        _mod.resolve_item(item["id"])

        stats = _mod.show_stats()

        assert stats["total"] == 2
        assert stats["open"] == 1
        assert stats["resolved"] == 1
        assert stats["high_open"] == 0
        assert stats["medium_open"] == 1

    def test_stats_empty_data(self, data_file):
        """데이터가 없을 때 통계는 모두 0이다."""
        # 파일이 없는 상태 → stats 없음 → 기본값 0
        stats = _mod.show_stats()

        assert stats["total"] == 0
        assert stats["open"] == 0
        assert stats["resolved"] == 0


# ---------------------------------------------------------------------------
# TestPrintItemTable
# ---------------------------------------------------------------------------


class TestPrintItemTable:
    def test_print_with_items(self, capsys, data_file):
        """항목이 있을 때 테이블 형식으로 출력한다."""
        items = [
            {
                "id": "TD-001",
                "title": "Test item",
                "severity": "high",
                "status": "open",
            }
        ]
        _mod.print_item_table(items)

        captured = capsys.readouterr()
        assert "TD-001" in captured.out
        assert "Test item" in captured.out

    def test_print_empty(self, capsys):
        """항목이 없으면 '등록된 항목이 없습니다.' 메시지를 출력한다."""
        _mod.print_item_table([])

        captured = capsys.readouterr()
        assert "등록된 항목이 없습니다." in captured.out

    def test_print_table_header(self, capsys):
        """테이블 헤더(ID, 상태, 심각도 등)가 출력된다."""
        items = [
            {
                "id": "TD-001",
                "title": "Header test",
                "severity": "medium",
                "status": "open",
            }
        ]
        _mod.print_item_table(items)

        captured = capsys.readouterr()
        assert "ID" in captured.out
        assert "상태" in captured.out
        assert "심각도" in captured.out

    def test_print_multiple_items(self, capsys):
        """여러 항목이 모두 출력된다."""
        items = [
            {
                "id": "TD-001",
                "title": "First",
                "severity": "high",
                "status": "open",
            },
            {
                "id": "TD-002",
                "title": "Second",
                "severity": "low",
                "status": "resolved",
            },
        ]
        _mod.print_item_table(items)

        captured = capsys.readouterr()
        assert "TD-001" in captured.out
        assert "TD-002" in captured.out
        assert "First" in captured.out
        assert "Second" in captured.out


# ---------------------------------------------------------------------------
# TestPrintItemDetail
# ---------------------------------------------------------------------------


class TestPrintItemDetail:
    def test_print_detail(self, capsys):
        """항목 상세 정보가 출력된다."""
        item = {
            "id": "TD-001",
            "title": "Detail test",
            "severity": "high",
            "status": "open",
            "description": "상세 설명",
            "tags": ["backend", "refactor"],
            "created_at": "2026-03-02 10:00:00",
            "resolved_at": None,
            "resolution_note": None,
        }
        _mod.print_item_detail(item)

        captured = capsys.readouterr()
        assert "TD-001" in captured.out
        assert "Detail test" in captured.out
        assert "상세 설명" in captured.out
        assert "backend" in captured.out
        assert "2026-03-02 10:00:00" in captured.out

    def test_print_resolved_detail(self, capsys):
        """해결된 항목은 resolved_at과 resolution_note가 출력된다."""
        item = {
            "id": "TD-002",
            "title": "Resolved item",
            "severity": "medium",
            "status": "resolved",
            "description": None,
            "tags": [],
            "created_at": "2026-03-01 09:00:00",
            "resolved_at": "2026-03-02 15:00:00",
            "resolution_note": "리팩토링 완료",
        }
        _mod.print_item_detail(item)

        captured = capsys.readouterr()
        assert "2026-03-02 15:00:00" in captured.out
        assert "리팩토링 완료" in captured.out

    def test_print_detail_without_description(self, capsys):
        """설명이 없어도 오류 없이 출력된다."""
        item = {
            "id": "TD-003",
            "title": "No desc",
            "severity": "low",
            "status": "open",
            "description": None,
            "tags": [],
            "created_at": "2026-03-02 08:00:00",
            "resolved_at": None,
            "resolution_note": None,
        }
        # 예외 없이 실행되어야 함
        _mod.print_item_detail(item)

        captured = capsys.readouterr()
        assert "TD-003" in captured.out

    def test_print_detail_without_tags(self, capsys):
        """태그가 없어도 오류 없이 출력된다."""
        item = {
            "id": "TD-004",
            "title": "No tags",
            "severity": "low",
            "status": "open",
            "description": "설명 있음",
            "tags": [],
            "created_at": "2026-03-02 08:00:00",
            "resolved_at": None,
            "resolution_note": None,
        }
        _mod.print_item_detail(item)

        captured = capsys.readouterr()
        assert "TD-004" in captured.out


# ---------------------------------------------------------------------------
# TestCLIMain
# ---------------------------------------------------------------------------


class TestCLIMain:
    def test_add_command(self, data_file, monkeypatch, capsys):
        """add 서브커맨드가 항목을 추가하고 결과를 출력한다."""
        monkeypatch.setattr(
            sys,
            "argv",
            [
                "tech-debt.py",
                "add",
                "--title",
                "CLI Add Test",
                "--severity",
                "high",
            ],
        )

        _mod.main()

        captured = capsys.readouterr()
        assert "CLI Add Test" in captured.out
        assert "TD-001" in captured.out

        # 파일에 저장 확인
        data = _mod.load_data()
        assert len(data["items"]) == 1
        assert data["items"][0]["title"] == "CLI Add Test"

    def test_list_command(self, data_file, monkeypatch, capsys):
        """list 서브커맨드가 항목 테이블을 출력한다."""
        _mod.add_item("List CLI item", "medium")

        monkeypatch.setattr(
            sys,
            "argv",
            ["tech-debt.py", "list"],
        )

        _mod.main()

        captured = capsys.readouterr()
        assert "List CLI item" in captured.out

    def test_list_command_with_filters(self, data_file, monkeypatch, capsys):
        """list 서브커맨드에 status 필터를 적용할 수 있다."""
        _mod.add_item("Open item", "high")
        item = _mod.add_item("Resolved item", "low")
        _mod.resolve_item(item["id"])

        monkeypatch.setattr(
            sys,
            "argv",
            ["tech-debt.py", "list", "--status", "open"],
        )

        _mod.main()

        captured = capsys.readouterr()
        assert "Open item" in captured.out
        # resolved 항목은 필터링되어 출력되지 않아야 함
        assert "Resolved item" not in captured.out

    def test_resolve_command(self, data_file, monkeypatch, capsys):
        """resolve 서브커맨드가 항목을 해결 처리한다."""
        added = _mod.add_item("Resolve CLI test", "high")

        monkeypatch.setattr(
            sys,
            "argv",
            [
                "tech-debt.py",
                "resolve",
                added["id"],
                "--note",
                "완료 처리",
            ],
        )

        _mod.main()

        captured = capsys.readouterr()
        assert added["id"] in captured.out

        # 상태 변경 확인
        resolved = _mod.show_item(added["id"])
        assert resolved["status"] == "resolved"
        assert resolved["resolution_note"] == "완료 처리"

    def test_show_command(self, data_file, monkeypatch, capsys):
        """show 서브커맨드가 항목 상세를 출력한다."""
        added = _mod.add_item("Show CLI test", "medium", description="상세 내용")

        monkeypatch.setattr(
            sys,
            "argv",
            ["tech-debt.py", "show", added["id"]],
        )

        _mod.main()

        captured = capsys.readouterr()
        assert "Show CLI test" in captured.out
        assert "상세 내용" in captured.out

    def test_stats_command(self, data_file, monkeypatch, capsys):
        """stats 서브커맨드가 통계를 출력한다."""
        _mod.add_item("Stats item 1", "high")
        _mod.add_item("Stats item 2", "medium")

        monkeypatch.setattr(
            sys,
            "argv",
            ["tech-debt.py", "stats"],
        )

        _mod.main()

        captured = capsys.readouterr()
        assert "총 항목" in captured.out
        assert "미해결" in captured.out
        assert "해결됨" in captured.out

    def test_no_command_exits(self, data_file, monkeypatch):
        """서브커맨드 없이 실행하면 sys.exit(1)이 발생한다."""
        monkeypatch.setattr(sys, "argv", ["tech-debt.py"])

        with pytest.raises(SystemExit) as exc_info:
            _mod.main()

        assert exc_info.value.code == 1

    def test_show_nonexistent_exits(self, data_file, monkeypatch, capsys):
        """존재하지 않는 ID로 show 호출 시 sys.exit(1)이 발생한다."""
        monkeypatch.setattr(
            sys,
            "argv",
            ["tech-debt.py", "show", "TD-999"],
        )

        with pytest.raises(SystemExit) as exc_info:
            _mod.main()

        assert exc_info.value.code == 1

    def test_add_command_with_tags_and_desc(self, data_file, monkeypatch, capsys):
        """add 서브커맨드에서 태그와 설명이 올바르게 처리된다."""
        monkeypatch.setattr(
            sys,
            "argv",
            [
                "tech-debt.py",
                "add",
                "--title",
                "Full Add Test",
                "--severity",
                "low",
                "--desc",
                "Full description",
                "--tags",
                "auth",
                "security",
            ],
        )

        _mod.main()

        data = _mod.load_data()
        item = data["items"][0]
        assert item["description"] == "Full description"
        assert item["tags"] == ["auth", "security"]

    def test_resolve_command_without_note(self, data_file, monkeypatch, capsys):
        """resolve 서브커맨드에서 note 없이 해결 처리할 수 있다."""
        added = _mod.add_item("No note resolve", "medium")

        monkeypatch.setattr(
            sys,
            "argv",
            ["tech-debt.py", "resolve", added["id"]],
        )

        _mod.main()

        resolved = _mod.show_item(added["id"])
        assert resolved["status"] == "resolved"
        assert resolved["resolution_note"] is None
