"""guards.evaluate_stock_state 회귀 테스트.

정책 (등급별 차등 — 2026-05-07 결정):
- 거부: orderWarning ∈ {2 정리매매, 4 투자위험} 또는 state 에 '거래정지'·'정리매매' 키워드
- 경고: orderWarning ∈ {1 ETF주의, 3 단기과열, 5 투자경과} 또는 state 에 '관리종목'
"""

from __future__ import annotations

import unittest
from unittest import mock

from orders import guards, handler, ledger


class EvaluateStockStateTests(unittest.TestCase):
    def test_normal_stock_passes_no_warning(self):
        meta = {'code': '005930', 'state': '증거금20%|담보대출|신용가능', 'order_warning': '0'}
        out = guards.evaluate_stock_state(meta)
        self.assertTrue(out['result'].ok)
        self.assertIsNone(out['warning'])

    def test_no_meta_passes_no_warning(self):
        out = guards.evaluate_stock_state(None)
        self.assertTrue(out['result'].ok)
        self.assertIsNone(out['warning'])

    def test_order_warning_2_정리매매_rejects(self):
        meta = {'code': 'X', 'state': '', 'order_warning': '2'}
        out = guards.evaluate_stock_state(meta)
        self.assertFalse(out['result'].ok)
        self.assertEqual(out['result'].code, 'STOCK_STATE_BLOCKED')
        self.assertIn('정리매매', out['result'].message)

    def test_order_warning_4_투자위험_rejects(self):
        meta = {'code': 'X', 'state': '', 'order_warning': '4'}
        out = guards.evaluate_stock_state(meta)
        self.assertFalse(out['result'].ok)
        self.assertIn('투자위험', out['result'].message)

    def test_state_거래정지_rejects(self):
        meta = {'code': 'X', 'state': '거래정지', 'order_warning': '0'}
        out = guards.evaluate_stock_state(meta)
        self.assertFalse(out['result'].ok)
        self.assertIn('거래정지', out['result'].message)

    def test_state_정리매매_rejects(self):
        meta = {'code': 'X', 'state': '정리매매|거래제한', 'order_warning': '0'}
        out = guards.evaluate_stock_state(meta)
        self.assertFalse(out['result'].ok)

    def test_order_warning_1_ETF주의_warns(self):
        meta = {'code': 'X', 'state': '', 'order_warning': '1'}
        out = guards.evaluate_stock_state(meta)
        self.assertTrue(out['result'].ok)
        self.assertIsNotNone(out['warning'])
        self.assertIn('ETF투자주의요망', out['warning'])

    def test_order_warning_3_단기과열_warns(self):
        meta = {'code': 'X', 'state': '', 'order_warning': '3'}
        out = guards.evaluate_stock_state(meta)
        self.assertTrue(out['result'].ok)
        self.assertIn('단기과열', out['warning'])

    def test_order_warning_5_투자경과_warns(self):
        meta = {'code': 'X', 'state': '', 'order_warning': '5'}
        out = guards.evaluate_stock_state(meta)
        self.assertTrue(out['result'].ok)
        self.assertIn('투자경과', out['warning'])

    def test_state_관리종목_warns(self):
        meta = {'code': 'X', 'state': '관리종목|증거금100%', 'order_warning': '0'}
        out = guards.evaluate_stock_state(meta)
        self.assertTrue(out['result'].ok)
        self.assertIn('관리종목', out['warning'])

    def test_reject_takes_precedence_over_warning(self):
        """state에 '관리종목'(경고) + orderWarning=2(정리매매·거부) → 거부 우선."""
        meta = {'code': 'X', 'state': '관리종목', 'order_warning': '2'}
        out = guards.evaluate_stock_state(meta)
        self.assertFalse(out['result'].ok)


def _md_with_meta(stock_meta):
    """handler 통합 테스트용 fixture market_data."""
    from datetime import datetime, timedelta, timezone
    KST = timezone(timedelta(hours=9))
    return {
        'now': datetime(2026, 5, 6, 10, 0, tzinfo=KST),
        'is_holiday': False,
        'nxt_eligible': True,
        'current_price': 75050,
        'prev_close': 75000,
        'upper_limit': 100000,
        'lower_limit': 50000,
        'halt': False,
        'vi': False,
        'orderbook': {
            'asks': [{'price': 75100, 'qty': 200}],
            'bids': [{'price': 75000, 'qty': 180}],
        },
        'broker_executions': [],
        'broker_query_error': None,
        'balance_d2': 5_000_000,
        'stock_meta': stock_meta,
    }


class HandlerIntegrationTests(unittest.TestCase):
    """propose_trade가 종목 상태 가드를 적용하는지 통합 검증."""

    def setUp(self):
        self.collect = mock.patch.object(handler.datasource, 'collect_market_data').start()
        mock.patch.object(handler.sidecar, 'guard_or_raise', return_value=None).start()
        mock.patch.object(handler, '_sweep_expired_and_notify', return_value=None).start()
        mock.patch.object(handler.ledger, 'append', return_value=None).start()
        mock.patch.object(ledger, 'last_terminal_event', return_value=None).start()
        mock.patch.object(ledger, 'last_event_for_symbol', return_value=None).start()
        fake_pending = mock.MagicMock()
        fake_pending.card_id = 'TEST'
        fake_pending.pin = '1234'
        fake_pending.expiry_seconds = 120
        fake_pending.account_label = '일반'
        self.issue = mock.patch.object(handler._pin_store, 'issue',
                                       return_value=fake_pending).start()
        self.addCleanup(mock.patch.stopall)

    def test_정리매매_rejects_before_card(self):
        self.collect.return_value = _md_with_meta(
            {'code': '005930', 'state': '', 'order_warning': '2'}
        )
        res = handler.propose_trade(
            account='일반', side='BUY', symbol='005930', symbol_name='X',
            qty=1, order_type='LIMIT', price=75000,
        )
        self.assertFalse(res['ok'])
        self.assertIn('STOCK_STATE_BLOCKED', res['message'])
        self.issue.assert_not_called()

    def test_관리종목_card_includes_warning(self):
        self.collect.return_value = _md_with_meta(
            {'code': '005930', 'state': '관리종목', 'order_warning': '0'}
        )
        res = handler.propose_trade(
            account='일반', side='BUY', symbol='005930', symbol_name='X',
            qty=1, order_type='LIMIT', price=75000,
        )
        self.assertTrue(res['ok'], msg=res.get('message'))
        self.assertIn('관리종목', res['card_message'])

    def test_normal_stock_no_warning_in_card(self):
        self.collect.return_value = _md_with_meta(
            {'code': '005930', 'state': '증거금20%', 'order_warning': '0'}
        )
        res = handler.propose_trade(
            account='일반', side='BUY', symbol='005930', symbol_name='X',
            qty=1, order_type='LIMIT', price=75000,
        )
        self.assertTrue(res['ok'])
        self.assertNotIn('키움 경고', res['card_message'])


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