from dataclasses import dataclass
from typing import Optional
from itertools import groupby

@dataclass
class Rejections:
    InvoiceNumber: int
    Group: int
    RejCode1: str
    Carrier: Optional[str] = None
    LineItemPost: bool = False
    Paycode: Optional[str] = None
    RejCode2: Optional[str] = None
    RejCode3: Optional[str] = None
    RejCode4: Optional[str] = None
    Remark1: Optional[str] = None
    Remark2: Optional[str] = None
    Remark3: Optional[str] = None
    Remark4: Optional[str] = None
    BatchNumber: Optional[int] = None
    Completed: bool = False
    Comments: Optional[str] = None
    
    @classmethod
    def from_dataframe_row(cls, row) -> 'Rejections':
        """Create a Rejections instance from a pandas DataFrame row."""
        return Rejections(
            InvoiceNumber=row['InvoiceNumber'],
            Group=row['Group'],
            RejCode1=row['RejCode1'],
            Carrier=row.get('Carrier'),
            LineItemPost=row.get('LineItemPost', False),
            Paycode=row.get('Paycode'),
            RejCode2=row.get('RejCode2'),
            RejCode3=row.get('RejCode3'),
            RejCode4=row.get('RejCode4'),
            Remark1=row.get('Remark1'),
            Remark2=row.get('Remark2'),
            Remark3=row.get('Remark3'),
            Remark4=row.get('Remark4'),
            BatchNumber=row.get('BatchNumber'),
            Completed=row.get('Completed', False),
            Comments=row.get('Comments')
        )

    @classmethod
    def from_dataframe(cls, df) -> list['Rejections']:
        """Create a list of Rejections instances from a pandas DataFrame."""
        return [cls.from_dataframe_row(row) for _, row in df.iterrows()]

@dataclass
class RejectionGrouped:
    group_id: Optional[int]
    rejections: list[Rejections]
    
    @classmethod
    def from_rejections(cls, rejections: list[Rejections]) -> 'RejectionGrouped':
        """Convert a list of Rejections into a RejectionGrouped object."""
        if not rejections:
            return cls(group_id=None, rejections=[])
        
        group_id = rejections[0].Group
        return cls(group_id=group_id, rejections=rejections)
    
    @staticmethod
    def group_by_group_num(rejections: list[Rejections]) -> list['RejectionGrouped']:
        """Group a list of Rejections by their Group field into multiple RejectionGrouped objects."""
        
        sorted_rejections = sorted(rejections, key=lambda r: r.Group)
        grouped = []
        
        for group_id, items in groupby(sorted_rejections, key=lambda r: r.Group):
            grouped.append(RejectionGrouped(group_id=group_id, rejections=list(items)))
        
        return grouped