"""
HWP 문서 구조 (Section, Body, HwpFile)
"""

import io
import struct
import zlib
from dataclasses import dataclass, field
from typing import List

import olefile

from .constants import ControlID, RecordTag
from .models import Header
from .char_paragraph import Paragraph
from .record_stream import RecordStream



@dataclass
class Section:
    """HWP 섹션"""
    paragraphs: List[Paragraph] = field(default_factory=list)
    table_metadata: List[dict] = field(default_factory=list)

    def to_string(self) -> str:
        """섹션을 문자열로 변환"""
        return "\n".join(para.to_string() for para in self.paragraphs)

@dataclass
class Body:
    """HWP 본문"""
    sections: List[Section] = field(default_factory=list)

    def to_string(self) -> str:
        """본문을 문자열로 변환"""
        return "\n\n".join(section.to_string() for section in self.sections)

@dataclass
class HwpFile:
    """HWP 파일 내부 구조"""
    header: Header
    body: Body
    char_shapes: dict = field(default_factory=dict)  # {char_shape_id: CharShapeInfo}

    @classmethod
    def from_file(cls, file_path: str) -> 'HwpFile':
        """파일 경로로부터 HWP 문서 로드"""
        with open(file_path, 'rb') as f:
            return cls.from_bytes(f.read())

    @classmethod
    def from_bytes(cls, data: bytes) -> 'HwpFile':
        """
        바이트 데이터로부터 HWP 문서 파싱

        참고: HWP 파일은 Compound File Binary (CFB) 형식을 따릅니다.
        주요 스트림:
        - FileHeader: 파일 인식 정보 (문서 버전, 압축 여부 등)
        - DocInfo: 문서 정보 (글꼴, 글자 모양, 문단 모양 등)
        - BodyText: 본문 데이터 (문단, 표, 개체 등)
        """
        # CFB 파일 열기
        ole = olefile.OleFileIO(data)

        # 1. 헤더 파싱
        header_stream = ole.openstream('FileHeader')
        header = Header.from_stream(header_stream)

        # 2. DocInfo 파싱 (글자 모양 정보)
        char_shapes = {}
        if ole.exists('DocInfo'):
            docinfo_stream = ole.openstream('DocInfo')
            docinfo_data = docinfo_stream.read()
            
            # 압축 해제
            if header.flags.compressed:
                try:
                    docinfo_data = zlib.decompress(docinfo_data, -zlib.MAX_WBITS)
                except zlib.error:
                    pass
            
            # CHAR_SHAPE 레코드 파싱
            char_shapes = cls._parse_char_shapes(docinfo_data)

        # 3. 본문 파싱
        body = Body()

        # distributed 플래그에 따라 ViewText 또는 BodyText 사용
        # distributed: 배포용 문서 (텍스트만 포함)
        body_path_prefix = 'ViewText' if header.flags.distributed else 'BodyText'

        # 섹션 개수 확인
        section_idx = 0
        while True:
            section_path = f'{body_path_prefix}/Section{section_idx}'
            if not ole.exists(section_path):
                break

            # 섹션 스트림 열기
            section_stream = ole.openstream(section_path)
            section_data = section_stream.read()

            # 압축 해제 (필요시)
            if header.flags.compressed:
                try:
                    section_data = zlib.decompress(section_data, -zlib.MAX_WBITS)
                except zlib.error:
                    # 압축 해제 실패시 원본 데이터 사용
                    pass

            # 레코드 파싱
            section = cls._parse_section(section_data)
            body.sections.append(section)

            section_idx += 1

        ole.close()
        return cls(header=header, body=body, char_shapes=char_shapes)

    @staticmethod
    def _parse_char_shapes(docinfo_data: bytes) -> dict:
        """
        DocInfo 스트림에서 CHAR_SHAPE 레코드 파싱

        CHAR_SHAPE 레코드 구조:
        - 글꼴 ID (WORD, 2바이트)
        - 장평 (UINT8, 1바이트, 50~200%)
        - 자간 (INT8, 1바이트, -50~50%)
        - 글자 크기 (INT32, 4바이트, 포인트 단위)
        - 속성 플래그 (UINT32, 4바이트, 굵게/기울임/밑줄 등)
        """
        char_shapes = {}
        stream = io.BytesIO(docinfo_data)
        cursor = RecordStream(stream)
        all_records = cursor.read_all_records()
        
        char_shape_id = 0
        for record in all_records:
            if record.tag_id == RecordTag.HWPTAG_CHAR_SHAPE:
                try:
                    # 글자 모양 데이터 파싱 (최소 72바이트)
                    if len(record.data) >= 56:
                        # 기준 크기 (offset 42, INT32, 포인트 * 100)
                        base_size = struct.unpack('<i', record.data[42:46])[0]
                        font_size = base_size / 100.0 if base_size > 0 else 10.0
                        
                        # 속성 (offset 46, UINT32, bit flags)
                        attr = struct.unpack('<I', record.data[46:50])[0]
                        bold = (attr & 0x1) != 0
                        italic = (attr & 0x2) != 0
                        underline = (attr & 0x4) != 0
                        
                        # 한글 글꼴 ID (offset 0, WORD)
                        font_id = struct.unpack('<H', record.data[0:2])[0]
                        
                        # 한글 장평 (offset 14, UINT8)
                        if len(record.data) > 20:
                            expansion = record.data[14]
                        else:
                            expansion = 100
                        
                        # 한글 자간 (offset 21, INT8)
                        if len(record.data) > 27:
                            spacing = struct.unpack('<b', bytes([record.data[21]]))[0]
                        else:
                            spacing = 0
                        
                        # 글자 색상 (offset 52, COLORREF)
                        color = struct.unpack('<I', record.data[52:56])[0] if len(record.data) >= 56 else 0
                        
                        char_shapes[char_shape_id] = CharShapeInfo(
                            font_size=font_size,
                            font_id=font_id,
                            bold=bold,
                            italic=italic,
                            underline=underline,
                            expansion=expansion,
                            spacing=spacing,
                            color=color
                        )
                        char_shape_id += 1
                except (struct.error, IndexError):
                    # 파싱 실패 시 기본값 사용
                    char_shapes[char_shape_id] = CharShapeInfo()
                    char_shape_id += 1
        
        return char_shapes

    @staticmethod
    def _parse_section(section_data: bytes) -> Section:
        """
        BodyText 스트림의 Section 데이터 파싱

        Section 데이터 구조:
        - 문단 (HWPTAG_PARA_HEADER, HWPTAG_PARA_TEXT 등)
        - 표, 개체 등 (HWPTAG_TABLE, HWPTAG_SHAPE_COMPONENT 등)
        """
        section = Section()
        stream = io.BytesIO(section_data)
        cursor = RecordStream(stream)

        # 모든 레코드 읽기
        all_records = cursor.read_all_records()

        # 단락 단위로 그룹화 (PARA_HEADER로 시작)
        current_para_records = []
        table_info_list = []  # 모든 테이블 정보 (리스트)
        current_table_idx = None  # 현재 처리 중인 테이블 인덱스

        for idx, record in enumerate(all_records):
            # 테이블 관련 레코드 파싱
            if record.tag_id == RecordTag.HWPTAG_CTRL_HEADER:
                # Control ID 확인 (TABLE = 0x74626C20)
                if len(record.data) >= 4:
                    ctrl_id = struct.unpack('<I', record.data[0:4])[0]
                    if ctrl_id == ControlID.TABLE:
                        # 새 테이블 항목 생성
                        table_info_list.append({
                            'ctrl_id': ctrl_id,
                            'idx': idx,
                            'level': record.level,
                            'cell_para_counts': []  # 각 셀의 문단 개수 배열
                        })
                        current_table_idx = len(table_info_list) - 1
                else:
                    current_table_idx = None
            
            elif record.tag_id == RecordTag.HWPTAG_SHAPE_COMPONENT:
                # 테이블 위치/크기/여백 정보 (46+ bytes)
                # SHAPE_COMPONENT는 현재 테이블에 속함
                if current_table_idx is not None and len(record.data) >= 46:
                    # Offset 8: Y position (HWPUNIT, 4 bytes)
                    y = struct.unpack('<i', record.data[8:12])[0]
                    # Offset 12: X position (HWPUNIT, 4 bytes)
                    x = struct.unpack('<i', record.data[12:16])[0]
                    # Offset 16: Width (HWPUNIT, 4 bytes)
                    width = struct.unpack('<I', record.data[16:20])[0]
                    # Offset 20: Height (HWPUNIT, 4 bytes)
                    height = struct.unpack('<I', record.data[20:24])[0]
                    # Offset 28: Margins (HWPUNIT16 array[4], 8 bytes)
                    margins = struct.unpack('<hhhh', record.data[28:36])
                    margin_left, margin_right, margin_top, margin_bottom = margins
                    
                    # 첫 번째 SHAPE_COMPONENT만 사용 (중복 방지)
                    if 'width' not in table_info_list[current_table_idx]:
                        table_info_list[current_table_idx].update({
                            'x': x, 'y': y,
                            'width': width, 'height': height,
                            'margin_left': margin_left, 'margin_right': margin_right,
                            'margin_top': margin_top, 'margin_bottom': margin_bottom
                        })
            
            elif record.tag_id == RecordTag.HWPTAG_LIST_HEADER:
                # LIST_HEADER 구조: 문단 개수(2) + 속성(4) + 패딩?(1) + 셀 속성(26)
                if current_table_idx is not None:
                    # 문단 개수 (각 셀마다 하나씩)
                    para_count = struct.unpack('<H', record.data[0:2])[0]
                    table_info_list[current_table_idx]['cell_para_counts'].append(para_count)
                    
                    # 셀 속성은 offset 7부터 시작 (6 bytes header + 1 byte padding)
                    cell_attr_offset = 7
                    if len(record.data) >= cell_attr_offset + 26:
                        # 표 80: 셀 속성 (26 bytes)
                        col = struct.unpack('<H', record.data[cell_attr_offset+1:cell_attr_offset+3])[0]
                        row = struct.unpack('<H', record.data[cell_attr_offset+3:cell_attr_offset+5])[0]
                        colspan = struct.unpack('<H', record.data[cell_attr_offset+5:cell_attr_offset+7])[0]
                        rowspan = struct.unpack('<H', record.data[cell_attr_offset+7:cell_attr_offset+9])[0]
                        width = struct.unpack('<I', record.data[cell_attr_offset+9:cell_attr_offset+13])[0]
                        height = struct.unpack('<I', record.data[cell_attr_offset+13:cell_attr_offset+17])[0]
                        
                        # 유효성 검사: col, row가 테이블 크기 내에 있는지
                        table_info = table_info_list[current_table_idx]
                        rows = table_info.get('rows', 999)
                        cols = table_info.get('cols', 999)
                        
                        if col < cols and row < rows:
                            # 셀별 크기 정보를 table_info에 추가
                            if 'cell_widths' not in table_info:
                                table_info['cell_widths'] = []
                                table_info['cell_heights'] = []
                                table_info['cell_colspans'] = []
                                table_info['cell_rowspans'] = []
                            
                            table_info['cell_widths'].append(width)
                            table_info['cell_heights'].append(height)
                            table_info['cell_colspans'].append(colspan)
                            table_info['cell_rowspans'].append(rowspan)
            
            elif record.tag_id == RecordTag.HWPTAG_TABLE:
                # 테이블 행/열 정보 및 Row Size 배열 파싱
                # Offset 0: 속성 (4 bytes), Offset 4: RowCount (2 bytes), Offset 6: nCols (2 bytes)
                # Offset 8: CellSpacing (2 bytes), Offset 10: 안쪽 여백 (8 bytes)
                # Offset 18: Row Size 배열 (2×rows bytes)
                if current_table_idx is not None and len(record.data) >= 10:
                    table_attr = struct.unpack('<I', record.data[0:4])[0]
                    rows = struct.unpack('<H', record.data[4:6])[0]
                    cols = struct.unpack('<H', record.data[6:8])[0]
                    cell_spacing = struct.unpack('<h', record.data[8:10])[0]
                    
                    # print(f"[DEBUG] HWPTAG_TABLE: table_idx={current_table_idx}, data_len={len(record.data)}")
                    # print(f"  attr={table_attr:08x}, rows={rows}, cols={cols}, spacing={cell_spacing}")
                    
                    if rows > 0 and cols > 0 and rows < 1000 and cols < 1000:
                        # Row Size 배열 파싱 (offset 18, 2 bytes × rows)
                        row_size_offset = 18
                        row_sizes = []
                        if len(record.data) >= row_size_offset + (2 * rows):
                            for i in range(rows):
                                offset = row_size_offset + (i * 2)
                                row_size = struct.unpack('<H', record.data[offset:offset+2])[0]
                                row_sizes.append(row_size)
                        
                        table_info_list[current_table_idx].update({
                            'rows': rows, 
                            'cols': cols,
                            'cell_spacing': cell_spacing,
                            'table_attr': table_attr,
                            'row_sizes': row_sizes if row_sizes else None,
                        })

            # 단락 그룹화
            if record.tag_id == RecordTag.HWPTAG_PARA_HEADER:
                # 이전 단락 완성
                if current_para_records:
                    paragraph = Paragraph.from_records(current_para_records)
                    section.paragraphs.append(paragraph)

                # 새 단락 시작
                current_para_records = [record]
            else:
                current_para_records.append(record)

        # 마지막 단락 추가
        if current_para_records:
            paragraph = Paragraph.from_records(current_para_records)
            section.paragraphs.append(paragraph)

        # 테이블 메타데이터 저장 (리스트)
        section.table_metadata = table_info_list

        return section

    def to_text(self) -> str:
        """HWP 문서를 텍스트로 변환"""
        return self.body.to_string()

@dataclass
class CharShapeInfo:
    """문자 서식 정보 (HWPTAG_CHAR_SHAPE 기반)"""
    font_size: float = 10.0  # 포인트 단위 (기준 크기 / 100)
    font_id: int = 0  # 한글 글꼴 ID (언어별 7개 중 첫번째)
    bold: bool = False  # 굵게
    italic: bool = False  # 기울임
    underline: bool = False  # 밑줄
    expansion: int = 100  # 장평 (50~200, 기본 100%)
    spacing: int = 0  # 자간 (-50~50, 기본 0%)
    color: int = 0x00000000  # RGB 색상 (0x00bbggrr)