"""
utils.crash_handler 단위 테스트.

GUI 다이얼로그는 호출하지 않음 (테스트 주체는 _log_crash + _send_to_gitea).
"""
import os
import sys
import tempfile
from unittest.mock import patch, MagicMock

import pytest

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from core.database import Database
from utils.crash_handler import _log_crash, _send_to_gitea


@pytest.fixture
def db():
    p = os.path.join(tempfile.gettempdir(), 'clockout_crash_ut.db')
    if os.path.exists(p):
        os.remove(p)
    d = Database(p)
    yield d
    try:
        os.remove(p)
    except OSError:
        pass


class TestLogCrash:
    def test_creates_table_and_inserts(self, db):
        _log_crash(db, 'TestExc', 'msg', 'Traceback ...', 'v2.6.0')
        conn = db.get_connection()
        cur = conn.cursor()
        cur.execute("SELECT exception_type, message, app_version FROM crash_log")
        row = cur.fetchone()
        conn.close()
        assert row[0] == 'TestExc'
        assert row[1] == 'msg'
        assert row[2] == 'v2.6.0'

    def test_table_idempotent_creation(self, db):
        # 두 번 호출해도 두 행이 들어가야 (CREATE TABLE IF NOT EXISTS)
        _log_crash(db, 'A', 'a', 't', 'v1')
        _log_crash(db, 'B', 'b', 't', 'v1')
        conn = db.get_connection()
        cur = conn.cursor()
        cur.execute("SELECT COUNT(*) FROM crash_log")
        count = cur.fetchone()[0]
        conn.close()
        assert count == 2

    def test_silent_on_db_error(self, db):
        # 잘못된 DB 객체를 줘도 예외 전파 안 됨 (안 그러면 후킹 자체가 죽음)
        broken = MagicMock()
        broken.get_connection.side_effect = RuntimeError('boom')
        # raise되면 안 됨
        _log_crash(broken, 'X', 'x', 'tb', 'v')


class TestSendToGitea:
    @patch('utils.crash_handler.urllib.request.urlopen')
    def test_success(self, mock_urlopen):
        resp = MagicMock()
        resp.status = 201
        resp.__enter__.return_value = resp
        resp.__exit__.return_value = False
        mock_urlopen.return_value = resp

        ok = _send_to_gitea('fake_token', 'title', 'body')
        assert ok is True

        req = mock_urlopen.call_args[0][0]
        # PAT 헤더 확인
        assert req.headers.get('Authorization') == 'token fake_token'
        # User-Agent 위장
        assert 'Mozilla' in req.headers.get('User-agent', '')

    @patch('utils.crash_handler.urllib.request.urlopen')
    def test_4xx_returns_false(self, mock_urlopen):
        resp = MagicMock()
        resp.status = 401
        resp.__enter__.return_value = resp
        resp.__exit__.return_value = False
        mock_urlopen.return_value = resp

        assert _send_to_gitea('bad', 't', 'b') is False

    @patch('utils.crash_handler.urllib.request.urlopen')
    def test_network_error(self, mock_urlopen):
        import urllib.error
        mock_urlopen.side_effect = urllib.error.URLError('boom')
        assert _send_to_gitea('t', 't', 'b') is False

    @patch('utils.crash_handler.urllib.request.urlopen')
    def test_payload_json(self, mock_urlopen):
        import json as _json
        resp = MagicMock()
        resp.status = 201
        resp.__enter__.return_value = resp
        resp.__exit__.return_value = False
        mock_urlopen.return_value = resp

        _send_to_gitea('tok', 'TITLE', 'BODY')
        req = mock_urlopen.call_args[0][0]
        body = _json.loads(req.data.decode('utf-8'))
        assert body['title'] == 'TITLE'
        assert body['body'] == 'BODY'
