"""
core.recurring_leaves 단위 테스트.
"""
import os
import sys
from datetime import date

import pytest

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

from core.recurring_leaves import (
    matches, expand_for_range, expand_for_date, describe_pattern, _parse_pattern,
)


class TestParsePattern:
    @pytest.mark.parametrize("pattern,expected_kind", [
        ('weekly:friday', 'weekly'),
        ('weekly:fri', 'weekly'),
        ('weekly:mon,wed,fri', 'weekly'),
        ('biweekly:friday', 'biweekly'),
        ('monthly:15', 'monthly'),
        ('monthly:1', 'monthly'),
    ])
    def test_valid(self, pattern, expected_kind):
        result = _parse_pattern(pattern)
        assert result is not None
        assert result[0] == expected_kind

    @pytest.mark.parametrize("pattern", [
        '', 'weekly', 'weekly:', 'weekly:xyz',
        'monthly:0', 'monthly:32', 'monthly:abc',
        'unknown:fri', None,
    ])
    def test_invalid(self, pattern):
        assert _parse_pattern(pattern) is None


class TestMatches:
    def _rec(self, pattern, start='2026-01-01', end=None, days=0.5, leave_type='반차'):
        return {
            'pattern': pattern,
            'start_date': start,
            'end_date': end,
            'days': days,
            'leave_type': leave_type,
        }

    def test_weekly_single_day(self):
        rec = self._rec('weekly:friday')
        assert matches(rec, date(2026, 5, 1))   # Fri
        assert not matches(rec, date(2026, 5, 2))  # Sat
        assert not matches(rec, date(2026, 5, 4))  # Mon

    def test_weekly_multiple_days(self):
        rec = self._rec('weekly:mon,wed,fri')
        assert matches(rec, date(2026, 5, 4))   # Mon
        assert matches(rec, date(2026, 5, 6))   # Wed
        assert matches(rec, date(2026, 5, 8))   # Fri
        assert not matches(rec, date(2026, 5, 5))  # Tue
        assert not matches(rec, date(2026, 5, 7))  # Thu

    def test_biweekly_alignment(self):
        # start_date 2026-01-02 = Friday (week 0)
        rec = self._rec('biweekly:friday', start='2026-01-02')
        assert matches(rec, date(2026, 1, 2))   # week 0
        assert not matches(rec, date(2026, 1, 9))   # week 1
        assert matches(rec, date(2026, 1, 16))  # week 2

    def test_monthly(self):
        rec = self._rec('monthly:15')
        assert matches(rec, date(2026, 1, 15))
        assert matches(rec, date(2026, 5, 15))
        assert not matches(rec, date(2026, 5, 14))
        assert not matches(rec, date(2026, 5, 16))

    def test_monthly_skipped_in_short_month(self):
        # 31일은 30일 달에는 매치되지 않음
        rec = self._rec('monthly:31')
        assert matches(rec, date(2026, 1, 31))
        assert not matches(rec, date(2026, 4, 30))  # 4월 31일 없음

    def test_before_start(self):
        rec = self._rec('weekly:friday', start='2026-05-01')
        assert matches(rec, date(2026, 5, 1))
        assert not matches(rec, date(2026, 4, 24))  # 시작 전

    def test_after_end(self):
        rec = self._rec('weekly:friday', start='2026-01-01', end='2026-04-30')
        assert matches(rec, date(2026, 4, 24))  # 종료일 이전 금요일
        assert not matches(rec, date(2026, 5, 1))  # 종료일 이후

    def test_no_end_means_forever(self):
        rec = self._rec('weekly:friday', start='2026-01-01', end=None)
        assert matches(rec, date(2030, 1, 4))  # 4년 후 금요일

    def test_invalid_pattern_returns_false(self):
        rec = self._rec('garbage:xyz')
        assert not matches(rec, date(2026, 5, 1))


class TestExpandRange:
    def _rec(self, pattern, start='2026-01-01'):
        return {
            'id': 1, 'pattern': pattern, 'start_date': start, 'end_date': None,
            'days': 0.5, 'leave_type': '반차', 'memo': '',
        }

    def test_expand_weekly_one_month(self):
        rec = self._rec('weekly:friday')
        occs = expand_for_range([rec], date(2026, 5, 1), date(2026, 5, 31))
        # 5월 금요일: 1, 8, 15, 22, 29 = 5회
        assert len(occs) == 5
        assert all(o.date.weekday() == 4 for o in occs)

    def test_expand_empty_when_outside(self):
        rec = self._rec('weekly:friday', start='2027-01-01')
        occs = expand_for_range([rec], date(2026, 5, 1), date(2026, 5, 31))
        assert occs == []

    def test_expand_invalid_range(self):
        # start > end
        rec = self._rec('weekly:friday')
        occs = expand_for_range([rec], date(2026, 5, 31), date(2026, 5, 1))
        assert occs == []

    def test_expand_multiple_recs(self):
        rec_fri = self._rec('weekly:friday')
        rec_mon = self._rec('weekly:monday')
        rec_mon['id'] = 2
        occs = expand_for_range([rec_fri, rec_mon], date(2026, 5, 1), date(2026, 5, 7))
        # 5/1=Fri (rec_fri), 5/4=Mon (rec_mon)
        assert len(occs) == 2

    def test_expand_for_date_single(self):
        rec = self._rec('monthly:15')
        occs = expand_for_date([rec], date(2026, 5, 15))
        assert len(occs) == 1
        assert occs[0].date == date(2026, 5, 15)


class TestDescribePattern:
    def test_weekly_korean(self):
        assert '매주' in describe_pattern('weekly:friday')
        assert '금' in describe_pattern('weekly:friday')

    def test_biweekly(self):
        assert '격주' in describe_pattern('biweekly:friday')

    def test_monthly(self):
        assert '매월' in describe_pattern('monthly:15')
        assert '15' in describe_pattern('monthly:15')
