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

실제 정부 API는 호출하지 않음 — 모두 urlopen mock.
"""
import json
import os
import sys
from unittest.mock import patch, MagicMock

import pytest

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

from utils.holiday_api import (
    fetch_korean_holidays, _parse_response, is_configured,
)


def _ok_response(items):
    """API 정상 응답 형식 빌드."""
    return {
        'response': {
            'header': {'resultCode': '00', 'resultMsg': 'NORMAL SERVICE.'},
            'body': {
                'items': {'item': items} if items else {'item': []},
                'numOfRows': 100,
                'pageNo': 1,
                'totalCount': len(items) if isinstance(items, list) else 1,
            },
        }
    }


class TestParseResponse:
    def test_multiple_items(self):
        items = [
            {'dateKind': '01', 'dateName': '근로자의 날', 'isHoliday': 'Y',
             'locdate': 20260501, 'seq': 1},
            {'dateKind': '01', 'dateName': '어린이날', 'isHoliday': 'Y',
             'locdate': 20260505, 'seq': 1},
        ]
        out = _parse_response(_ok_response(items))
        assert len(out) == 2
        assert out[0]['date'] == '2026-05-01'
        assert out[0]['name'] == '근로자의 날'
        assert out[0]['is_holiday'] is True
        assert out[1]['date'] == '2026-05-05'

    def test_single_item_as_dict(self):
        # API가 결과 1개일 때 list가 아닌 dict로 반환하는 케이스
        item = {'dateName': '근로자의 날', 'isHoliday': 'Y', 'locdate': 20260501}
        data = {
            'response': {
                'header': {'resultCode': '00'},
                'body': {'items': {'item': item}, 'totalCount': 1},
            }
        }
        out = _parse_response(data)
        assert len(out) == 1
        assert out[0]['name'] == '근로자의 날'

    def test_empty_year(self):
        # totalCount=0 같은 정상 빈 응답
        data = {
            'response': {
                'header': {'resultCode': '00'},
                'body': {'items': '', 'totalCount': 0},
            }
        }
        assert _parse_response(data) == []

    def test_error_result_code(self):
        data = {
            'response': {
                'header': {'resultCode': '30', 'resultMsg': 'SERVICE_KEY_IS_NOT_REGISTERED'},
                'body': {},
            }
        }
        assert _parse_response(data) is None

    def test_isholiday_n_filtered_at_caller_level(self):
        # 응답 자체엔 is_holiday=False도 포함됨 (예: 24절기). _parse는 그대로 반환,
        # 실제 휴일 등록은 호출자가 is_holiday=True만 필터.
        items = [{'dateName': '동지', 'isHoliday': 'N', 'locdate': 20261221}]
        out = _parse_response(_ok_response(items))
        assert len(out) == 1
        assert out[0]['is_holiday'] is False

    def test_locdate_str_form(self):
        items = [{'dateName': '근로자의 날', 'isHoliday': 'Y', 'locdate': '20260501'}]
        out = _parse_response(_ok_response(items))
        assert out[0]['date'] == '2026-05-01'

    def test_invalid_locdate_skipped(self):
        items = [
            {'dateName': '근로자의 날', 'isHoliday': 'Y', 'locdate': 20260501},
            {'dateName': '잘못된 날짜', 'isHoliday': 'Y', 'locdate': 'abc'},
            {'dateName': '짧은 날짜', 'isHoliday': 'Y', 'locdate': '202605'},
        ]
        out = _parse_response(_ok_response(items))
        assert len(out) == 1  # 정상 1개만
        assert out[0]['name'] == '근로자의 날'

    def test_missing_required_fields_skipped(self):
        items = [
            {'dateName': '근로자의 날', 'isHoliday': 'Y', 'locdate': 20260501},
            {'isHoliday': 'Y', 'locdate': 20260505},  # name 없음
            {'dateName': '신정', 'isHoliday': 'Y'},   # locdate 없음
        ]
        out = _parse_response(_ok_response(items))
        assert len(out) == 1

    def test_malformed_response_returns_none(self):
        # response 구조 자체가 깨진 경우
        assert _parse_response({'random': 'data'}) is None or _parse_response({'random': 'data'}) == []
        # 위는 implementation-dependent — 둘 다 합리적
        # 정확히는: response 키 없음 → response={}, header={}, resultCode != '00' → None
        assert _parse_response({}) is None


class TestFetchNetwork:
    @patch('utils.holiday_api.urllib.request.urlopen')
    def test_success(self, mock_urlopen):
        items = [{'dateName': '근로자의 날', 'isHoliday': 'Y', 'locdate': 20260501}]
        resp = MagicMock()
        resp.read.return_value = json.dumps(_ok_response(items)).encode('utf-8')
        resp.__enter__.return_value = resp
        resp.__exit__.return_value = False
        mock_urlopen.return_value = resp

        out = fetch_korean_holidays(2026)
        assert out is not None
        assert len(out) == 1
        assert out[0]['date'] == '2026-05-01'

        # 요청 URL에 serviceKey + solYear=2026 + _type=json 포함되었는지
        req = mock_urlopen.call_args[0][0]
        assert 'serviceKey=' in req.full_url
        assert 'solYear=2026' in req.full_url
        assert '_type=json' in req.full_url

    @patch('utils.holiday_api.urllib.request.urlopen')
    def test_network_error_returns_none(self, mock_urlopen):
        import urllib.error
        mock_urlopen.side_effect = urllib.error.URLError('boom')
        assert fetch_korean_holidays(2026) is None

    @patch('utils.holiday_api.urllib.request.urlopen')
    def test_timeout_returns_none(self, mock_urlopen):
        mock_urlopen.side_effect = TimeoutError('slow')
        assert fetch_korean_holidays(2026) is None

    @patch('utils.holiday_api.urllib.request.urlopen')
    def test_invalid_json_returns_none(self, mock_urlopen):
        resp = MagicMock()
        resp.read.return_value = b'<html>error</html>'
        resp.__enter__.return_value = resp
        resp.__exit__.return_value = False
        mock_urlopen.return_value = resp
        assert fetch_korean_holidays(2026) is None


class TestConfigured:
    def test_key_set(self, monkeypatch):
        import utils.holiday_api as _ha
        monkeypatch.setattr(_ha, '_SERVICE_KEY', 'fa419259319e31d2fcd4f959e65da817fe2f19894bff340a63889db7a8ffac93')
        assert _ha.is_configured() is True

    def test_key_empty(self, monkeypatch):
        import utils.holiday_api as _ha
        monkeypatch.setattr(_ha, '_SERVICE_KEY', '')
        assert _ha.is_configured() is False
