#!/usr/bin/env python3
"""Test case generator for differential testing."""

import itertools
from datetime import datetime, timedelta
from typing import List, Tuple, Dict, Any
import random


class TestCaseGenerator:
    """Generate comprehensive test cases for croniter differential testing."""

    # Cron field ranges
    MINUTE_RANGE = (0, 59)
    HOUR_RANGE = (0, 23)
    DAY_RANGE = (1, 31)
    MONTH_RANGE = (1, 12)
    DOW_RANGE = (0, 6)
    SECOND_RANGE = (0, 59)

    # Special values
    WEEKDAY_NAMES = ['MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT', 'SUN']
    MONTH_NAMES = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']

    def __init__(self, seed: int = 42):
        self.rng = random.Random(seed)

    def generate_basic_expressions(self) -> List[str]:
        """Generate basic cron expressions covering all operators."""
        expressions = [
            # Wildcards
            "* * * * *",
            "* * * * * *",

            # Specific values
            "0 0 1 1 *",
            "30 12 15 6 *",
            "0 0 * * 0",

            # Ranges
            "0-30 * * * *",
            "0 9-17 * * *",
            "0 0 1-15 * *",
            "0 0 * 1-6 *",
            "0 0 * * 1-5",

            # Steps
            "*/5 * * * *",
            "0 */2 * * *",
            "0 0 */3 * *",
            "0 0 1 */2 *",
            "0 0 * * */2",

            # Lists
            "0,15,30,45 * * * *",
            "0 0,6,12,18 * * *",
            "0 0 1,15 * *",
            "0 0 * 1,4,7,10 *",
            "0 0 * * 1,3,5",

            # Combinations
            "0-30/5 * * * *",
            "0 9-17/2 * * *",
            "0 0 1-15/3 * *",
            "0,30 9-17 * * 1-5",
            "*/15 8-18/2 * * *",

            # Last day of month
            "0 0 L * *",
            "0 0 L-1 * *",
            "0 0 L-7 * *",

            # Nth weekday
            "0 0 * * 1#1",
            "0 0 * * 5#2",
            "0 0 * * 0#4",

            # Last weekday of month
            "0 0 * * 5L",
            "0 0 * * 1L",

            # Weekday names
            "0 0 * * MON",
            "0 0 * * MON-FRI",
            "0 0 * * SAT,SUN",

            # Month names
            "0 0 1 JAN *",
            "0 0 15 JAN-JUN *",
            "0 0 * JAN,JUL *",

            # 6-field (with seconds)
            "0 0 0 * * *",
            "*/30 * * * * *",
            "0,30 0 0 * * *",
            "0-30/10 * * * * *",

            # Edge cases
            "59 23 31 12 *",
            "0 0 29 2 *",
            "0 0 31 * *",
        ]
        return expressions

    def generate_hash_expressions(self) -> List[Tuple[str, str]]:
        """Generate hash expressions with hash_id."""
        expressions = [
            ("H * * * *", "test-id"),
            ("H H * * *", "test-id"),
            ("H H * * H", "test-id"),
            ("H H H * *", "test-id"),
            ("H H * H *", "test-id"),
            ("H(0-30) * * * *", "test-id"),
            ("H H(9-17) * * *", "test-id"),
            ("H/15 * * * *", "test-id"),
            ("H H/6 * * *", "test-id"),
        ]
        return expressions

    def generate_random_expressions(self) -> List[Tuple[str, str]]:
        """Generate random expressions with hash_id."""
        expressions = [
            ("R R * * *", "random-id"),
            ("R R R * *", "random-id"),
            ("R(0-30) * * * *", "random-id"),
            ("R R(9-17) * * *", "random-id"),
            ("R R R(1-15) * *", "random-id"),
        ]
        return expressions

    def generate_boundary_dates(self) -> List[datetime]:
        """Generate boundary test dates."""
        dates = [
            # Regular dates
            datetime(2020, 1, 1, 0, 0, 0),
            datetime(2020, 6, 15, 12, 30, 45),
            datetime(2020, 12, 31, 23, 59, 59),

            # Leap year
            datetime(2020, 2, 28, 0, 0, 0),
            datetime(2020, 2, 29, 0, 0, 0),
            datetime(2024, 2, 29, 0, 0, 0),

            # Non-leap year
            datetime(2021, 2, 28, 0, 0, 0),
            datetime(2023, 2, 28, 23, 59, 59),

            # Month boundaries
            datetime(2020, 1, 31, 0, 0, 0),
            datetime(2020, 3, 31, 0, 0, 0),
            datetime(2020, 4, 30, 0, 0, 0),

            # Year boundaries
            datetime(2019, 12, 31, 23, 59, 59),
            datetime(2020, 1, 1, 0, 0, 0),

            # Weekday boundaries
            datetime(2020, 1, 6, 0, 0, 0),  # Monday
            datetime(2020, 1, 12, 0, 0, 0),  # Sunday

            # Various times
            datetime(2020, 6, 15, 0, 0, 0),
            datetime(2020, 6, 15, 12, 0, 0),
            datetime(2020, 6, 15, 23, 59, 59),
        ]
        return dates

    def generate_equivalence_classes(self) -> List[Dict[str, Any]]:
        """Generate test cases using equivalence class partitioning."""
        test_cases = []

        # Minute field equivalence classes
        for expr in ["0 * * * *", "30 * * * *", "59 * * * *", "*/15 * * * *", "0-30 * * * *"]:
            test_cases.append({
                'expr': expr,
                'start': datetime(2020, 1, 1, 0, 0, 0),
                'iterations': 10,
                'description': f'Minute field: {expr}'
            })

        # Hour field equivalence classes
        for expr in ["0 0 * * *", "0 12 * * *", "0 23 * * *", "0 */6 * * *", "0 9-17 * * *"]:
            test_cases.append({
                'expr': expr,
                'start': datetime(2020, 1, 1, 0, 0, 0),
                'iterations': 10,
                'description': f'Hour field: {expr}'
            })

        # Day field equivalence classes
        for expr in ["0 0 1 * *", "0 0 15 * *", "0 0 31 * *", "0 0 */7 * *", "0 0 L * *"]:
            test_cases.append({
                'expr': expr,
                'start': datetime(2020, 1, 1, 0, 0, 0),
                'iterations': 10,
                'description': f'Day field: {expr}'
            })

        # Month field equivalence classes
        for expr in ["0 0 1 1 *", "0 0 1 6 *", "0 0 1 12 *", "0 0 1 */3 *"]:
            test_cases.append({
                'expr': expr,
                'start': datetime(2020, 1, 1, 0, 0, 0),
                'iterations': 10,
                'description': f'Month field: {expr}'
            })

        # Weekday field equivalence classes
        for expr in ["0 0 * * 0", "0 0 * * 3", "0 0 * * 6", "0 0 * * 1-5", "0 0 * * 1#2"]:
            test_cases.append({
                'expr': expr,
                'start': datetime(2020, 1, 1, 0, 0, 0),
                'iterations': 10,
                'description': f'Weekday field: {expr}'
            })

        return test_cases

    def generate_boundary_value_tests(self) -> List[Dict[str, Any]]:
        """Generate test cases using boundary value analysis."""
        test_cases = []

        # Minute boundaries
        for val in [0, 1, 29, 30, 58, 59]:
            test_cases.append({
                'expr': f'{val} * * * *',
                'start': datetime(2020, 1, 1, 0, 0, 0),
                'iterations': 5,
                'description': f'Minute boundary: {val}'
            })

        # Hour boundaries
        for val in [0, 1, 11, 12, 22, 23]:
            test_cases.append({
                'expr': f'0 {val} * * *',
                'start': datetime(2020, 1, 1, 0, 0, 0),
                'iterations': 5,
                'description': f'Hour boundary: {val}'
            })

        # Day boundaries
        for val in [1, 2, 15, 28, 29, 30, 31]:
            test_cases.append({
                'expr': f'0 0 {val} * *',
                'start': datetime(2020, 1, 1, 0, 0, 0),
                'iterations': 5,
                'description': f'Day boundary: {val}'
            })

        # Month boundaries
        for val in [1, 2, 6, 11, 12]:
            test_cases.append({
                'expr': f'0 0 1 {val} *',
                'start': datetime(2020, 1, 1, 0, 0, 0),
                'iterations': 5,
                'description': f'Month boundary: {val}'
            })

        # Weekday boundaries
        for val in [0, 1, 3, 5, 6]:
            test_cases.append({
                'expr': f'0 0 * * {val}',
                'start': datetime(2020, 1, 1, 0, 0, 0),
                'iterations': 5,
                'description': f'Weekday boundary: {val}'
            })

        return test_cases

    def generate_fuzzing_cases(self, count: int = 1000) -> List[Dict[str, Any]]:
        """Generate random test cases for fuzzing."""
        test_cases = []

        for i in range(count):
            # Generate random cron expression
            expr_type = self.rng.choice(['basic', 'range', 'step', 'list', 'combo'])

            if expr_type == 'basic':
                expr = self._generate_random_basic()
            elif expr_type == 'range':
                expr = self._generate_random_range()
            elif expr_type == 'step':
                expr = self._generate_random_step()
            elif expr_type == 'list':
                expr = self._generate_random_list()
            else:
                expr = self._generate_random_combo()

            # Random start date
            year = self.rng.randint(2010, 2030)
            month = self.rng.randint(1, 12)
            day = self.rng.randint(1, 28)
            hour = self.rng.randint(0, 23)
            minute = self.rng.randint(0, 59)
            second = self.rng.randint(0, 59)

            test_cases.append({
                'expr': expr,
                'start': datetime(year, month, day, hour, minute, second),
                'iterations': self.rng.randint(5, 20),
                'description': f'Fuzz test {i}: {expr}'
            })

        return test_cases

    def _generate_random_basic(self) -> str:
        """Generate random basic expression."""
        fields = []
        ranges = [(0, 59), (0, 23), (1, 31), (1, 12), (0, 6)]
        for min_val, max_val in ranges:
            if self.rng.random() < 0.3:
                fields.append('*')
            else:
                fields.append(str(self.rng.randint(min_val, max_val)))
        return ' '.join(fields)

    def _generate_random_range(self) -> str:
        """Generate random range expression."""
        fields = []
        ranges = [(0, 59), (0, 23), (1, 31), (1, 12), (0, 6)]
        for min_val, max_val in ranges:
            if self.rng.random() < 0.3:
                fields.append('*')
            else:
                start = self.rng.randint(min_val, max_val - 1) if max_val > min_val else min_val
                end = self.rng.randint(start + 1, max_val) if start < max_val else max_val
                if end > start:
                    fields.append(f'{start}-{end}')
                else:
                    fields.append(str(start))
        return ' '.join(fields)

    def _generate_random_step(self) -> str:
        """Generate random step expression."""
        fields = []
        ranges = [(0, 59), (0, 23), (1, 31), (1, 12), (0, 6)]
        for min_val, max_val in ranges:
            if self.rng.random() < 0.3:
                fields.append('*')
            else:
                step = self.rng.choice([2, 3, 5, 10, 15])
                if self.rng.random() < 0.5:
                    fields.append(f'*/{step}')
                else:
                    start = self.rng.randint(min_val, max_val // 2)
                    end = self.rng.randint(start + step, max_val) if start + step <= max_val else max_val
                    if end > start:
                        fields.append(f'{start}-{end}/{step}')
                    else:
                        fields.append(f'*/{step}')
        return ' '.join(fields)

    def _generate_random_list(self) -> str:
        """Generate random list expression."""
        fields = []
        ranges = [(0, 59), (0, 23), (1, 31), (1, 12), (0, 6)]
        for min_val, max_val in ranges:
            if self.rng.random() < 0.3:
                fields.append('*')
            else:
                count = self.rng.randint(2, 4)
                values = [str(self.rng.randint(min_val, max_val)) for _ in range(count)]
                fields.append(','.join(sorted(set(values))))
        return ' '.join(fields)

    def _generate_random_combo(self) -> str:
        """Generate random combination expression."""
        fields = []
        ranges = [(0, 59), (0, 23), (1, 31), (1, 12), (0, 6)]
        for min_val, max_val in ranges:
            choice = self.rng.choice(['*', 'value', 'range', 'step', 'list'])
            if choice == '*':
                fields.append('*')
            elif choice == 'value':
                fields.append(str(self.rng.randint(min_val, max_val)))
            elif choice == 'range':
                start = self.rng.randint(min_val, max_val - 1)
                end = self.rng.randint(start + 1, max_val)
                fields.append(f'{start}-{end}')
            elif choice == 'step':
                step = self.rng.choice([2, 3, 5])
                fields.append(f'*/{step}')
            else:
                count = self.rng.randint(2, 3)
                values = [str(self.rng.randint(min_val, max_val)) for _ in range(count)]
                fields.append(','.join(sorted(set(values))))
        return ' '.join(fields)


if __name__ == '__main__':
    gen = TestCaseGenerator()

    print("Basic expressions:", len(gen.generate_basic_expressions()))
    print("Hash expressions:", len(gen.generate_hash_expressions()))
    print("Random expressions:", len(gen.generate_random_expressions()))
    print("Boundary dates:", len(gen.generate_boundary_dates()))
    print("Equivalence classes:", len(gen.generate_equivalence_classes()))
    print("Boundary value tests:", len(gen.generate_boundary_value_tests()))
    print("Fuzzing cases (100):", len(gen.generate_fuzzing_cases(100)))
