#!/usr/bin/env python3
"""utils/atomic_write.py 테스트 스위트"""

import json
import os
import sys
from pathlib import Path
from unittest.mock import patch

import pytest

sys.path.insert(0, str(Path(__file__).parent.parent.parent))

from utils.atomic_write import atomic_json_write, atomic_text_write, atomic_yaml_write


class TestAtomicJsonWrite:
    """atomic_json_write() 테스트"""

    def test_writes_valid_json(self, tmp_path):
        """JSON 데이터를 올바르게 씀"""
        target = tmp_path / "output.json"
        data = {"key": "value", "num": 42, "list": [1, 2, 3]}
        atomic_json_write(target, data)

        assert target.exists()
        loaded = json.loads(target.read_text())
        assert loaded == data

    def test_creates_parent_directories(self, tmp_path):
        """상위 디렉토리 자동 생성"""
        target = tmp_path / "nested" / "deep" / "output.json"
        atomic_json_write(target, {"ok": True})
        assert target.exists()

    def test_overwrites_existing_file(self, tmp_path):
        """기존 파일 덮어쓰기"""
        target = tmp_path / "output.json"
        target.write_text('{"old": "data"}')
        atomic_json_write(target, {"new": "data"})
        loaded = json.loads(target.read_text())
        assert loaded == {"new": "data"}

    def test_accepts_string_path(self, tmp_path):
        """문자열 경로 허용"""
        target = str(tmp_path / "output.json")
        atomic_json_write(target, [1, 2, 3])
        assert json.loads(Path(target).read_text()) == [1, 2, 3]

    def test_no_temp_file_left_on_success(self, tmp_path):
        """성공 후 임시 파일 없음"""
        target = tmp_path / "output.json"
        atomic_json_write(target, {"ok": True})
        tmp_files = list(tmp_path.glob("*.tmp"))
        assert len(tmp_files) == 0

    def test_no_temp_file_left_on_error(self, tmp_path):
        """직렬화 오류 발생 시 임시 파일 정리"""
        target = tmp_path / "output.json"

        class Unserializable:
            pass

        with pytest.raises(TypeError):
            atomic_json_write(target, {"bad": Unserializable()})

        # 임시 파일 남지 않음
        tmp_files = list(tmp_path.glob("*.tmp"))
        assert len(tmp_files) == 0

    def test_utf8_unicode_content(self, tmp_path):
        """한글 등 유니코드 데이터 올바르게 씀"""
        target = tmp_path / "unicode.json"
        data = {"message": "안녕하세요", "emoji": "🎉"}
        atomic_json_write(target, data)
        loaded = json.loads(target.read_text(encoding="utf-8"))
        assert loaded["message"] == "안녕하세요"

    def test_custom_indent(self, tmp_path):
        """indent 파라미터 적용"""
        target = tmp_path / "output.json"
        atomic_json_write(target, {"a": 1}, indent=4)
        content = target.read_text()
        assert "    " in content  # 4-space indent

    def test_keyboard_interrupt_cleans_temp(self, tmp_path):
        """KeyboardInterrupt 발생 시 임시 파일 정리"""
        target = tmp_path / "output.json"

        with patch("json.dump", side_effect=KeyboardInterrupt):
            with pytest.raises(KeyboardInterrupt):
                atomic_json_write(target, {"key": "value"})

        tmp_files = list(tmp_path.glob("*.tmp"))
        assert len(tmp_files) == 0


class TestAtomicYamlWrite:
    """atomic_yaml_write() 테스트"""

    def test_writes_valid_yaml(self, tmp_path):
        """YAML 데이터를 올바르게 씀"""
        yaml = pytest.importorskip("yaml")
        target = tmp_path / "output.yaml"
        data = {"name": "thor", "team": "dev2", "active": True}
        atomic_yaml_write(target, data)

        assert target.exists()
        loaded = yaml.safe_load(target.read_text())
        assert loaded == data

    def test_creates_parent_directories(self, tmp_path):
        """상위 디렉토리 자동 생성"""
        target = tmp_path / "nested" / "config.yaml"
        atomic_yaml_write(target, {"ok": True})
        assert target.exists()

    def test_overwrites_existing_file(self, tmp_path):
        """기존 파일 덮어쓰기"""
        yaml = pytest.importorskip("yaml")
        target = tmp_path / "config.yaml"
        target.write_text("old: data\n")
        atomic_yaml_write(target, {"new": "data"})
        loaded = yaml.safe_load(target.read_text())
        assert loaded == {"new": "data"}

    def test_accepts_string_path(self, tmp_path):
        """문자열 경로 허용"""
        yaml = pytest.importorskip("yaml")
        target = str(tmp_path / "output.yaml")
        atomic_yaml_write(target, {"key": "val"})
        loaded = yaml.safe_load(Path(target).read_text())
        assert loaded == {"key": "val"}

    def test_no_temp_file_left_on_success(self, tmp_path):
        """성공 후 임시 파일 없음"""
        target = tmp_path / "output.yaml"
        atomic_yaml_write(target, {"ok": True})
        tmp_files = list(tmp_path.glob("*.tmp"))
        assert len(tmp_files) == 0

    def test_no_temp_file_left_on_error(self, tmp_path):
        """YAML 직렬화 오류 시 임시 파일 정리"""
        target = tmp_path / "output.yaml"

        with patch("yaml.dump", side_effect=RuntimeError("yaml error")):
            with pytest.raises(RuntimeError):
                atomic_yaml_write(target, {"key": "value"})

        tmp_files = list(tmp_path.glob("*.tmp"))
        assert len(tmp_files) == 0

    def test_keyboard_interrupt_cleans_temp(self, tmp_path):
        """KeyboardInterrupt 발생 시 임시 파일 정리"""
        target = tmp_path / "output.yaml"

        with patch("yaml.dump", side_effect=KeyboardInterrupt):
            with pytest.raises(KeyboardInterrupt):
                atomic_yaml_write(target, {"key": "value"})

        tmp_files = list(tmp_path.glob("*.tmp"))
        assert len(tmp_files) == 0


class TestAtomicTextWrite:
    """atomic_text_write() 테스트"""

    def test_writes_text_content(self, tmp_path):
        """텍스트 내용을 올바르게 씀"""
        target = tmp_path / "output.txt"
        atomic_text_write(target, "hello world\nline 2")
        assert target.read_text() == "hello world\nline 2"

    def test_creates_parent_directories(self, tmp_path):
        """상위 디렉토리 자동 생성"""
        target = tmp_path / "sub" / "output.txt"
        atomic_text_write(target, "content")
        assert target.exists()

    def test_overwrites_existing_file(self, tmp_path):
        """기존 파일 덮어쓰기"""
        target = tmp_path / "output.txt"
        target.write_text("old content")
        atomic_text_write(target, "new content")
        assert target.read_text() == "new content"

    def test_accepts_string_path(self, tmp_path):
        """문자열 경로 허용"""
        target = str(tmp_path / "output.txt")
        atomic_text_write(target, "text content")
        assert Path(target).read_text() == "text content"

    def test_no_temp_file_left_on_success(self, tmp_path):
        """성공 후 임시 파일 없음"""
        target = tmp_path / "output.txt"
        atomic_text_write(target, "content")
        tmp_files = list(tmp_path.glob("*.tmp"))
        assert len(tmp_files) == 0

    def test_utf8_unicode_text(self, tmp_path):
        """한글 등 유니코드 텍스트 올바르게 씀"""
        target = tmp_path / "unicode.txt"
        atomic_text_write(target, "안녕하세요 🎉")
        assert target.read_text(encoding="utf-8") == "안녕하세요 🎉"

    def test_keyboard_interrupt_cleans_temp(self, tmp_path):
        """KeyboardInterrupt 발생 시 임시 파일 정리"""
        target = tmp_path / "output.txt"

        with patch("os.fsync", side_effect=KeyboardInterrupt):
            with pytest.raises(KeyboardInterrupt):
                atomic_text_write(target, "content")

        tmp_files = list(tmp_path.glob("*.tmp"))
        assert len(tmp_files) == 0

    def test_empty_string(self, tmp_path):
        """빈 문자열도 올바르게 씀"""
        target = tmp_path / "empty.txt"
        atomic_text_write(target, "")
        assert target.read_text() == ""


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
