"""kiwoom_client._call_paginated 회귀 테스트.

명세상 list 반환 TR 응답 헤더에 cont-yn/next-key 정의됨. 1페이지로 끝나는 경우(cont-yn=N)와
다중 페이지(cont-yn=Y → next-key 후속 호출) 모두 list 누적이 정확한지 검증.
"""

from __future__ import annotations

import sys
import unittest
from pathlib import Path
from unittest import mock

_PARENT = Path(__file__).resolve().parent.parent.parent
if str(_PARENT) not in sys.path:
    sys.path.insert(0, str(_PARENT))

import kiwoom_client as kc


class CallPaginatedTests(unittest.TestCase):
    def setUp(self):
        mock.patch.object(kc, 'auth_headers', return_value={}).start()
        mock.patch.object(kc, 'base_url', return_value='https://api.kiwoom.com').start()
        mock.patch.object(kc, 'issue_token', return_value=None).start()
        mock.patch('kiwoom_client.time.sleep', return_value=None).start()
        self.post = mock.patch.object(kc, '_http_post_full').start()
        self.addCleanup(mock.patch.stopall)

    def test_single_page_returns_immediately(self):
        """cont-yn=N → 1회 호출, list 그대로 반환."""
        self.post.return_value = (
            {'return_code': 0, 'tot_pl_amt': '1000', 'tdy_trde_diary': [{'stk_cd': 'A005930'}]},
            {'cont-yn': 'N', 'next-key': ''},
        )
        out = kc._call_paginated('일반', 'ka10170', {}, list_field='tdy_trde_diary')
        self.assertEqual(self.post.call_count, 1)
        self.assertEqual(len(out['tdy_trde_diary']), 1)
        self.assertEqual(out['tot_pl_amt'], '1000')

    def test_multi_page_accumulates_list(self):
        """cont-yn=Y → next-key로 후속 호출, list 누적."""
        self.post.side_effect = [
            (
                {'return_code': 0, 'tot_pl_amt': '500',
                 'tdy_trde_diary': [{'stk_cd': 'A005930'}, {'stk_cd': 'A035720'}]},
                {'cont-yn': 'Y', 'next-key': 'page2'},
            ),
            (
                {'return_code': 0, 'tot_pl_amt': '500',
                 'tdy_trde_diary': [{'stk_cd': 'A000660'}]},
                {'cont-yn': 'N', 'next-key': ''},
            ),
        ]
        out = kc._call_paginated('일반', 'ka10170', {}, list_field='tdy_trde_diary')
        self.assertEqual(self.post.call_count, 2)
        self.assertEqual(len(out['tdy_trde_diary']), 3)
        codes = [it['stk_cd'] for it in out['tdy_trde_diary']]
        self.assertEqual(codes, ['A005930', 'A035720', 'A000660'])

    def test_three_pages(self):
        self.post.side_effect = [
            ({'return_code': 0, 'list': [1, 2]}, {'cont-yn': 'Y', 'next-key': 'p2'}),
            ({'return_code': 0, 'list': [3]},    {'cont-yn': 'Y', 'next-key': 'p3'}),
            ({'return_code': 0, 'list': [4, 5]}, {'cont-yn': 'N', 'next-key': ''}),
        ]
        out = kc._call_paginated('일반', 'tr', {}, list_field='list')
        self.assertEqual(self.post.call_count, 3)
        self.assertEqual(out['list'], [1, 2, 3, 4, 5])

    def test_max_pages_safety_cap(self):
        """무한 cont-yn=Y 응답 → max_pages 초과 시 RuntimeError."""
        self.post.return_value = (
            {'return_code': 0, 'list': [1]},
            {'cont-yn': 'Y', 'next-key': 'next'},
        )
        with self.assertRaisesRegex(RuntimeError, '페이지 한도'):
            kc._call_paginated('일반', 'tr', {}, list_field='list', max_pages=3)

    def test_cont_yn_y_without_next_key_stops(self):
        """cont-yn=Y지만 next-key 빈 값 → 페이징 중단."""
        self.post.return_value = (
            {'return_code': 0, 'list': [1]},
            {'cont-yn': 'Y', 'next-key': ''},
        )
        out = kc._call_paginated('일반', 'tr', {}, list_field='list')
        self.assertEqual(self.post.call_count, 1)
        self.assertEqual(out['list'], [1])

    def test_first_page_8005_token_retry(self):
        """첫 페이지 8005 → 토큰 재발급 후 재호출 정상."""
        self.post.side_effect = [
            ({'return_code': 1, 'return_msg': '8005 Token이 유효하지 않습니다'}, {}),
            ({'return_code': 0, 'list': [1, 2]}, {'cont-yn': 'N', 'next-key': ''}),
        ]
        out = kc._call_paginated('일반', 'tr', {}, list_field='list')
        self.assertEqual(self.post.call_count, 2)
        self.assertEqual(out['list'], [1, 2])

    def test_non_8005_error_raises(self):
        self.post.return_value = (
            {'return_code': 1, 'return_msg': '잘못된 파라미터'},
            {},
        )
        with self.assertRaisesRegex(RuntimeError, '잘못된 파라미터'):
            kc._call_paginated('일반', 'tr', {}, list_field='list')

    def test_empty_list_field_initialized(self):
        """1페이지 응답에 list_field 없어도 빈 list로 초기화."""
        self.post.return_value = (
            {'return_code': 0},
            {'cont-yn': 'N', 'next-key': ''},
        )
        out = kc._call_paginated('일반', 'tr', {}, list_field='list')
        self.assertEqual(out['list'], [])


if __name__ == '__main__':
    unittest.main(verbosity=2)
